1 // This file is part of Moodle - http://moodle.org/
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 * Page utility helpers.
19 * @module core/pagehelpers
20 * @copyright 2023 Ferran Recio <ferran@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 * Maximum sizes for breakpoints. This needs to correspond with Bootstrap
37 focusable: 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
41 behatSite: 'behat-site',
45 * Check fi the current page is a Behat site.
46 * @returns {boolean} true if the current page is a Behat site.
48 export const isBehatSite = () => {
49 return document.body.classList.contains(Classes.behatSite);
53 * Get the current body width.
54 * @returns {number} the current body width.
56 export const getCurrentWidth = () => {
57 const DomRect = document.body.getBoundingClientRect();
58 return DomRect.x + DomRect.width;
62 * Check if the user uses an extra small size browser.
64 * @returns {boolean} true if the body is smaller than sizes.small max size.
66 export const isExtraSmall = () => {
67 const browserWidth = getCurrentWidth();
68 return browserWidth < Sizes.small;
72 * Check if the user uses a small size browser.
74 * @returns {boolean} true if the body is smaller than sizes.medium max size.
76 export const isSmall = () => {
77 const browserWidth = getCurrentWidth();
78 return browserWidth < Sizes.medium;
82 * Check if the user uses a large size browser.
84 * @returns {boolean} true if the body is smaller than sizes.large max size.
86 export const isLarge = () => {
87 const browserWidth = getCurrentWidth();
88 return browserWidth >= Sizes.large;
92 * Get the first focusable element inside a container.
93 * @param {HTMLElement} [container] Container to search in. Defaults to document.
94 * @returns {HTMLElement|null}
96 export const firstFocusableElement = (container) => {
97 const containerElement = container || document;
98 return containerElement.querySelector(Selectors.focusable);
102 * Get the last focusable element inside a container.
103 * @param {HTMLElement} [container] Container to search in. Defaults to document.
104 * @returns {HTMLElement|null}
106 export const lastFocusableElement = (container) => {
107 const containerElement = container || document;
108 const focusableElements = containerElement.querySelectorAll(Selectors.focusable);
109 return focusableElements[focusableElements.length - 1] ?? null;
113 * Get all focusable elements inside a container.
114 * @param {HTMLElement} [container] Container to search in. Defaults to document.
115 * @returns {HTMLElement[]}
117 export const focusableElements = (container) => {
118 const containerElement = container || document;
119 return containerElement.querySelectorAll(Selectors.focusable);
123 * Get the previous focusable element in a container.
124 * It uses the current focused element to know where to start the search.
125 * @param {HTMLElement} [container] Container to search in. Defaults to document.
126 * @param {Boolean} [loopSelection] Whether to loop selection or not. Default to false.
127 * @returns {HTMLElement|null}
129 export const previousFocusableElement = (container, loopSelection) => {
130 return getRelativeFocusableElement(container, loopSelection, -1);
134 * Get the next focusable element in a container.
135 * It uses the current focused element to know where to start the search.
136 * @param {HTMLElement} [container] Container to search in. Defaults to document.
137 * @param {Boolean} [loopSelection] Whether to loop selection or not. Default to false.
138 * @returns {HTMLElement|null}
140 export const nextFocusableElement = (container, loopSelection) => {
141 return getRelativeFocusableElement(container, loopSelection, 1);
145 * Internal function to get the next or previous focusable element.
146 * @param {HTMLElement} [container] Container to search in. Defaults to document.
147 * @param {Boolean} [loopSelection] Whether to loop selection or not.
148 * @param {Number} [direction] Direction to search in. 1 for next, -1 for previous.
149 * @returns {HTMLElement|null}
152 const getRelativeFocusableElement = (container, loopSelection, direction) => {
153 const focusedElement = document.activeElement;
154 const focusables = [...focusableElements(container)];
155 const focusedIndex = focusables.indexOf(focusedElement);
157 if (focusedIndex === -1) {
161 const newIndex = focusedIndex + direction;
163 if (focusables[newIndex] !== undefined) {
164 return focusables[newIndex];
166 if (loopSelection != true) {
170 return focusables[0] ?? null;
172 return focusables[focusables.length - 1] ?? null;