lib/amd/src/pagehelpers.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  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.
  7. //
  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.
  12. //
  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/>.
  15. /**
  16. * Page utility helpers.
  17. *
  18. * @module core/pagehelpers
  19. * @copyright 2023 Ferran Recio <ferran@moodle.com>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. /**
  23. * Maximum sizes for breakpoints. This needs to correspond with Bootstrap
  24. * Breakpoints
  25. *
  26. * @private
  27. */
  28. const Sizes = {
  29. small: 576,
  30. medium: 991,
  31. large: 1400
  32. };
  33. const Selectors = {
  34. focusable: 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
  35. };
  36. const Classes = {
  37. behatSite: 'behat-site',
  38. };
  39. /**
  40. * Check fi the current page is a Behat site.
  41. * @returns {boolean} true if the current page is a Behat site.
  42. */
  43. export const isBehatSite = () => {
  44. return document.body.classList.contains(Classes.behatSite);
  45. };
  46. /**
  47. * Get the current body width.
  48. * @returns {number} the current body width.
  49. */
  50. export const getCurrentWidth = () => {
  51. const DomRect = document.body.getBoundingClientRect();
  52. return DomRect.x + DomRect.width;
  53. };
  54. /**
  55. * Check if the user uses an extra small size browser.
  56. *
  57. * @returns {boolean} true if the body is smaller than sizes.small max size.
  58. */
  59. export const isExtraSmall = () => {
  60. const browserWidth = getCurrentWidth();
  61. return browserWidth < Sizes.small;
  62. };
  63. /**
  64. * Check if the user uses a small size browser.
  65. *
  66. * @returns {boolean} true if the body is smaller than sizes.medium max size.
  67. */
  68. export const isSmall = () => {
  69. const browserWidth = getCurrentWidth();
  70. return browserWidth < Sizes.medium;
  71. };
  72. /**
  73. * Check if the user uses a large size browser.
  74. *
  75. * @returns {boolean} true if the body is smaller than sizes.large max size.
  76. */
  77. export const isLarge = () => {
  78. const browserWidth = getCurrentWidth();
  79. return browserWidth >= Sizes.large;
  80. };
  81. /**
  82. * Get the first focusable element inside a container.
  83. * @param {HTMLElement} [container] Container to search in. Defaults to document.
  84. * @returns {HTMLElement|null}
  85. */
  86. export const firstFocusableElement = (container) => {
  87. const containerElement = container || document;
  88. return containerElement.querySelector(Selectors.focusable);
  89. };
  90. /**
  91. * Get the last focusable element inside a container.
  92. * @param {HTMLElement} [container] Container to search in. Defaults to document.
  93. * @returns {HTMLElement|null}
  94. */
  95. export const lastFocusableElement = (container) => {
  96. const containerElement = container || document;
  97. const focusableElements = containerElement.querySelectorAll(Selectors.focusable);
  98. return focusableElements[focusableElements.length - 1] ?? null;
  99. };
  100. /**
  101. * Get all focusable elements inside a container.
  102. * @param {HTMLElement} [container] Container to search in. Defaults to document.
  103. * @returns {HTMLElement[]}
  104. */
  105. export const focusableElements = (container) => {
  106. const containerElement = container || document;
  107. return containerElement.querySelectorAll(Selectors.focusable);
  108. };
  109. /**
  110. * Get the previous focusable element in a container.
  111. * It uses the current focused element to know where to start the search.
  112. * @param {HTMLElement} [container] Container to search in. Defaults to document.
  113. * @param {Boolean} [loopSelection] Whether to loop selection or not. Default to false.
  114. * @returns {HTMLElement|null}
  115. */
  116. export const previousFocusableElement = (container, loopSelection) => {
  117. return getRelativeFocusableElement(container, loopSelection, -1);
  118. };
  119. /**
  120. * Get the next focusable element in a container.
  121. * It uses the current focused element to know where to start the search.
  122. * @param {HTMLElement} [container] Container to search in. Defaults to document.
  123. * @param {Boolean} [loopSelection] Whether to loop selection or not. Default to false.
  124. * @returns {HTMLElement|null}
  125. */
  126. export const nextFocusableElement = (container, loopSelection) => {
  127. return getRelativeFocusableElement(container, loopSelection, 1);
  128. };
  129. /**
  130. * Internal function to get the next or previous focusable element.
  131. * @param {HTMLElement} [container] Container to search in. Defaults to document.
  132. * @param {Boolean} [loopSelection] Whether to loop selection or not.
  133. * @param {Number} [direction] Direction to search in. 1 for next, -1 for previous.
  134. * @returns {HTMLElement|null}
  135. * @private
  136. */
  137. const getRelativeFocusableElement = (container, loopSelection, direction) => {
  138. const focusedElement = document.activeElement;
  139. const focusables = [...focusableElements(container)];
  140. const focusedIndex = focusables.indexOf(focusedElement);
  141. if (focusedIndex === -1) {
  142. return null;
  143. }
  144. const newIndex = focusedIndex + direction;
  145. if (focusables[newIndex] !== undefined) {
  146. return focusables[newIndex];
  147. }
  148. if (loopSelection != true) {
  149. return null;
  150. }
  151. if (direction > 0) {
  152. return focusables[0] ?? null;
  153. }
  154. return focusables[focusables.length - 1] ?? null;
  155. };