/*! @preserve

 * bootbox.js

 * version: 5.5.2

 * author: Nick Payne <nick@kurai.co.uk>

 * license: MIT

 * http://bootboxjs.com/

 */

(function (root, factory) {

  'use strict';

  if (typeof define === 'function' && define.amd) {

    // AMD

    define(['jquery'], factory);

  } else if (typeof exports === 'object') {

    // Node, CommonJS-like

    module.exports = factory(require('jquery'));

  } else {

    // Browser globals (root is window)

    root.bootbox = factory(root.jQuery);

  }

}(this, function init($, undefined) {

  'use strict';



  //  Polyfills Object.keys, if necessary.

  //  @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

  if (!Object.keys) {

    Object.keys = (function () {

      var hasOwnProperty = Object.prototype.hasOwnProperty,

        hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),

        dontEnums = [

          'toString',

          'toLocaleString',

          'valueOf',

          'hasOwnProperty',

          'isPrototypeOf',

          'propertyIsEnumerable',

          'constructor'

        ],

        dontEnumsLength = dontEnums.length;



      return function (obj) {

        if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {

          throw new TypeError('Object.keys called on non-object');

        }



        var result = [], prop, i;



        for (prop in obj) {

          if (hasOwnProperty.call(obj, prop)) {

            result.push(prop);

          }

        }



        if (hasDontEnumBug) {

          for (i = 0; i < dontEnumsLength; i++) {

            if (hasOwnProperty.call(obj, dontEnums[i])) {

              result.push(dontEnums[i]);

            }

          }

        }



        return result;

      };

    }());

  }



  var exports = {};



  var VERSION = '5.5.2';

  exports.VERSION = VERSION;



  var locales = {

      ar : {

        OK      : 'موافق',

        CANCEL  : 'الغاء',

        CONFIRM : 'تأكيد'

      },

      bg_BG : {

        OK      : 'Ок',

        CANCEL  : 'Отказ',

        CONFIRM : 'Потвърждавам'

      },

      br : {

        OK      : 'OK',

        CANCEL  : 'Cancelar',

        CONFIRM : 'Sim'

      },

      cs : {

        OK      : 'OK',

        CANCEL  : 'Zrušit',

        CONFIRM : 'Potvrdit'

      },

      da : {

        OK      : 'OK',

        CANCEL  : 'Annuller',

        CONFIRM : 'Accepter'

      },

      de : {

        OK      : 'OK',

        CANCEL  : 'Abbrechen',

        CONFIRM : 'Akzeptieren'

      },

      el : {

        OK      : 'Εντάξει',

        CANCEL  : 'Ακύρωση',

        CONFIRM : 'Επιβεβαίωση'

      },

      en : {

        OK      : 'OK',

        CANCEL  : 'Cancel',

        CONFIRM : 'OK'

      },

      es : {

        OK      : 'OK',

        CANCEL  : 'Cancelar',

        CONFIRM : 'Aceptar'

      },

      eu : {

        OK      : 'OK',

        CANCEL  : 'Ezeztatu',

        CONFIRM : 'Onartu'

      },

      et : {

        OK      : 'OK',

        CANCEL  : 'Katkesta',

        CONFIRM : 'OK'

      },

      fa : {

        OK      : 'قبول',

        CANCEL  : 'لغو',

        CONFIRM : 'تایید'

      },

      fi : {

        OK      : 'OK',

        CANCEL  : 'Peruuta',

        CONFIRM : 'OK'

      },

      fr : {

        OK      : 'OK',

        CANCEL  : 'Annuler',

        CONFIRM : 'Confirmer'

      },

      he : {

        OK      : 'אישור',

        CANCEL  : 'ביטול',

        CONFIRM : 'אישור'

      },

      hu : {

        OK      : 'OK',

        CANCEL  : 'Mégsem',

        CONFIRM : 'Megerősít'

      },

      hr : {

        OK      : 'OK',

        CANCEL  : 'Odustani',

        CONFIRM : 'Potvrdi'

      },

      id : {

        OK      : 'OK',

        CANCEL  : 'Batal',

        CONFIRM : 'OK'

      },

      it : {

        OK      : 'OK',

        CANCEL  : 'Annulla',

        CONFIRM : 'Conferma'

      },

      ja : {

        OK      : 'OK',

        CANCEL  : 'キャンセル',

        CONFIRM : '確認'

      },

      ka : {

        OK: 'OK',

        CANCEL: 'გაუქმება',

        CONFIRM: 'დადასტურება'

      },

      ko : {

        OK: 'OK',

        CANCEL: '취소',

        CONFIRM: '확인'

      },

      lt : {

        OK      : 'Gerai',

        CANCEL  : 'Atšaukti',

        CONFIRM : 'Patvirtinti'

      },

      lv : {

        OK      : 'Labi',

        CANCEL  : 'Atcelt',

        CONFIRM : 'Apstiprināt'

      },

      nl : {

        OK      : 'OK',

        CANCEL  : 'Annuleren',

        CONFIRM : 'Accepteren'

      },

      no : {

        OK      : 'OK',

        CANCEL  : 'Avbryt',

        CONFIRM : 'OK'

      },

      pl : {

        OK      : 'OK',

        CANCEL  : 'Anuluj',

        CONFIRM : 'Potwierdź'

      },

      pt : {

        OK      : 'OK',

        CANCEL  : 'Cancelar',

        CONFIRM : 'Confirmar'

      },

      ru : {

        OK      : 'OK',

        CANCEL  : 'Отмена',

        CONFIRM : 'Применить'

      },

      sk : {

        OK      : 'OK',

        CANCEL  : 'Zrušiť',

        CONFIRM : 'Potvrdiť'

      },

      sl : {

        OK      : 'OK',

        CANCEL  : 'Prekliči',

        CONFIRM : 'Potrdi'

      },

      sq : {

        OK      : 'OK',

        CANCEL  : 'Anulo',

        CONFIRM : 'Prano'

      },

      sv : {

        OK      : 'OK',

        CANCEL  : 'Avbryt',

        CONFIRM : 'OK'

      },

      sw: {

        OK      : 'Sawa',

        CANCEL  : 'Ghairi',

        CONFIRM: 'Thibitisha'

      },

      ta:{

        OK      : 'சரி',

        CANCEL  : 'ரத்து செய்',

        CONFIRM : 'உறுதி செய்'

      },

      th : {

        OK      : 'ตกลง',

        CANCEL  : 'ยกเลิก',

        CONFIRM : 'ยืนยัน'

      },

      tr : {

        OK      : 'Tamam',

        CANCEL  : 'İptal',

        CONFIRM : 'Onayla'

      },

      uk : {

        OK      : 'OK',

        CANCEL  : 'Відміна',

        CONFIRM : 'Прийняти'

      },

      vi : {

        OK      : 'OK',

        CANCEL  : 'Hủy bỏ',

        CONFIRM : 'Xác nhận'

      },

      zh_CN : {

        OK      : 'OK',

        CANCEL  : '取消',

        CONFIRM : '确认'

      },

      zh_TW : {

        OK      : 'OK',

        CANCEL  : '取消',

        CONFIRM : '確認'

      }

  };



  var templates = {

    dialog:

    '<div class="bootbox modal" tabindex="-1" role="dialog" aria-hidden="true">' +

    '<div class="modal-dialog">' +

    '<div class="modal-content">' +

    '<div class="modal-body"><div class="bootbox-body"></div></div>' +

    '</div>' +

    '</div>' +

    '</div>',

    header:

    '<div class="modal-header">' +

    '<h5 class="modal-title"></h5>' +

    '</div>',

    footer:

    '<div class="modal-footer"></div>',

    closeButton:

    '<button type="button" class="bootbox-close-button close" aria-hidden="true">&times;</button>',

    form:

    '<form class="bootbox-form"></form>',

    button:

    '<button type="button" class="btn"></button>',

    option:

    '<option></option>',

    promptMessage:

    '<div class="bootbox-prompt-message"></div>',

    inputs: {

      text:

      '<input class="bootbox-input bootbox-input-text form-control" autocomplete="off" type="text" />',

      textarea:

      '<textarea class="bootbox-input bootbox-input-textarea form-control"></textarea>',

      email:

      '<input class="bootbox-input bootbox-input-email form-control" autocomplete="off" type="email" />',

      select:

      '<select class="bootbox-input bootbox-input-select form-control"></select>',

      checkbox:

      '<div class="form-check checkbox"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-checkbox" type="checkbox" /></label></div>',

      radio:

      '<div class="form-check radio"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-radio" type="radio" name="bootbox-radio" /></label></div>',

      date:

      '<input class="bootbox-input bootbox-input-date form-control" autocomplete="off" type="date" />',

      time:

      '<input class="bootbox-input bootbox-input-time form-control" autocomplete="off" type="time" />',

      number:

      '<input class="bootbox-input bootbox-input-number form-control" autocomplete="off" type="number" />',

      password:

      '<input class="bootbox-input bootbox-input-password form-control" autocomplete="off" type="password" />',

      range:

      '<input class="bootbox-input bootbox-input-range form-control-range" autocomplete="off" type="range" />'

    }

  };





  var defaults = {

    // default language

    locale: 'en',

    // show backdrop or not. Default to static so user has to interact with dialog

    backdrop: 'static',

    // animate the modal in/out

    animate: true,

    // additional class string applied to the top level dialog

    className: null,

    // whether or not to include a close button

    closeButton: true,

    // show the dialog immediately by default

    show: true,

    // dialog container

    container: 'body',

    // default value (used by the prompt helper)

    value: '',

    // default input type (used by the prompt helper)

    inputType: 'text',

    // switch button order from cancel/confirm (default) to confirm/cancel

    swapButtonOrder: false,

    // center modal vertically in page

    centerVertical: false,

    // Append "multiple" property to the select when using the "prompt" helper

    multiple: false,

    // Automatically scroll modal content when height exceeds viewport height

    scrollable: false,

    // whether or not to destroy the modal on hide

    reusable: false

  };





  // PUBLIC FUNCTIONS

  // *************************************************************************************************************



  // Return all currently registered locales, or a specific locale if "name" is defined

  exports.locales = function (name) {

    return name ? locales[name] : locales;

  };





  // Register localized strings for the OK, CONFIRM, and CANCEL buttons

  exports.addLocale = function (name, values) {

    $.each(['OK', 'CANCEL', 'CONFIRM'], function (_, v) {

      if (!values[v]) {

        throw new Error('Please supply a translation for "' + v + '"');

      }

    });



    locales[name] = {

      OK: values.OK,

      CANCEL: values.CANCEL,

      CONFIRM: values.CONFIRM

    };



    return exports;

  };





  // Remove a previously-registered locale

  exports.removeLocale = function (name) {

    if (name !== 'en') {

      delete locales[name];

    }

    else {

      throw new Error('"en" is used as the default and fallback locale and cannot be removed.');

    }



    return exports;

  };





  // Set the default locale

  exports.setLocale = function (name) {

    return exports.setDefaults('locale', name);

  };





  // Override default value(s) of Bootbox.

  exports.setDefaults = function () {

    var values = {};



    if (arguments.length === 2) {

      // allow passing of single key/value...

      values[arguments[0]] = arguments[1];

    } else {

      // ... and as an object too

      values = arguments[0];

    }



    $.extend(defaults, values);



    return exports;

  };





  // Hides all currently active Bootbox modals

  exports.hideAll = function () {

    $('.bootbox').modal('hide');



    return exports;

  };





  // Allows the base init() function to be overridden

  exports.init = function (_$) {

    return init(_$ || $);

  };





  // CORE HELPER FUNCTIONS

  // *************************************************************************************************************



  // Core dialog function

  exports.dialog = function (options) {

    if ($.fn.modal === undefined) {

      throw new Error(

        '"$.fn.modal" is not defined; please double check you have included ' +

        'the Bootstrap JavaScript library. See https://getbootstrap.com/docs/4.4/getting-started/javascript/ ' +

        'for more details.'

      );

    }



    options = sanitize(options);



    if ($.fn.modal.Constructor.VERSION) {

      options.fullBootstrapVersion = $.fn.modal.Constructor.VERSION;

      var i = options.fullBootstrapVersion.indexOf('.');

      options.bootstrap = options.fullBootstrapVersion.substring(0, i);

    }

    else {

      // Assuming version 2.3.2, as that was the last "supported" 2.x version

      options.bootstrap = '2';

      options.fullBootstrapVersion = '2.3.2';

      console.warn('Bootbox will *mostly* work with Bootstrap 2, but we do not officially support it. Please upgrade, if possible.');

    }



    var dialog = $(templates.dialog);

    var innerDialog = dialog.find('.modal-dialog');

    var body = dialog.find('.modal-body');

    var header = $(templates.header);

    var footer = $(templates.footer);

    var buttons = options.buttons;



    var callbacks = {

      onEscape: options.onEscape

    };



    body.find('.bootbox-body').html(options.message);



    // Only attempt to create buttons if at least one has 

    // been defined in the options object

    if (getKeyLength(options.buttons) > 0) {

      each(buttons, function (key, b) {

        var button = $(templates.button);

        button.data('bb-handler', key);

        button.addClass(b.className);



        switch (key) {

          case 'ok':

          case 'confirm':

            button.addClass('bootbox-accept');

            break;



          case 'cancel':

            button.addClass('bootbox-cancel');

            break;

        }



        button.html(b.label);

        footer.append(button);



        callbacks[key] = b.callback;

      });



      body.after(footer);

    }



    if (options.animate === true) {

      dialog.addClass('fade');

    }



    if (options.className) {

      dialog.addClass(options.className);

    }



    if (options.size) {

      // Requires Bootstrap 3.1.0 or higher

      if (options.fullBootstrapVersion.substring(0, 3) < '3.1') {

        console.warn('"size" requires Bootstrap 3.1.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');

      }



      switch (options.size) {

        case 'small':

        case 'sm':

          innerDialog.addClass('modal-sm');

          break;



        case 'large':

        case 'lg':

          innerDialog.addClass('modal-lg');

          break;



        case 'extra-large':

        case 'xl':

          innerDialog.addClass('modal-xl');



          // Requires Bootstrap 4.2.0 or higher

          if (options.fullBootstrapVersion.substring(0, 3) < '4.2') {

            console.warn('Using size "xl"/"extra-large" requires Bootstrap 4.2.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');

          }

          break;

      }

    }



    if (options.scrollable) {

      innerDialog.addClass('modal-dialog-scrollable');



      // Requires Bootstrap 4.3.0 or higher

      if (options.fullBootstrapVersion.substring(0, 3) < '4.3') {

        console.warn('Using "scrollable" requires Bootstrap 4.3.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');

      }

    }



    if (options.title) {

      body.before(header);

      dialog.find('.modal-title').html(options.title);

    }



    if (options.closeButton) {

      var closeButton = $(templates.closeButton);



      if (options.title) {

        if (options.bootstrap > 3) {

          dialog.find('.modal-header').append(closeButton);

        }

        else {

          dialog.find('.modal-header').prepend(closeButton);

        }

      } else {

        closeButton.prependTo(body);

      }

    }



    if (options.centerVertical) {

      innerDialog.addClass('modal-dialog-centered');



      // Requires Bootstrap 4.0.0-beta.3 or higher

      if (options.fullBootstrapVersion < '4.0.0') {

        console.warn('"centerVertical" requires Bootstrap 4.0.0-beta.3 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');

      }

    }



    // Bootstrap event listeners; these handle extra

    // setup & teardown required after the underlying

    // modal has performed certain actions.



    if(!options.reusable) {

      // make sure we unbind any listeners once the dialog has definitively been dismissed

      dialog.one('hide.bs.modal', { dialog: dialog }, unbindModal);

    }



    if (options.onHide) {

      if ($.isFunction(options.onHide)) {

        dialog.on('hide.bs.modal', options.onHide);

      }

      else {

        throw new Error('Argument supplied to "onHide" must be a function');

      }

    }



    if(!options.reusable) {

      dialog.one('hidden.bs.modal', { dialog: dialog }, destroyModal);

    }



    if (options.onHidden) {

      if ($.isFunction(options.onHidden)) {

        dialog.on('hidden.bs.modal', options.onHidden);

      }

      else {

        throw new Error('Argument supplied to "onHidden" must be a function');

      }

    }



    if (options.onShow) {

      if ($.isFunction(options.onShow)) {

        dialog.on('show.bs.modal', options.onShow);

      }

      else {

        throw new Error('Argument supplied to "onShow" must be a function');

      }

    }



    dialog.one('shown.bs.modal', { dialog: dialog }, focusPrimaryButton);



    if (options.onShown) {

      if ($.isFunction(options.onShown)) {

        dialog.on('shown.bs.modal', options.onShown);

      }

      else {

        throw new Error('Argument supplied to "onShown" must be a function');

      }

    }



    // Bootbox event listeners; used to decouple some

    // behaviours from their respective triggers



    if (options.backdrop === true) {

      // A boolean true/false according to the Bootstrap docs

      // should show a dialog the user can dismiss by clicking on

      // the background.

      // We always only ever pass static/false to the actual

      // $.modal function because with "true" we can't trap

      // this event (the .modal-backdrop swallows it)

      // However, we still want to sort-of respect true

      // and invoke the escape mechanism instead

      dialog.on('click.dismiss.bs.modal', function (e) {

        // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop

        // moved *inside* the outer dialog rather than *alongside* it

        if (dialog.children('.modal-backdrop').length) {

          e.currentTarget = dialog.children('.modal-backdrop').get(0);

        }



        if (e.target !== e.currentTarget) {

          return;

        }



        dialog.trigger('escape.close.bb');

      });

    }



    dialog.on('escape.close.bb', function (e) {

      // the if statement looks redundant but it isn't; without it

      // if we *didn't* have an onEscape handler then processCallback

      // would automatically dismiss the dialog

      if (callbacks.onEscape) {

        processCallback(e, dialog, callbacks.onEscape);

      }

    });





    dialog.on('click', '.modal-footer button:not(.disabled)', function (e) {

      var callbackKey = $(this).data('bb-handler');



      if (callbackKey !== undefined) {

        // Only process callbacks for buttons we recognize:

        processCallback(e, dialog, callbacks[callbackKey]);

      }

    });



    dialog.on('click', '.bootbox-close-button', function (e) {

      // onEscape might be falsy but that's fine; the fact is

      // if the user has managed to click the close button we

      // have to close the dialog, callback or not

      processCallback(e, dialog, callbacks.onEscape);

    });



    dialog.on('keyup', function (e) {

      if (e.which === 27) {

        dialog.trigger('escape.close.bb');

      }

    });



    // the remainder of this method simply deals with adding our

    // dialog element to the DOM, augmenting it with Bootstrap's modal

    // functionality and then giving the resulting object back

    // to our caller



    $(options.container).append(dialog);



    dialog.modal({

      backdrop: options.backdrop,

      keyboard: false,

      show: false

    });



    if (options.show) {

      dialog.modal('show');

    }



    return dialog;

  };





  // Helper function to simulate the native alert() behavior. **NOTE**: This is non-blocking, so any

  // code that must happen after the alert is dismissed should be placed within the callback function 

  // for this alert.

  exports.alert = function () {

    var options;



    options = mergeDialogOptions('alert', ['ok'], ['message', 'callback'], arguments);



    // @TODO: can this move inside exports.dialog when we're iterating over each

    // button and checking its button.callback value instead?

    if (options.callback && !$.isFunction(options.callback)) {

      throw new Error('alert requires the "callback" property to be a function when provided');

    }



    // override the ok and escape callback to make sure they just invoke

    // the single user-supplied one (if provided)

    options.buttons.ok.callback = options.onEscape = function () {

      if ($.isFunction(options.callback)) {

        return options.callback.call(this);

      }



      return true;

    };



    return exports.dialog(options);

  };





  // Helper function to simulate the native confirm() behavior. **NOTE**: This is non-blocking, so any

  // code that must happen after the confirm is dismissed should be placed within the callback function 

  // for this confirm.

  exports.confirm = function () {

    var options;



    options = mergeDialogOptions('confirm', ['cancel', 'confirm'], ['message', 'callback'], arguments);



    // confirm specific validation; they don't make sense without a callback so make

    // sure it's present

    if (!$.isFunction(options.callback)) {

      throw new Error('confirm requires a callback');

    }



    // overrides; undo anything the user tried to set they shouldn't have

    options.buttons.cancel.callback = options.onEscape = function () {

      return options.callback.call(this, false);

    };



    options.buttons.confirm.callback = function () {

      return options.callback.call(this, true);

    };



    return exports.dialog(options);

  };





  // Helper function to simulate the native prompt() behavior. **NOTE**: This is non-blocking, so any

  // code that must happen after the prompt is dismissed should be placed within the callback function 

  // for this prompt.

  exports.prompt = function () {

    var options;

    var promptDialog;

    var form;

    var input;

    var shouldShow;

    var inputOptions;



    // we have to create our form first otherwise

    // its value is undefined when gearing up our options

    // @TODO this could be solved by allowing message to

    // be a function instead...

    form = $(templates.form);



    // prompt defaults are more complex than others in that

    // users can override more defaults

    options = mergeDialogOptions('prompt', ['cancel', 'confirm'], ['title', 'callback'], arguments);



    if (!options.value) {

      options.value = defaults.value;

    }



    if (!options.inputType) {

      options.inputType = defaults.inputType;

    }



    // capture the user's show value; we always set this to false before

    // spawning the dialog to give us a chance to attach some handlers to

    // it, but we need to make sure we respect a preference not to show it

    shouldShow = (options.show === undefined) ? defaults.show : options.show;



    // This is required prior to calling the dialog builder below - we need to 

    // add an event handler just before the prompt is shown

    options.show = false;



    // Handles the 'cancel' action

    options.buttons.cancel.callback = options.onEscape = function () {

      return options.callback.call(this, null);

    };



    // Prompt submitted - extract the prompt value. This requires a bit of work, 

    // given the different input types available.

    options.buttons.confirm.callback = function () {

      var value;



      if (options.inputType === 'checkbox') {

        value = input.find('input:checked').map(function () {

          return $(this).val();

        }).get();

      } else if (options.inputType === 'radio') {

        value = input.find('input:checked').val();

      }

      else {

        if (input[0].checkValidity && !input[0].checkValidity()) {

          // prevents button callback from being called

          return false;

        } else {

          if (options.inputType === 'select' && options.multiple === true) {

            value = input.find('option:selected').map(function () {

              return $(this).val();

            }).get();

          }

          else {

            value = input.val();

          }

        }

      }



      return options.callback.call(this, value);

    };



    // prompt-specific validation

    if (!options.title) {

      throw new Error('prompt requires a title');

    }



    if (!$.isFunction(options.callback)) {

      throw new Error('prompt requires a callback');

    }



    if (!templates.inputs[options.inputType]) {

      throw new Error('Invalid prompt type');

    }



    // create the input based on the supplied type

    input = $(templates.inputs[options.inputType]);



    switch (options.inputType) {

      case 'text':

      case 'textarea':

      case 'email':

      case 'password':

        input.val(options.value);



        if (options.placeholder) {

          input.attr('placeholder', options.placeholder);

        }



        if (options.pattern) {

          input.attr('pattern', options.pattern);

        }



        if (options.maxlength) {

          input.attr('maxlength', options.maxlength);

        }



        if (options.required) {

          input.prop({ 'required': true });

        }



        if (options.rows && !isNaN(parseInt(options.rows))) {

          if (options.inputType === 'textarea') {

            input.attr({ 'rows': options.rows });

          }

        }



        break;





      case 'date':

      case 'time':

      case 'number':

      case 'range':

        input.val(options.value);



        if (options.placeholder) {

          input.attr('placeholder', options.placeholder);

        }



        if (options.pattern) {

          input.attr('pattern', options.pattern);

        }



        if (options.required) {

          input.prop({ 'required': true });

        }



        // These input types have extra attributes which affect their input validation.

        // Warning: For most browsers, date inputs are buggy in their implementation of 'step', so 

        // this attribute will have no effect. Therefore, we don't set the attribute for date inputs.

        // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#Setting_maximum_and_minimum_dates

        if (options.inputType !== 'date') {

          if (options.step) {

            if (options.step === 'any' || (!isNaN(options.step) && parseFloat(options.step) > 0)) {

              input.attr('step', options.step);

            }

            else {

              throw new Error('"step" must be a valid positive number or the value "any". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-step for more information.');

            }

          }

        }



        if (minAndMaxAreValid(options.inputType, options.min, options.max)) {

          if (options.min !== undefined) {

            input.attr('min', options.min);

          }

          if (options.max !== undefined) {

            input.attr('max', options.max);

          }

        }



        break;





      case 'select':

        var groups = {};

        inputOptions = options.inputOptions || [];



        if (!$.isArray(inputOptions)) {

          throw new Error('Please pass an array of input options');

        }



        if (!inputOptions.length) {

          throw new Error('prompt with "inputType" set to "select" requires at least one option');

        }



        // placeholder is not actually a valid attribute for select,

        // but we'll allow it, assuming it might be used for a plugin

        if (options.placeholder) {

          input.attr('placeholder', options.placeholder);

        }



        if (options.required) {

          input.prop({ 'required': true });

        }



        if (options.multiple) {

          input.prop({ 'multiple': true });

        }



        each(inputOptions, function (_, option) {

          // assume the element to attach to is the input...

          var elem = input;



          if (option.value === undefined || option.text === undefined) {

            throw new Error('each option needs a "value" property and a "text" property');

          }



          // ... but override that element if this option sits in a group



          if (option.group) {

            // initialise group if necessary

            if (!groups[option.group]) {

              groups[option.group] = $('<optgroup />').attr('label', option.group);

            }



            elem = groups[option.group];

          }



          var o = $(templates.option);

          o.attr('value', option.value).text(option.text);

          elem.append(o);

        });



        each(groups, function (_, group) {

          input.append(group);

        });



        // safe to set a select's value as per a normal input

        input.val(options.value);



        break;





      case 'checkbox':

        var checkboxValues = $.isArray(options.value) ? options.value : [options.value];

        inputOptions = options.inputOptions || [];



        if (!inputOptions.length) {

          throw new Error('prompt with "inputType" set to "checkbox" requires at least one option');

        }



        // checkboxes have to nest within a containing element, so

        // they break the rules a bit and we end up re-assigning

        // our 'input' element to this container instead

        input = $('<div class="bootbox-checkbox-list"></div>');



        each(inputOptions, function (_, option) {

          if (option.value === undefined || option.text === undefined) {

            throw new Error('each option needs a "value" property and a "text" property');

          }



          var checkbox = $(templates.inputs[options.inputType]);



          checkbox.find('input').attr('value', option.value);

          checkbox.find('label').append('\n' + option.text);



          // we've ensured values is an array so we can always iterate over it

          each(checkboxValues, function (_, value) {

            if (value === option.value) {

              checkbox.find('input').prop('checked', true);

            }

          });



          input.append(checkbox);

        });

        break;





      case 'radio':

        // Make sure that value is not an array (only a single radio can ever be checked)

        if (options.value !== undefined && $.isArray(options.value)) {

          throw new Error('prompt with "inputType" set to "radio" requires a single, non-array value for "value"');

        }



        inputOptions = options.inputOptions || [];



        if (!inputOptions.length) {

          throw new Error('prompt with "inputType" set to "radio" requires at least one option');

        }



        // Radiobuttons have to nest within a containing element, so

        // they break the rules a bit and we end up re-assigning

        // our 'input' element to this container instead

        input = $('<div class="bootbox-radiobutton-list"></div>');



        // Radiobuttons should always have an initial checked input checked in a "group".

        // If value is undefined or doesn't match an input option, select the first radiobutton

        var checkFirstRadio = true;



        each(inputOptions, function (_, option) {

          if (option.value === undefined || option.text === undefined) {

            throw new Error('each option needs a "value" property and a "text" property');

          }



          var radio = $(templates.inputs[options.inputType]);



          radio.find('input').attr('value', option.value);

          radio.find('label').append('\n' + option.text);



          if (options.value !== undefined) {

            if (option.value === options.value) {

              radio.find('input').prop('checked', true);

              checkFirstRadio = false;

            }

          }



          input.append(radio);

        });



        if (checkFirstRadio) {

          input.find('input[type="radio"]').first().prop('checked', true);

        }

        break;

    }



    // now place it in our form

    form.append(input);



    form.on('submit', function (e) {

      e.preventDefault();

      // Fix for SammyJS (or similar JS routing library) hijacking the form post.

      e.stopPropagation();



      // @TODO can we actually click *the* button object instead?

      // e.g. buttons.confirm.click() or similar

      promptDialog.find('.bootbox-accept').trigger('click');

    });



    if ($.trim(options.message) !== '') {

      // Add the form to whatever content the user may have added.

      var message = $(templates.promptMessage).html(options.message);

      form.prepend(message);

      options.message = form;

    }

    else {

      options.message = form;

    }



    // Generate the dialog

    promptDialog = exports.dialog(options);



    // clear the existing handler focusing the submit button...

    promptDialog.off('shown.bs.modal', focusPrimaryButton);



    // ...and replace it with one focusing our input, if possible

    promptDialog.on('shown.bs.modal', function () {

      // need the closure here since input isn't

      // an object otherwise

      input.focus();

    });



    if (shouldShow === true) {

      promptDialog.modal('show');

    }



    return promptDialog;

  };





  // INTERNAL FUNCTIONS

  // *************************************************************************************************************



  // Map a flexible set of arguments into a single returned object

  // If args.length is already one just return it, otherwise

  // use the properties argument to map the unnamed args to

  // object properties.

  // So in the latter case:

  //  mapArguments(["foo", $.noop], ["message", "callback"])

  //  -> { message: "foo", callback: $.noop }

  function mapArguments(args, properties) {

    var argn = args.length;

    var options = {};



    if (argn < 1 || argn > 2) {

      throw new Error('Invalid argument length');

    }



    if (argn === 2 || typeof args[0] === 'string') {

      options[properties[0]] = args[0];

      options[properties[1]] = args[1];

    } else {

      options = args[0];

    }



    return options;

  }





  //  Merge a set of default dialog options with user supplied arguments

  function mergeArguments(defaults, args, properties) {

    return $.extend(

      // deep merge

      true,

      // ensure the target is an empty, unreferenced object

      {},

      // the base options object for this type of dialog (often just buttons)

      defaults,

      // args could be an object or array; if it's an array properties will

      // map it to a proper options object

      mapArguments(

        args,

        properties

      )

    );

  }





  //  This entry-level method makes heavy use of composition to take a simple

  //  range of inputs and return valid options suitable for passing to bootbox.dialog

  function mergeDialogOptions(className, labels, properties, args) {

    var locale;

    if (args && args[0]) {

      locale = args[0].locale || defaults.locale;

      var swapButtons = args[0].swapButtonOrder || defaults.swapButtonOrder;



      if (swapButtons) {

        labels = labels.reverse();

      }

    }



    //  build up a base set of dialog properties

    var baseOptions = {

      className: 'bootbox-' + className,

      buttons: createLabels(labels, locale)

    };



    // Ensure the buttons properties generated, *after* merging

    // with user args are still valid against the supplied labels

    return validateButtons(

      // merge the generated base properties with user supplied arguments

      mergeArguments(

        baseOptions,

        args,

        // if args.length > 1, properties specify how each arg maps to an object key

        properties

      ),

      labels

    );

  }





  //  Checks each button object to see if key is valid. 

  //  This function will only be called by the alert, confirm, and prompt helpers. 

  function validateButtons(options, buttons) {

    var allowedButtons = {};

    each(buttons, function (key, value) {

      allowedButtons[value] = true;

    });



    each(options.buttons, function (key) {

      if (allowedButtons[key] === undefined) {

        throw new Error('button key "' + key + '" is not allowed (options are ' + buttons.join(' ') + ')');

      }

    });



    return options;

  }







  //  From a given list of arguments, return a suitable object of button labels.

  //  All this does is normalise the given labels and translate them where possible.

  //  e.g. "ok", "confirm" -> { ok: "OK", cancel: "Annuleren" }

  function createLabels(labels, locale) {

    var buttons = {};



    for (var i = 0, j = labels.length; i < j; i++) {

      var argument = labels[i];

      var key = argument.toLowerCase();

      var value = argument.toUpperCase();



      buttons[key] = {

        label: getText(value, locale)

      };

    }



    return buttons;

  }







  //  Get localized text from a locale. Defaults to 'en' locale if no locale 

  //  provided or a non-registered locale is requested

  function getText(key, locale) {

    var labels = locales[locale];



    return labels ? labels[key] : locales.en[key];

  }







  //  Filter and tidy up any user supplied parameters to this dialog.

  //  Also looks for any shorthands used and ensures that the options

  //  which are returned are all normalized properly

  function sanitize(options) {

    var buttons;

    var total;



    if (typeof options !== 'object') {

      throw new Error('Please supply an object of options');

    }



    if (!options.message) {

      throw new Error('"message" option must not be null or an empty string.');

    }



    // make sure any supplied options take precedence over defaults

    options = $.extend({}, defaults, options);



    //make sure backdrop is either true, false, or 'static'

    if (!options.backdrop) {

      options.backdrop = (options.backdrop === false || options.backdrop === 0) ? false : 'static';

    } else {

      options.backdrop = typeof options.backdrop === 'string' && options.backdrop.toLowerCase() === 'static' ? 'static' : true;

    } 



    // no buttons is still a valid dialog but it's cleaner to always have

    // a buttons object to iterate over, even if it's empty

    if (!options.buttons) {

      options.buttons = {};

    }

    

    buttons = options.buttons;



    total = getKeyLength(buttons);



    each(buttons, function (key, button, index) {

      if ($.isFunction(button)) {

        // short form, assume value is our callback. Since button

        // isn't an object it isn't a reference either so re-assign it

        button = buttons[key] = {

          callback: button

        };

      }



      // before any further checks make sure by now button is the correct type

      if ($.type(button) !== 'object') {

        throw new Error('button with key "' + key + '" must be an object');

      }



      if (!button.label) {

        // the lack of an explicit label means we'll assume the key is good enough

        button.label = key;

      }



      if (!button.className) {

        var isPrimary = false;

        if (options.swapButtonOrder) {

          isPrimary = index === 0;

        }

        else {

          isPrimary = index === total - 1;

        }



        if (total <= 2 && isPrimary) {

          // always add a primary to the main option in a one or two-button dialog

          button.className = 'btn-primary';

        } else {

          // adding both classes allows us to target both BS3 and BS4 without needing to check the version

          button.className = 'btn-secondary btn-default';

        }

      }

    });



    return options;

  }





  //  Returns a count of the properties defined on the object

  function getKeyLength(obj) {

    return Object.keys(obj).length;

  }





  //  Tiny wrapper function around jQuery.each; just adds index as the third parameter

  function each(collection, iterator) {

    var index = 0;

    $.each(collection, function (key, value) {

      iterator(key, value, index++);

    });

  }





  function focusPrimaryButton(e) {

    e.data.dialog.find('.bootbox-accept').first().trigger('focus');

  }





  function destroyModal(e) {

    // ensure we don't accidentally intercept hidden events triggered

    // by children of the current dialog. We shouldn't need to handle this anymore, 

    // now that Bootstrap namespaces its events, but still worth doing.

    if (e.target === e.data.dialog[0]) {

      e.data.dialog.remove();

    }

  }





  function unbindModal(e) {

    if (e.target === e.data.dialog[0]) {

      e.data.dialog.off('escape.close.bb');

      e.data.dialog.off('click');

    }

  }





  //  Handle the invoked dialog callback

  function processCallback(e, dialog, callback) {

    e.stopPropagation();

    e.preventDefault();



    // by default we assume a callback will get rid of the dialog,

    // although it is given the opportunity to override this



    // so, if the callback can be invoked and it *explicitly returns false*

    // then we'll set a flag to keep the dialog active...

    var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;



    // ... otherwise we'll bin it

    if (!preserveDialog) {

      dialog.modal('hide');

    }

  }



  // Validate `min` and `max` values based on the current `inputType` value

  function minAndMaxAreValid(type, min, max) {

    var result = false;

    var minValid = true;

    var maxValid = true;



    if (type === 'date') {

      if (min !== undefined && !(minValid = dateIsValid(min))) {

        console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your min value may not be enforced by this browser.');

      }

      else if (max !== undefined && !(maxValid = dateIsValid(max))) {

        console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your max value may not be enforced by this browser.');

      }

    }

    else if (type === 'time') {

      if (min !== undefined && !(minValid = timeIsValid(min))) {

        throw new Error('"min" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');

      }

      else if (max !== undefined && !(maxValid = timeIsValid(max))) {

        throw new Error('"max" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');

      }

    }

    else {

      if (min !== undefined && isNaN(min)) {

        minValid = false;

        throw new Error('"min" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-min for more information.');

      }



      if (max !== undefined && isNaN(max)) {

        maxValid = false;

        throw new Error('"max" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');

      }

    }



    if (minValid && maxValid) {

      if (max <= min) {

        throw new Error('"max" must be greater than "min". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');

      }

      else {

        result = true;

      }

    }



    return result;

  }



  function timeIsValid(value) {

    return /([01][0-9]|2[0-3]):[0-5][0-9]?:[0-5][0-9]/.test(value);

  }



  function dateIsValid(value) {

    return /(\d{4})-(\d{2})-(\d{2})/.test(value);

  }



  //  The Bootbox object

  return exports;

}));