diff options
Diffstat (limited to 'MLEB/UniversalLanguageSelector/resources/js/ext.uls.dialog.js')
-rw-r--r-- | MLEB/UniversalLanguageSelector/resources/js/ext.uls.dialog.js | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.dialog.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.dialog.js new file mode 100644 index 00000000..737f6522 --- /dev/null +++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.dialog.js @@ -0,0 +1,205 @@ +/*! + * 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 $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^="-"])' + ], + afterOpen = options.afterOpen, + afterClose = options.afterClose; + + 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(); + if ( afterOpen ) { + afterOpen(); + } + } + + function close() { + $dialog.hide(); + removeEvents(); + hideOverlay(); + if ( afterClose ) { + afterClose(); + } + } + + function elem() { + return $dialog; + } + + function addOverlay() { + // Check if overlay is already there. + if ( !$overlay ) { + $overlay = $( '<div>' ) + .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; +}() ); |