/*! * A simple dialog to be used inside ULS. * * @private * @since 2020.01 * * Copyright (C) 2019-2020 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris, * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland and other * contributors. See CREDITS for a list. * * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't * have to do anything special to choose one license or the other and you don't * have to notify anyone which license you are using. You are free to use * UniversalLanguageSelector in commercial projects as long as the copyright * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. * * @file * @ingroup Extensions * @licence GNU General Public Licence 2.0 or later * @licence MIT License */ ( function () { 'use strict'; var ULSDialog = function ( options ) { var noop = function () { }, $dialog = options.container, hasOverlay = options.hasOverlay, $overlay, // Source: https://github.com/ghosh/Micromodal/blob/master/lib/src/index.js#L4 FOCUSABLE_NODES = [ 'a[href]', 'area[href]', 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', 'select:not([disabled]):not([aria-hidden])', 'textarea:not([disabled]):not([aria-hidden])', 'button:not([disabled]):not([aria-hidden])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])' ], afterClose = options.afterClose || noop, afterOpen = options.afterOpen || noop; function getFocusableNodes() { return $dialog.find( FOCUSABLE_NODES.join( ', ' ) ); } function isElementInDialog( targetElement ) { return $dialog.get( 0 ).contains( targetElement ); } function focusOverlay() { if ( $overlay ) { $overlay.get( 0 ).focus(); } } function focusFirstNodeOrOverlay( $focusableNodes ) { if ( $focusableNodes === undefined ) { $focusableNodes = getFocusableNodes(); } if ( $focusableNodes.length ) { $focusableNodes.get( 0 ).focus(); } else { focusOverlay(); } } function maintainFocus( event ) { var $focusableNodes = getFocusableNodes(), focusedItemIndex; if ( !hasOverlay ) { // overlay is not present, so let tabbing flow as normal. return; } if ( !$focusableNodes.length ) { // no focusable node in the dialog, focus on the overlay. focusOverlay(); return; } if ( !isElementInDialog( document.activeElement ) ) { focusFirstNodeOrOverlay( $focusableNodes ); } else { focusedItemIndex = $focusableNodes.index( document.activeElement ); if ( event.shiftKey && focusedItemIndex === 0 ) { $focusableNodes.get( -1 ).focus(); event.preventDefault(); } else if ( !event.shiftKey && focusedItemIndex === $focusableNodes.length - 1 ) { focusFirstNodeOrOverlay( $focusableNodes ); event.preventDefault(); } } } function handleFirstFocus( event ) { if ( !hasOverlay ) { // Overlay is not present, so let tabbing flow as normal. return; } if ( isElementInDialog( event.target ) ) { return; } focusFirstNodeOrOverlay(); } function onKeydown( event ) { switch ( event.key ) { case 'Esc': case 'Escape': // eslint-disable-next-line no-use-before-define close(); event.preventDefault(); break; case 'Tab': maintainFocus( event ); break; } } function addEvents() { $( document ) .on( 'keydown', onKeydown ) .on( 'focusin', handleFirstFocus ); } function removeEvents() { $( document ) .off( 'keydown', onKeydown ) .off( 'focusin', handleFirstFocus ); } function showOverlay() { if ( $overlay ) { $overlay.show(); $( document.body ).addClass( 'uls-no-overflow' ); } } function hideOverlay() { if ( $overlay ) { $overlay.hide(); $( document.body ).removeClass( 'uls-no-overflow' ); } } function open() { $dialog.show(); addEvents(); showOverlay(); focusFirstNodeOrOverlay(); afterOpen(); } function close() { $dialog.hide(); removeEvents(); hideOverlay(); afterClose(); } function elem() { return $dialog; } function addOverlay() { // Check if overlay is already there. if ( !$overlay ) { $overlay = $( '
' ) .addClass( 'uls-overlay' ) .prop( 'tabindex', '-1' ) .on( 'click', close ) .appendTo( document.body ); } } $dialog.addClass( 'uls-dialog' ); if ( hasOverlay ) { addOverlay(); } return { open: open, close: close, elem: elem }; }; mw.uls = mw.uls || {}; mw.uls.Dialog = ULSDialog; }() );