@atlassian/aui
Version:
Atlassian User Interface library
235 lines (203 loc) • 7.61 kB
JavaScript
import $ from './jquery';
import amdify from './internal/amdify';
import globalize from './internal/globalize';
import layerWidget from './layer';
import widget from './internal/widget';
import keyCode from './key-code';
import { CLOSE_BUTTON, CLOSE_BUTTON_CLASS, CLOSE_BUTTON_CLASS_SELECTOR } from './close-button';
import * as deprecate from './internal/deprecation';
const defaults = {
'aui-focus': 'false', // do not focus by default as it's overridden below
'aui-blanketed': 'true',
};
function applyDefaults($el) {
$.each(defaults, function (key, value) {
const dataKey = 'data-' + key;
if (!$el[0].hasAttribute(dataKey)) {
$el.attr(dataKey, value);
}
});
}
/**
* Gets keyboard-focusable elements within a specified element
* https://zellwk.com/blog/keyboard-focusable-elements/
* @param {HTMLElement} [element=document] element
* @returns {Array}
*/
function getKeyboardFocusableElements(element) {
const focusableSelector =
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])';
const elementList = [...element.querySelectorAll(focusableSelector)];
return elementList.filter(
(el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden')
);
}
function handleInitialFocus(el) {
// prevent inability to focus the close button.
const closeButton = el.querySelector(CLOSE_BUTTON_CLASS_SELECTOR);
if (closeButton) {
const lazyDeprecate = deprecate.getMessageLogger(
`Dialog2 [${CLOSE_BUTTON_CLASS_SELECTOR}]`,
{
removeInVersion: '10.0.0',
sinceVersion: '9.6.0',
extraInfo:
'Replace it with another actionable element inside your Dialog which will trigger closing of the modal.',
deprecationType: 'MARKUP',
}
);
lazyDeprecate();
closeButton.setAttribute('tabindex', '0');
}
el.setAttribute('tabindex', '-1');
// Print deprecation note for old custom focus selection if found
const deprecatedFocusSelector = el.hasAttribute('data-aui-focus-selector');
if (deprecatedFocusSelector) {
const lazyDeprecate = deprecate.getMessageLogger(
'Dialog2 [data-aui-focus-selector] attribute',
{
removeInVersion: '10.0.0',
alternativeName: 'initialisation on DOM insertion',
sinceVersion: '9.6.0',
extraInfo:
'Replace [data-aui-focus-selector] attribute with HTML [autofocus] attribute',
deprecationType: 'ATTRIBUTE',
}
);
lazyDeprecate();
}
// Focus custom element user marked by autofocus if found
const autofocusEl = el.querySelector('[autofocus]');
if (autofocusEl) {
// Set timeout and blur are needed to avoid Chrome complaining about focus already set on an element
// Also helps to ensure focus will 100% work in every environment
document.activeElement.blur();
setTimeout(() => autofocusEl.focus(), 0);
return;
}
// Deprecated focus selection, if autofocus not found
if (deprecatedFocusSelector) {
const elementToFocus = el.querySelector(deprecatedFocusSelector);
if (elementToFocus) {
// Set timeout and blur are needed to avoid Chrome complaining about focus already set on an element
// Also helps to ensure focus will 100% work in every environment
elementToFocus.blur();
setTimeout(() => elementToFocus.focus(), 0);
return;
}
}
// If not custom focus target, focus first focusable element if possible
const focusableElementList = getKeyboardFocusableElements(el);
if (focusableElementList.length) {
let focusableElement = focusableElementList.shift();
// if the first focusable element is Dialog's close button
// then take another one from the list
if (focusableElement.classList.contains(CLOSE_BUTTON_CLASS)) {
focusableElement = focusableElementList.shift();
}
if (focusableElement) {
// Set timeout and blur are needed to avoid Chrome complaining about focus already set on an element
// Also helps to ensure focus will 100% work in every environment
focusableElement.blur();
setTimeout(() => focusableElement.focus(), 0);
}
return;
}
// If no focusable elements found, put focus on dialog container itself
el.focus();
}
function Dialog2(selector) {
this._handlers = new WeakMap();
if (selector) {
this.$el = $(selector);
} else {
this.$el = $(`
<section role="dialog" aria-modal="true" class="aui-layer aui-dialog2 aui-dialog2-medium">
<header class="aui-dialog2-header">
<h2 class="aui-dialog2-header-main"></h2>
${CLOSE_BUTTON}
</button>
</header>
<div class="aui-dialog2-content"></div>
<footer class="aui-dialog2-footer"></footer>
</section>`);
}
this.$header = this.$el.find('.aui-dialog2-header');
this.$content = this.$el.find('.aui-dialog2-content');
this.$footer = this.$el.find('.aui-dialog2-footer');
applyDefaults(this.$el);
}
Dialog2.prototype.on = function (event, fn) {
const $el = this.$el;
if (!this._handlers.get(fn)) {
const handler = function (e) {
if (e.target === $el.get(0)) {
fn.apply(this, arguments);
}
};
layerWidget($el).on(event, handler);
this._handlers.set(fn, handler);
}
return this;
};
Dialog2.prototype.off = function (event, fn) {
const $el = this.$el;
const handler = this._handlers.get(fn);
if (handler) {
layerWidget($el).off(event, handler);
this._handlers.delete(fn);
}
return this;
};
Dialog2.prototype.show = function () {
layerWidget(this.$el).show();
return this;
};
Dialog2.prototype.hide = function () {
layerWidget(this.$el).hide();
return this;
};
Dialog2.prototype.remove = function () {
layerWidget(this.$el).remove();
return this;
};
Dialog2.prototype.isVisible = function () {
return layerWidget(this.$el).isVisible();
};
var dialog2Widget = widget('dialog2', Dialog2);
var dialog2GlobalHandlers = new Set();
dialog2Widget.on = function (eventName, fn) {
if (!dialog2GlobalHandlers.has(fn)) {
layerWidget.on(eventName, '.aui-dialog2', fn);
dialog2GlobalHandlers.add(fn);
}
return this;
};
dialog2Widget.off = function (eventName, fn) {
if (dialog2GlobalHandlers.has(fn)) {
layerWidget.off(eventName, '.aui-dialog2', fn);
dialog2GlobalHandlers.delete(fn);
}
return this;
};
/* Live events */
$(document).on('click keydown', `.aui-dialog2-header ${CLOSE_BUTTON_CLASS_SELECTOR}`, function (e) {
const shouldHandle =
e.type === 'click' || e.keyCode === keyCode.ENTER || e.keyCode === keyCode.SPACE;
if (shouldHandle) {
e.preventDefault();
dialog2Widget($(e.target).closest('.aui-dialog2')).hide();
}
});
dialog2Widget.on('show', function (e, $el) {
handleInitialFocus($el[0]);
});
dialog2Widget.on('hide', function (e, $el) {
const layer = layerWidget($el);
if ($el.data('aui-remove-on-hide')) {
layer.remove();
}
});
amdify('aui/dialog2', dialog2Widget);
globalize('dialog2', dialog2Widget);
export default dialog2Widget;