UNPKG

@atlassian/aui

Version:

Atlassian User Interface library

235 lines (203 loc) 7.61 kB
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;