mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-27 00:50:08 +00:00
19edeeabe0
Other page elements outside of Vector should be notified that the content area is changing size, in case they need to adjust the size of fixed elements. Triggering window.resize should allow them to adapt without needing to write Vector-specific code. A specific case of this: the floating edit toolbar when VisualEditor is open would be incorrectly sized / aligned if you toggled the sidebar in edit mode. Bug: T300826 Change-Id: I79e0fc67b5e35c2fb975a0a3048184de0d63813e
177 lines
5.3 KiB
JavaScript
177 lines
5.3 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 );
|
|
|
|
// Trigger a resize event so other parts of the page can adapt:
|
|
var event;
|
|
if ( typeof Event === 'function' ) {
|
|
event = new Event( 'resize' );
|
|
} else {
|
|
// IE11
|
|
event = window.document.createEvent( 'UIEvents' );
|
|
event.initUIEvent( 'resize', true, false, window, 0 );
|
|
}
|
|
window.dispatchEvent( event );
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
};
|