mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-24 23:55:53 +00:00
80141b61c9
Depends-on: Id74a80cda6cf460cc2b579269b8d5b2ce81c8ca5 Change-Id: Ibd9518dab78d1d9a541b501d920fe3bd4d362093
166 lines
5 KiB
JavaScript
166 lines
5 KiB
JavaScript
/**
|
|
* JavaScript enhancement to the collapsible sidebar.
|
|
*
|
|
* The sidebar provides basic show/hide functionality with CSS
|
|
* but JavaScript is used for progressive enhancements.
|
|
*
|
|
* Enhancements include:
|
|
* - Update `aria-role`s based on expanded/collapsed state.
|
|
* - Update button icon based on expanded/collapsed state.
|
|
* - Persist the sidebar state for logged-in users.
|
|
*
|
|
*/
|
|
|
|
/** @interface MwApiConstructor */
|
|
/** @interface CheckboxHack */
|
|
/** @interface MwApi */
|
|
|
|
var checkboxHack = /** @type {CheckboxHack} */ require( /** @type {string} */( 'mediawiki.page.ready' ) ).checkboxHack,
|
|
SIDEBAR_BUTTON_ID = 'mw-sidebar-button',
|
|
SIDEBAR_CHECKBOX_ID = 'mw-sidebar-checkbox',
|
|
SIDEBAR_PREFERENCE_NAME = 'VectorSidebarVisible';
|
|
|
|
var debounce = require( /** @type {string} */ ( 'mediawiki.util' ) ).debounce;
|
|
/** @type {MwApi} */ var api;
|
|
|
|
/**
|
|
* Revise the button's `aria-expanded` state to match the checked state.
|
|
*
|
|
* @param {HTMLInputElement} checkbox
|
|
* @param {HTMLElement} button
|
|
* @return {void}
|
|
* @ignore
|
|
*/
|
|
function updateAriaExpanded( checkbox, button ) {
|
|
button.setAttribute( 'aria-expanded', checkbox.checked.toString() );
|
|
}
|
|
|
|
/**
|
|
* Update the `aria-expanded` attribute based on checkbox state (target visibility) changes.
|
|
*
|
|
* @param {HTMLInputElement} checkbox
|
|
* @param {HTMLElement} button
|
|
* @return {function(): void} Cleanup function that removes the added event listeners.
|
|
* @ignore
|
|
*/
|
|
function bindUpdateAriaExpandedOnInput( checkbox, button ) {
|
|
var listener = updateAriaExpanded.bind( undefined, checkbox, button );
|
|
// Whenever the checkbox state changes, update the `aria-expanded` state.
|
|
checkbox.addEventListener( 'input', listener );
|
|
|
|
return function () {
|
|
checkbox.removeEventListener( 'input', listener );
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Manually change the checkbox state when the button is focused and SPACE is pressed.
|
|
*
|
|
* @param {HTMLElement} button
|
|
* @return {function(): void} Cleanup function that removes the added event listeners.
|
|
* @ignore
|
|
*/
|
|
function bindToggleOnSpaceEnter( button ) {
|
|
function isEnterOrSpace( /** @type {KeyboardEvent} */ event ) {
|
|
return event.key === ' ' || event.key === 'Enter';
|
|
}
|
|
|
|
function onKeydown( /** @type {KeyboardEvent} */ event ) {
|
|
// Only handle SPACE and ENTER.
|
|
if ( !isEnterOrSpace( event ) ) {
|
|
return;
|
|
}
|
|
// Prevent the browser from scrolling when pressing space. The browser will
|
|
// try to do this unless the "button" element is a button or a checkbox.
|
|
// Depending on the actual "button" element, this also possibly prevents a
|
|
// native click event from being triggered so we programatically trigger a
|
|
// click event in the keyup handler.
|
|
event.preventDefault();
|
|
}
|
|
|
|
function onKeyup( /** @type {KeyboardEvent} */ event ) {
|
|
// Only handle SPACE and ENTER.
|
|
if ( !isEnterOrSpace( event ) ) {
|
|
return;
|
|
}
|
|
|
|
// A native button element triggers a click event when the space or enter
|
|
// keys are pressed. Since the passed in "button" may or may not be a
|
|
// button, programmatically trigger a click event to make it act like a
|
|
// button.
|
|
button.click();
|
|
}
|
|
|
|
button.addEventListener( 'keydown', onKeydown );
|
|
button.addEventListener( 'keyup', onKeyup );
|
|
|
|
return function () {
|
|
button.removeEventListener( 'keydown', onKeydown );
|
|
button.removeEventListener( 'keyup', onKeyup );
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Improve the interactivity of the sidebar panel by binding optional checkbox hack enhancements
|
|
* for focus and `aria-expanded`. Also, flip the icon image on click.
|
|
*
|
|
* @param {HTMLElement|null} checkbox
|
|
* @param {HTMLElement|null} button
|
|
* @return {void}
|
|
*/
|
|
function initCheckboxHack( checkbox, button ) {
|
|
if ( checkbox instanceof HTMLInputElement && button ) {
|
|
checkboxHack.bindToggleOnClick( checkbox, button );
|
|
bindUpdateAriaExpandedOnInput( checkbox, button );
|
|
updateAriaExpanded( checkbox, button );
|
|
bindToggleOnSpaceEnter( button );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a debounced API request to save the sidebar user preference.
|
|
* The request is meant to fire 1000 milliseconds after the last click on
|
|
* the sidebar button.
|
|
*
|
|
* @param {HTMLInputElement} checkbox
|
|
* @return {any}
|
|
*/
|
|
function saveSidebarState( checkbox ) {
|
|
return debounce( 1000, function () {
|
|
api = api || new mw.Api();
|
|
api.saveOption( SIDEBAR_PREFERENCE_NAME, checkbox.checked ? 1 : 0 );
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Bind the event handler that saves the sidebar state to the click event
|
|
* on the sidebar button.
|
|
*
|
|
* @param {HTMLElement|null} checkbox
|
|
* @param {HTMLElement|null} button
|
|
*/
|
|
function bindSidebarClickEvent( checkbox, button ) {
|
|
if ( checkbox instanceof HTMLInputElement && button ) {
|
|
checkbox.addEventListener( 'input', saveSidebarState( checkbox ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize all JavaScript sidebar enhancements.
|
|
*
|
|
* @param {Window} window
|
|
*/
|
|
function init( window ) {
|
|
var checkbox = window.document.getElementById( SIDEBAR_CHECKBOX_ID ),
|
|
button = window.document.getElementById( SIDEBAR_BUTTON_ID );
|
|
|
|
initCheckboxHack( checkbox, button );
|
|
if ( mw.config.get( 'wgUserName' ) && !mw.config.get( 'wgVectorDisableSidebarPersistence' ) ) {
|
|
bindSidebarClickEvent( checkbox, button );
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
init: init
|
|
};
|