first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,585 @@
/**
* @file
* Bootstrap Modals.
*
* @param {jQuery} $
* @param {Drupal} Drupal
* @param {Drupal.bootstrap} Bootstrap
* @param {Attributes} Attributes
* @param {drupalSettings} drupalSettings
*/
(function ($, Drupal, Bootstrap, Attributes, drupalSettings) {
'use strict';
/**
* Only process this once.
*/
Bootstrap.once('modal.jquery.ui.bridge', function (settings) {
// RTL support.
var rtl = document.documentElement.getAttribute('dir').toLowerCase() === 'rtl';
// Override drupal.dialog button classes. This must be done on DOM ready
// since core/drupal.dialog technically depends on this file and has not
// yet set their default settings.
$(function () {
drupalSettings.dialog.buttonClass = 'btn';
drupalSettings.dialog.buttonPrimaryClass = 'btn-primary';
});
// Create the "dialog" plugin bridge.
Bootstrap.Dialog.Bridge = function (options) {
var args = Array.prototype.slice.call(arguments);
var $element = $(this);
var type = options && options.dialogType || $element[0].dialogType || 'modal';
$element[0].dialogType = type;
var handler = Bootstrap.Dialog.Handler.get(type);
// When only options are passed, jQuery UI dialog treats this like a
// initialization method. Destroy any existing Bootstrap modal and
// recreate it using the contents of the dialog HTML.
if (args.length === 1 && typeof options === 'object') {
this.each(function () {
handler.ensureModalStructure(this, options);
});
// Proxy to the Bootstrap Modal plugin, indicating that this is a
// jQuery UI dialog bridge.
return handler.invoke(this, {
dialogOptions: options,
jQueryUiBridge: true
});
}
// Otherwise, proxy all arguments to the Bootstrap Modal plugin.
var ret;
try {
ret = handler.invoke.apply(handler, [this].concat(args));
}
catch (e) {
Bootstrap.warn(e);
}
// If just one element and there was a result returned for the option passed,
// then return the result. Otherwise, just return the jQuery object.
return this.length === 1 && ret !== void 0 ? ret : this;
};
// Assign the jQuery "dialog" plugin to use to the bridge.
Bootstrap.createPlugin('dialog', Bootstrap.Dialog.Bridge);
// Create the "modal" plugin bridge.
Bootstrap.Modal.Bridge = function () {
var Modal = this;
return {
DEFAULTS: {
// By default, this option is disabled. It's only flagged when a modal
// was created using $.fn.dialog above.
jQueryUiBridge: false
},
prototype: {
/**
* Handler for $.fn.dialog('close').
*/
close: function () {
var _this = this;
this.hide.apply(this, arguments);
// For some reason (likely due to the transition event not being
// registered properly), the backdrop doesn't always get removed
// after the above "hide" method is invoked . Instead, ensure the
// backdrop is removed after the transition duration by manually
// invoking the internal "hideModal" method shortly thereafter.
setTimeout(function () {
if (!_this.isShown && _this.$backdrop) {
_this.hideModal();
}
}, (Modal.TRANSITION_DURATION !== void 0 ? Modal.TRANSITION_DURATION : 300) + 10);
},
/**
* Creates any necessary buttons from dialog options.
*/
createButtons: function () {
var handler = Bootstrap.Dialog.Handler.get(this.$element);
this.$footer.find(handler.selectors.buttons).remove();
// jQuery UI supports both objects and arrays. Unfortunately
// developers have misunderstood and abused this by simply placing
// the objects that should be in an array inside an object with
// arbitrary keys (likely to target specific buttons as a hack).
var buttons = this.options.dialogOptions && this.options.dialogOptions.buttons || [];
if (!Array.isArray(buttons)) {
var array = [];
for (var k in buttons) {
// Support the proper object values: label => click callback.
if (typeof buttons[k] === 'function') {
array.push({
label: k,
click: buttons[k],
});
}
// Support nested objects, but log a warning.
else if (buttons[k].text || buttons[k].label) {
Bootstrap.warn('Malformed jQuery UI dialog button: @key. The button object should be inside an array.', {
'@key': k
});
array.push(buttons[k]);
}
else {
Bootstrap.unsupported('button', k, buttons[k]);
}
}
buttons = array;
}
if (buttons.length) {
var $buttons = $('<div class="modal-buttons"/>').appendTo(this.$footer);
for (var i = 0, l = buttons.length; i < l; i++) {
var button = buttons[i];
var $button = $(Drupal.theme('bootstrapModalDialogButton', button));
// Invoke the "create" method for jQuery UI buttons.
if (typeof button.create === 'function') {
button.create.call($button[0]);
}
// Bind the "click" method for jQuery UI buttons to the modal.
if (typeof button.click === 'function') {
$button.on('click', button.click.bind(this.$element));
}
$buttons.append($button);
}
}
// Toggle footer visibility based on whether it has child elements.
this.$footer[this.$footer.children()[0] ? 'show' : 'hide']();
},
/**
* Initializes the Bootstrap Modal.
*/
init: function () {
var handler = Bootstrap.Dialog.Handler.get(this.$element);
if (!this.$dialog) {
this.$dialog = this.$element.find(handler.selectors.dialog);
}
this.$dialog.addClass('js-drupal-dialog');
if (!this.$header) {
this.$header = this.$dialog.find(handler.selectors.header);
}
if (!this.$title) {
this.$title = this.$dialog.find(handler.selectors.title);
}
if (!this.$close) {
this.$close = this.$header.find(handler.selectors.close);
}
if (!this.$footer) {
this.$footer = this.$dialog.find(handler.selectors.footer);
}
if (!this.$content) {
this.$content = this.$dialog.find(handler.selectors.content);
}
if (!this.$dialogBody) {
this.$dialogBody = this.$dialog.find(handler.selectors.body);
}
// Relay necessary events.
if (this.options.jQueryUiBridge) {
this.$element.on('hide.bs.modal', Bootstrap.relayEvent(this.$element, 'dialogbeforeclose', false));
this.$element.on('hidden.bs.modal', Bootstrap.relayEvent(this.$element, 'dialogclose', false));
this.$element.on('show.bs.modal', Bootstrap.relayEvent(this.$element, 'dialogcreate', false));
this.$element.on('shown.bs.modal', Bootstrap.relayEvent(this.$element, 'dialogopen', false));
}
// Create a footer if one doesn't exist.
// This is necessary in case dialog.ajax.js decides to add buttons.
if (!this.$footer[0]) {
this.$footer = handler.theme('footer', {}, true).insertAfter(this.$dialogBody);
}
// Map the initial options.
$.extend(true, this.options, this.mapDialogOptions(this.options));
// Update buttons.
this.createButtons();
// Now call the parent init method.
this.super();
// Handle autoResize option (this is a drupal.dialog option).
if (this.options.dialogOptions && this.options.dialogOptions.autoResize && this.options.dialogOptions.position) {
this.position(this.options.dialogOptions.position);
}
// If show is enabled and currently not shown, show it.
if (this.options.jQueryUiBridge && this.options.show && !this.isShown) {
this.show();
}
},
/**
* Handler for $.fn.dialog('instance').
*/
instance: function () {
Bootstrap.unsupported('method', 'instance', arguments);
},
/**
* Handler for $.fn.dialog('isOpen').
*/
isOpen: function () {
return !!this.isShown;
},
/**
* Maps dialog options to the modal.
*
* @param {Object} options
* The options to map.
*/
mapDialogOptions: function (options) {
// Retrieve the dialog handler for this type.
var handler = Bootstrap.Dialog.Handler.get(this.$element);
var mappedOptions = {};
var dialogOptions = options.dialogOptions || {};
// Remove any existing dialog options.
delete options.dialogOptions;
// Separate Bootstrap modal options from jQuery UI dialog options.
for (var k in options) {
if (Modal.DEFAULTS.hasOwnProperty(k)) {
mappedOptions[k] = options[k];
}
else {
dialogOptions[k] = options[k];
}
}
// Handle CSS properties.
var cssUnitRegExp = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)?$/;
var parseCssUnit = function (value, defaultUnit) {
var parts = ('' + value).match(cssUnitRegExp);
return parts && parts[1] !== void 0 ? parts[1] + (parts[2] || defaultUnit || 'px') : null;
};
var styles = {};
var cssProperties = ['height', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'width'];
for (var i = 0, l = cssProperties.length; i < l; i++) {
var prop = cssProperties[i];
if (dialogOptions[prop] !== void 0) {
var value = parseCssUnit(dialogOptions[prop]);
if (value) {
styles[prop] = value;
// If there's a defined height of some kind, enforce the modal
// to use flex (on modern browsers). This will ensure that
// the core autoResize calculations don't cause the content
// to overflow.
if (dialogOptions.autoResize && (prop === 'height' || prop === 'maxHeight')) {
styles.display = 'flex';
styles.flexDirection = 'column';
this.$dialogBody.css('overflow', 'scroll');
}
}
}
}
// Apply mapped CSS styles to the modal-content container.
this.$content.css(styles);
// Handle deprecated "dialogClass" option by merging it with "classes".
var classesMap = {
'ui-dialog': 'modal-content',
'ui-dialog-titlebar': 'modal-header',
'ui-dialog-title': 'modal-title',
'ui-dialog-titlebar-close': 'close',
'ui-dialog-content': 'modal-body',
'ui-dialog-buttonpane': 'modal-footer'
};
if (dialogOptions.dialogClass) {
if (dialogOptions.classes === void 0) {
dialogOptions.classes = {};
}
if (dialogOptions.classes['ui-dialog'] === void 0) {
dialogOptions.classes['ui-dialog'] = '';
}
var dialogClass = dialogOptions.classes['ui-dialog'].split(' ');
dialogClass.push(dialogOptions.dialogClass);
dialogOptions.classes['ui-dialog'] = dialogClass.join(' ');
delete dialogOptions.dialogClass;
}
// Add jQuery UI classes to elements in case developers target them
// in callbacks.
for (k in classesMap) {
this.$element.find('.' + classesMap[k]).addClass(k);
}
// Bind events.
var events = [
'beforeClose', 'close',
'create',
'drag', 'dragStart', 'dragStop',
'focus',
'open',
'resize', 'resizeStart', 'resizeStop'
];
for (i = 0, l = events.length; i < l; i++) {
var event = events[i].toLowerCase();
if (dialogOptions[event] === void 0 || typeof dialogOptions[event] !== 'function') continue;
this.$element.on('dialog' + event, dialogOptions[event]);
}
// Support title attribute on the modal.
var title;
if ((dialogOptions.title === null || dialogOptions.title === void 0) && (title = this.$element.attr('title'))) {
dialogOptions.title = title;
}
// Handle the reset of the options.
for (var name in dialogOptions) {
if (!dialogOptions.hasOwnProperty(name) || dialogOptions[name] === void 0) continue;
switch (name) {
case 'appendTo':
Bootstrap.unsupported('option', name, dialogOptions.appendTo);
break;
case 'autoOpen':
mappedOptions.show = dialogOptions.show = !!dialogOptions.autoOpen;
break;
case 'classes':
if (dialogOptions.classes) {
for (var key in dialogOptions.classes) {
if (dialogOptions.classes.hasOwnProperty(key) && classesMap[key] !== void 0) {
// Run through Attributes to sanitize classes.
var attributes = Attributes.create().addClass(dialogOptions.classes[key]).toPlainObject();
var selector = '.' + classesMap[key];
this.$element.find(selector).addClass(attributes['class']);
}
}
}
break;
case 'closeOnEscape':
mappedOptions.keyboard = !!dialogOptions.closeOnEscape;
if (!dialogOptions.closeOnEscape && dialogOptions.modal) {
mappedOptions.backdrop = 'static';
}
break;
case 'closeText':
Bootstrap.unsupported('option', name, dialogOptions.closeText);
break;
case 'draggable':
this.$content
.draggable({
handle: handler.selectors.header,
drag: Bootstrap.relayEvent(this.$element, 'dialogdrag'),
start: Bootstrap.relayEvent(this.$element, 'dialogdragstart'),
end: Bootstrap.relayEvent(this.$element, 'dialogdragend')
})
.draggable(dialogOptions.draggable ? 'enable' : 'disable');
break;
case 'hide':
if (dialogOptions.hide === false || dialogOptions.hide === true) {
this.$element[dialogOptions.hide ? 'addClass' : 'removeClass']('fade');
mappedOptions.animation = dialogOptions.hide;
}
else {
Bootstrap.unsupported('option', name + ' (complex animation)', dialogOptions.hide);
}
break;
case 'modal':
if (!dialogOptions.closeOnEscape && dialogOptions.modal) {
mappedOptions.backdrop = 'static';
}
else {
mappedOptions.backdrop = dialogOptions.modal;
}
// If not a modal and no initial position, center it.
if (!dialogOptions.modal && !dialogOptions.position) {
this.position({ my: 'center', of: window });
}
break;
case 'position':
this.position(dialogOptions.position);
break;
// Resizable support (must initialize first).
case 'resizable':
this.$content
.resizable({
resize: Bootstrap.relayEvent(this.$element, 'dialogresize'),
start: Bootstrap.relayEvent(this.$element, 'dialogresizestart'),
end: Bootstrap.relayEvent(this.$element, 'dialogresizeend')
})
.resizable(dialogOptions.resizable ? 'enable' : 'disable');
break;
case 'show':
if (dialogOptions.show === false || dialogOptions.show === true) {
this.$element[dialogOptions.show ? 'addClass' : 'removeClass']('fade');
mappedOptions.animation = dialogOptions.show;
}
else {
Bootstrap.unsupported('option', name + ' (complex animation)', dialogOptions.show);
}
break;
case 'title':
this.$title.text(dialogOptions.title);
break;
}
}
// Add the supported dialog options to the mapped options.
mappedOptions.dialogOptions = dialogOptions;
return mappedOptions;
},
/**
* Handler for $.fn.dialog('moveToTop').
*/
moveToTop: function () {
Bootstrap.unsupported('method', 'moveToTop', arguments);
},
/**
* Handler for $.fn.dialog('option').
*/
option: function () {
var clone = {options: {}};
// Apply the parent option method to the clone of current options.
this.super.apply(clone, arguments);
// Merge in the cloned mapped options.
$.extend(true, this.options, this.mapDialogOptions(clone.options));
// Update buttons.
this.createButtons();
},
position: function(position) {
// Reset modal styling.
this.$element.css({
bottom: 'initial',
overflow: 'visible',
right: 'initial'
});
// Position the modal.
this.$element.position(position);
},
/**
* Handler for $.fn.dialog('open').
*/
open: function () {
this.show.apply(this, arguments);
},
/**
* Handler for $.fn.dialog('widget').
*/
widget: function () {
return this.$element;
}
}
};
};
// Extend the Bootstrap Modal plugin constructor class.
Bootstrap.extendPlugin('modal', Bootstrap.Modal.Bridge);
// Register default core dialog type handlers.
Bootstrap.Dialog.Handler.register('dialog');
Bootstrap.Dialog.Handler.register('modal');
/**
* Extend Drupal theming functions.
*/
$.extend(Drupal.theme, /** @lend Drupal.theme */ {
/**
* Renders a jQuery UI Dialog compatible button element.
*
* @param {Object} button
* The button object passed in the dialog options.
*
* @return {String}
* The modal dialog button markup.
*
* @see http://api.jqueryui.com/dialog/#option-buttons
* @see http://api.jqueryui.com/button/
*/
bootstrapModalDialogButton: function (button) {
var attributes = Attributes.create();
var icon = '';
var iconPosition = button.iconPosition || 'beginning';
iconPosition = (iconPosition === 'end' && !rtl) || (iconPosition === 'beginning' && rtl) ? 'after' : 'before';
// Handle Bootstrap icons differently.
if (button.bootstrapIcon) {
icon = Drupal.theme('icon', 'bootstrap', button.icon);
}
// Otherwise, assume it's a jQuery UI icon.
// @todo Map jQuery UI icons to Bootstrap icons?
else if (button.icon) {
var iconAttributes = Attributes.create()
.addClass(['ui-icon', button.icon])
.set('aria-hidden', 'true');
icon = '<span' + iconAttributes + '></span>';
}
// Label. Note: jQuery UI dialog has an inconsistency where it uses
// "text" instead of "label", so both need to be supported.
var value = button.label || button.text;
// Show/hide label.
if (icon && ((button.showLabel !== void 0 && !button.showLabel) || (button.text !== void 0 && !button.text))) {
value = '<span' + Attributes.create().addClass('sr-only') + '>' + value + '</span>';
}
attributes.set('value', iconPosition === 'before' ? icon + value : value + icon);
// Handle disabled.
attributes[button.disabled ? 'set' :'remove']('disabled', 'disabled');
if (button.classes) {
attributes.addClass(Object.keys(button.classes).map(function(key) { return button.classes[key]; }));
}
if (button['class']) {
attributes.addClass(button['class']);
}
if (button.primary) {
attributes.addClass('btn-primary');
}
return Drupal.theme('button', attributes);
}
});
});
})(window.jQuery, window.Drupal, window.Drupal.bootstrap, window.Attributes, window.drupalSettings);