MDL-78665 libs: page helpers global amd module

Add a new global module to get information from the page. Some methods
are just local functions moved to as a global library. For example, the
drawers isSmall or isLarge to detect the page width. The other funcionts
added are to detect focusable elements and they are needed to loop on
elements, especially when accessibility keyboard navigation is implemented.
This commit is contained in:
Ferran Recio 2023-07-18 18:01:28 +02:00
parent 1474f74687
commit 3b53616321
6 changed files with 165 additions and 46 deletions

11
lib/amd/build/pagehelpers.min.js vendored Normal file
View File

@ -0,0 +1,11 @@
define("core/pagehelpers",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.previousFocusableElement=_exports.nextFocusableElement=_exports.isSmall=_exports.isLarge=_exports.isExtraSmall=_exports.getCurrentWidth=_exports.focusableElements=_exports.firstFocusableElement=void 0;
/**
* Page utility helpers.
*
* @module core/pagehelpers
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const Sizes_small=576,Sizes_medium=991,Sizes_large=1400,Selectors_focusable='a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])',getCurrentWidth=()=>{const DomRect=document.body.getBoundingClientRect();return DomRect.x+DomRect.width};_exports.getCurrentWidth=getCurrentWidth;_exports.isExtraSmall=()=>getCurrentWidth()<Sizes_small;_exports.isSmall=()=>getCurrentWidth()<Sizes_medium;_exports.isLarge=()=>getCurrentWidth()>=Sizes_large;_exports.firstFocusableElement=container=>(container||document).querySelector(Selectors_focusable);const focusableElements=container=>(container||document).querySelectorAll(Selectors_focusable);_exports.focusableElements=focusableElements;_exports.previousFocusableElement=(container,loopSelection)=>getRelativeFocusableElement(container,loopSelection,-1);_exports.nextFocusableElement=(container,loopSelection)=>getRelativeFocusableElement(container,loopSelection,1);const getRelativeFocusableElement=(container,loopSelection,direction)=>{var _focusables;const focusedElement=document.activeElement,focusables=[...focusableElements(container)],focusedIndex=focusables.indexOf(focusedElement);if(-1===focusedIndex)return null;const newIndex=focusedIndex+direction;return void 0!==focusables[newIndex]?focusables[newIndex]:1!=loopSelection?null:direction>0?null!==(_focusables$=focusables[0])&&void 0!==_focusables$?_focusables$:null:null!==(_focusables=focusables[focusables.length-1])&&void 0!==_focusables?_focusables:null;var _focusables$}}));
//# sourceMappingURL=pagehelpers.min.js.map

File diff suppressed because one or more lines are too long

150
lib/amd/src/pagehelpers.js Normal file
View File

@ -0,0 +1,150 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Page utility helpers.
*
* @module core/pagehelpers
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Maximum sizes for breakpoints. This needs to correspond with Bootstrap
* Breakpoints
*
* @private
*/
const Sizes = {
small: 576,
medium: 991,
large: 1400
};
const Selectors = {
focusable: 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
};
/**
* Get the current body width.
* @returns {number} the current body width.
*/
export const getCurrentWidth = () => {
const DomRect = document.body.getBoundingClientRect();
return DomRect.x + DomRect.width;
};
/**
* Check if the user uses an extra small size browser.
*
* @returns {boolean} true if the body is smaller than sizes.small max size.
*/
export const isExtraSmall = () => {
const browserWidth = getCurrentWidth();
return browserWidth < Sizes.small;
};
/**
* Check if the user uses a small size browser.
*
* @returns {boolean} true if the body is smaller than sizes.medium max size.
*/
export const isSmall = () => {
const browserWidth = getCurrentWidth();
return browserWidth < Sizes.medium;
};
/**
* Check if the user uses a large size browser.
*
* @returns {boolean} true if the body is smaller than sizes.large max size.
*/
export const isLarge = () => {
const browserWidth = getCurrentWidth();
return browserWidth >= Sizes.large;
};
/**
* Get the first focusable element inside a container.
* @param {HTMLElement} [container] Container to search in. Defaults to document.
* @returns {HTMLElement|null}
*/
export const firstFocusableElement = (container) => {
const containerElement = container || document;
return containerElement.querySelector(Selectors.focusable);
};
/**
* Get all focusable elements inside a container.
* @param {HTMLElement} [container] Container to search in. Defaults to document.
* @returns {HTMLElement[]}
*/
export const focusableElements = (container) => {
const containerElement = container || document;
return containerElement.querySelectorAll(Selectors.focusable);
};
/**
* Get the previous focusable element in a container.
* It uses the current focused element to know where to start the search.
* @param {HTMLElement} [container] Container to search in. Defaults to document.
* @param {Boolean} [loopSelection] Whether to loop selection or not. Default to false.
* @returns {HTMLElement|null}
*/
export const previousFocusableElement = (container, loopSelection) => {
return getRelativeFocusableElement(container, loopSelection, -1);
};
/**
* Get the next focusable element in a container.
* It uses the current focused element to know where to start the search.
* @param {HTMLElement} [container] Container to search in. Defaults to document.
* @param {Boolean} [loopSelection] Whether to loop selection or not. Default to false.
* @returns {HTMLElement|null}
*/
export const nextFocusableElement = (container, loopSelection) => {
return getRelativeFocusableElement(container, loopSelection, 1);
};
/**
* Internal function to get the next or previous focusable element.
* @param {HTMLElement} [container] Container to search in. Defaults to document.
* @param {Boolean} [loopSelection] Whether to loop selection or not.
* @param {Number} [direction] Direction to search in. 1 for next, -1 for previous.
* @returns {HTMLElement|null}
* @private
*/
const getRelativeFocusableElement = (container, loopSelection, direction) => {
const focusedElement = document.activeElement;
const focusables = [...focusableElements(container)];
const focusedIndex = focusables.indexOf(focusedElement);
if (focusedIndex === -1) {
return null;
}
const newIndex = focusedIndex + direction;
if (focusables[newIndex] !== undefined) {
return focusables[newIndex];
}
if (loopSelection != true) {
return null;
}
if (direction > 0) {
return focusables[0] ?? null;
}
return focusables[focusables.length - 1] ?? null;
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,7 @@ import Templates from 'core/templates';
import * as Aria from 'core/aria';
import {dispatchEvent} from 'core/event_dispatcher';
import {debounce} from 'core/utils';
import {isSmall, isLarge} from 'core/pagehelpers';
import Pending from 'core/pending';
// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.
import jQuery from 'jquery';
@ -50,50 +51,6 @@ const CLASSES = {
TOGGLERIGHT: '.drawer-right-toggle',
};
/**
* Maximum sizes for breakpoints. This needs to correspond with Bootstrap
* Breakpoints
*
* @private
*/
const sizes = {
medium: 991,
large: 1400
};
/**
* Get the current body width.
*
* @returns {number} the current body width.
* @private
*/
const getCurrentWidth = () => {
const DomRect = document.body.getBoundingClientRect();
return DomRect.x + DomRect.width;
};
/**
* Check if the user uses a small size browser.
*
* @returns {boolean} true if the body is smaller than sizes.medium max size.
* @private
*/
const isSmall = () => {
const browserWidth = getCurrentWidth();
return browserWidth < sizes.medium;
};
/**
* Check if the user uses a large size browser.
*
* @returns {boolean} true if the body is smaller than sizes.large max size.
* @private
*/
const isLarge = () => {
const browserWidth = getCurrentWidth();
return browserWidth >= sizes.large;
};
/**
* Add a backdrop to the page.
*