MinervaNeue/components/ToggleList/ToggleList.js

100 lines
3.0 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
} );
}() );