@atlassian/aui
Version:
Atlassian User Interface library
181 lines (149 loc) • 6.67 kB
JavaScript
import $ from './jquery';
import 'select2/select2';
import layer from './layer';
/**
* Wraps a vanilla Select2 with ADG _style_, as an auiSelect2 method on jQuery objects.
*
* @since 5.2
*/
/**
* We make a copy of the original select2 so that later we might re-specify $.fn.auiSelect2 as $.fn.select2. That
* way, calling code will be able to call $thing.select2() as if they were calling the original library,
* and ADG styling will just magically happen.
*/
const originalSelect2 = $.fn.select2;
// AUI-specific classes
const auiContainer = 'aui-select2-container';
const auiDropdown = 'aui-select2-drop aui-dropdown2';
const auiHasAvatar = 'aui-has-avatar';
// Other constants
const DROPDOWN_LAYER_KEY = 'aui.select2DropdownLayer';
$.fn.auiSelect2 = function (first) {
let updatedArgs;
if ($.isPlainObject(first)) {
const auiOpts = $.extend({}, first);
const auiAvatarClass = auiOpts.hasAvatar ? ' ' + auiHasAvatar : '';
//add our classes in addition to those the caller specified
auiOpts.containerCssClass =
auiContainer +
auiAvatarClass +
(auiOpts.containerCssClass ? ' ' + auiOpts.containerCssClass : '');
auiOpts.dropdownCssClass =
auiDropdown +
auiAvatarClass +
(auiOpts.dropdownCssClass ? ' ' + auiOpts.dropdownCssClass : '');
updatedArgs = Array.prototype.slice.call(arguments, 1);
updatedArgs.unshift(auiOpts);
} else if (!arguments.length) {
updatedArgs = [
{
containerCssClass: auiContainer,
dropdownCssClass: auiDropdown,
},
];
} else {
updatedArgs = arguments;
}
const options = updatedArgs[0];
/**
* AUI-5464: after the upgrade to select2 v3.5.4, custom error handling stopped
* working, as the `ajax.params.error` function is overriden by select2, and
* there is no alternative to this...
*
* This is a workaround for creating an array of ajax error handlers that will
* contain the default handler and the custom ones, which is supported starting
* from jQuery v1.5: http://api.jquery.com/jquery.ajax/
*
* Please note this issue is fixed starting from select2 v4, though the data format
* is different.
*
* @see https://atlassian.slack.com/archives/CFGN5350T/p1686741137056489
*/
if (options.ajax && options.ajax.params && options.ajax.params.error) {
let customErrorHandlers = options.ajax.params.error;
if (!Array.isArray(customErrorHandlers)) {
customErrorHandlers = [customErrorHandlers];
}
let originalTransport = options.ajax.transport;
if (!originalTransport) {
originalTransport = originalSelect2.ajaxDefaults.transport;
}
const newTransport = function (...args) {
args[0].error = [args[0].error, ...customErrorHandlers];
return originalTransport(...args);
};
options.ajax.transport = newTransport;
}
const result = originalSelect2.apply(this, updatedArgs);
const select2Instance = this;
const searchLabel = options.searchLabel;
select2Instance.on('select2-open', function () {
const $selectInput = $(this);
const $selectDropdown = $selectInput.select2('dropdown');
let dropdownLayer = $selectInput.data(DROPDOWN_LAYER_KEY);
if (!dropdownLayer) {
dropdownLayer = layer();
$selectInput.data(DROPDOWN_LAYER_KEY, dropdownLayer);
}
dropdownLayer.show(); // add layer to layer manager to get top z-index
$selectDropdown.css('z-index', dropdownLayer.$el.css('z-index'));
// mask is created during opening event, before open
$('#select2-drop-mask').css('z-index', dropdownLayer.$el.css('z-index'));
if (options.multiple || $selectInput.attr('multiple')) {
// This is a multi-select, exiting
return;
}
if (searchLabel) {
$selectDropdown.find('.select2-search label').text(searchLabel);
}
});
select2Instance.on('select2-close', function () {
const $selectInput = $(this);
const dropdownLayer = $selectInput.data(DROPDOWN_LAYER_KEY);
if (dropdownLayer) {
dropdownLayer.hide();
} else {
AJS.warn(
'Warning! AUI: `select2-close` event handler could not discover the layer linked with the Select2 dropdown.',
'This may be happening when there are many instances of Select2 created on the same DOM element.',
'Consider checking your code as this can negatively affect performance.'
);
}
$selectInput.removeData('was-ariadescribedby-cleared');
});
select2Instance.on('select2-loaded', function () {
const $selectInput = $(this);
const wasAriaDescribedByCleared = $selectInput.data('was-ariadescribedby-cleared');
if (options.multiple || $selectInput.attr('multiple') || wasAriaDescribedByCleared) {
return;
}
const $selectDropdown = $selectInput.select2('dropdown');
// AUI-5461: when single select dropdown opens up, the first option is
// instantly focused, making SRs announce it, while skipping the search field
$selectDropdown.find('.select2-search .select2-input').attr('aria-activedescendant', '');
$selectInput.data('was-ariadescribedby-cleared', true);
});
select2Instance.on('select2-focus', function () {
const $selectInput = $(this);
const $container = $selectInput.parent().find('.select2-container');
if (options.multiple || $selectInput.attr('multiple')) {
if (searchLabel) {
$container.find('.select2-search-field label').text(searchLabel);
}
return;
}
const $focusserLabel = $container.find('label');
if ($focusserLabel.attr('id')) {
// The id is already assigned, exiting
return;
}
// AUI-5462: when single select is initialized, the selected option container is
// linked to the button via aria-labelledby, preventing SRs from reading its label
const $focusserInput = $container.find('.select2-focusser');
const labelId = $focusserInput.attr('id') + '-label';
const ariaLabelledby = $focusserInput.attr('aria-labelledby');
$focusserLabel.attr('id', labelId);
$focusserInput.attr('aria-labelledby', labelId + ' ' + ariaLabelledby);
});
return result;
};