mediawiki-skins-MinervaNeue/components/ToggleList/ToggleList.js
jdlrobson 72df451bd3 Embrace packageFiles
Help with readability by using module.exports and require rather than the MobileFrontend
provided mw.mobileFrontend module manager (and avoid adopting webpack at this time)

Replace usages of mw.mobileFrontend.require with local require and module.exports
(compatible with RL or Node implementation)

Changes:
* Notifications modules are merged into skins.minerva.scripts and initialised
via a client side check.
* new file overlayManager for exporting an overlayManager singleton
rather than being hidden inside resources/skins.minerva.scripts/init.js
* All M.define/M.requires swapped out for require where possible
The `define` method is now forbidden in the repo.

Bug: T212944
Change-Id: I44790dd3fc6fe42bb502d79c39c4081c223bf2b1
2019-07-16 18:04:10 +00:00

100 lines
3 KiB
JavaScript

( function () {
var
/** The component selector. */
selector = '.toggle-list',
/** The visible label icon associated with the checkbox. */
toggleSelector = '.toggle-list__toggle',
/** The underlying hidden checkbox that controls list visibility. */
checkboxSelector = '.toggle-list__checkbox',
listSelector = '.toggle-list__list';
/**
* Automatically dismiss the list when clicking or focusing elsewhere and update the
* aria-expanded attribute based on list visibility.
* @param {Window} window
* @param {HTMLElement} component
* @param {OO.EventEmitter} eventBus
* @param {boolean} [resize] If true, resize the menu on scroll and window resize.
* @return {void}
*/
function bind( window, component, eventBus, resize ) {
var
toggle = component.querySelector( toggleSelector ),
checkbox = /** @type {HTMLInputElement} */ (
component.querySelector( checkboxSelector )
),
list = component.querySelector( listSelector );
window.addEventListener( 'click', function ( event ) {
if ( event.target !== toggle && event.target !== checkbox ) {
// Something besides the button or checkbox was tapped. Dismiss the list.
_dismiss( checkbox );
}
}, true );
// If focus is given to any element outside the list, dismiss the list. Setting a focusout
// listener on list would be preferable, but this interferes with the click listener.
window.addEventListener( 'focusin', function ( event ) {
if ( event.target instanceof Node && !component.contains( event.target ) ) {
// Something besides the button or checkbox was focused. Dismiss the list.
_dismiss( checkbox );
}
}, true );
checkbox.addEventListener( 'change', _updateAriaExpanded.bind( undefined, checkbox ) );
if ( resize ) {
eventBus.on( 'scroll:throttled', _resize.bind( undefined, list ) );
eventBus.on( 'resize:throttled', _resize.bind( undefined, list ) );
}
}
/**
* @param {HTMLElement} component
* @param {boolean} [resize] If true, resize the menu to fit within the window.
* @return {void}
*/
function render( component, resize ) {
var list = /** @type {HTMLElement} */ ( component.querySelector( listSelector ) );
if ( resize ) {
_resize( list );
}
}
/**
* Hides the list.
* @param {HTMLInputElement} checkbox
* @return {void}
*/
function _dismiss( checkbox ) {
checkbox.checked = false;
_updateAriaExpanded( checkbox );
}
/**
* @param {HTMLElement} list
* @return {void}
*/
function _resize( list ) {
var rect = list.getClientRects()[ 0 ];
if ( rect ) {
list.style.maxHeight = window.document.documentElement.clientHeight - rect.top + 'px';
}
}
/**
* Revise the aria-expanded state to match the checked state.
* @param {HTMLInputElement} checkbox
* @return {void}
*/
function _updateAriaExpanded( checkbox ) {
checkbox.setAttribute( 'aria-expanded', ( !!checkbox.checked ).toString() );
}
module.exports = Object.freeze( {
selector: selector,
render: render,
bind: bind
} );
}() );