Файловый менеджер - Редактировать - /home/harasnat/www/learning/lib/amd/src/local/action_menu/subpanel.js
Назад
// 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/>. /** * Action menu subpanel JS controls. * * @module core/local/action_menu/subpanel * @copyright 2023 Mikel Martín <mikel@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import jQuery from 'jquery'; import {debounce} from 'core/utils'; import { isBehatSite, isExtraSmall, firstFocusableElement, lastFocusableElement, previousFocusableElement, nextFocusableElement, } from 'core/pagehelpers'; import Pending from 'core/pending'; import { hide, unhide, } from 'core/aria'; const Selectors = { mainMenu: '[role="menu"]', dropdownRight: '.dropdown-menu-right', subPanel: '.dropdown-subpanel', subPanelMenuItem: '.dropdown-subpanel > .dropdown-item', subPanelContent: '.dropdown-subpanel > .dropdown-menu', // Drawer selector. drawer: '[data-region="fixed-drawer"]', // Lateral blocks columns selectors. blockColumn: '.blockcolumn', columnLeft: '.columnleft', }; const Classes = { dropRight: 'dropright', dropLeft: 'dropleft', dropDown: 'dropdown', forceLeft: 'downleft', contentDisplayed: 'content-displayed', }; const BootstrapEvents = { hideDropdown: 'hidden.bs.dropdown', }; let initialized = false; /** * Initialize all delegated events into the page. */ const initPageEvents = () => { if (initialized) { return; } // Hide all subpanels when hidind a dropdown. // This is using JQuery because of BS4 events. JQuery won't be needed with BS5. jQuery(document).on(BootstrapEvents.hideDropdown, () => { document.querySelectorAll(`${Selectors.subPanelContent}.show`).forEach(visibleSubPanel => { const dropdownSubPanel = visibleSubPanel.closest(Selectors.subPanel); const subPanel = new SubPanel(dropdownSubPanel); subPanel.setVisibility(false); }); }); window.addEventListener('resize', debounce(updateAllPanelsPosition, 400)); initialized = true; }; /** * Update all the panels position. */ const updateAllPanelsPosition = () => { document.querySelectorAll(Selectors.subPanel).forEach(dropdown => { const subpanel = new SubPanel(dropdown); subpanel.updatePosition(); }); }; /** * Subpanel class. * @private */ class SubPanel { /** * Constructor. * @param {HTMLElement} element The element to initialize. */ constructor(element) { this.element = element; this.menuItem = element.querySelector(Selectors.subPanelMenuItem); this.panelContent = element.querySelector(Selectors.subPanelContent); /** * Enable preview when the menu item has focus. * * This is disabled when the user press ESC or shift+TAB to force closing * * @type {Boolean} * @private */ this.showPreviewOnFocus = true; } /** * Initialize the subpanel element. * * This method adds the event listeners to the subpanel and the position classes. */ init() { if (this.element.dataset.subPanelInitialized) { return; } this.updatePosition(); // Full element events. this.element.addEventListener('focusin', this._mainElementFocusInHandler.bind(this)); // Menu Item events. this.menuItem.addEventListener('click', this._menuItemClickHandler.bind(this)); this.menuItem.addEventListener('keydown', this._menuItemKeyHandler.bind(this)); if (!isBehatSite()) { // Behat in Chrome usually move the mouse over the page when trying clicking a subpanel element. // If the menu has more than one subpanel this could cause closing the subpanel by mistake. this.menuItem.addEventListener('mouseover', this._menuItemHoverHandler.bind(this)); this.menuItem.addEventListener('mouseout', this._menuItemHoverOutHandler.bind(this)); } // Subpanel content events. this.panelContent.addEventListener('keydown', this._panelContentKeyHandler.bind(this)); this.element.dataset.subPanelInitialized = true; } /** * Checks if the subpanel has enough space. * * In general there are two scenarios were the subpanel must be interacted differently: * - Extra small screens: The subpanel is displayed below the menu item. * - Drawer: The subpanel is displayed one of the drawers. * - Block columns: for classic based themes. * * @returns {Boolean} true if the subpanel should be displayed in small screens. */ _needSmallSpaceBehaviour() { return isExtraSmall() || this.element.closest(Selectors.drawer) !== null || this.element.closest(Selectors.blockColumn) !== null; } /** * Check if the subpanel should be displayed on the right. * * This is defined by the drop right boostrap class. However, if the menu is * displayed in a block column on the right, the subpanel should be forced * to the right. * * @returns {Boolean} true if the subpanel should be displayed on the right. */ _needDropdownRight() { if (this.element.closest(Selectors.columnLeft) !== null) { return false; } return this.element.closest(Selectors.dropdownRight) !== null; } /** * Main element focus in handler. */ _mainElementFocusInHandler() { if (this._needSmallSpaceBehaviour() || !this.showPreviewOnFocus) { // Preview is disabled when the user press ESC or shift+TAB to force closing // but if the continue navigating with keyboard the preview is enabled again. this.showPreviewOnFocus = true; return; } this.setVisibility(true); } /** * Menu item click handler. * @param {Event} event */ _menuItemClickHandler(event) { // Avoid dropdowns being closed after clicking a subemnu. // This won't be needed with BS5 (data-bs-auto-close handles it). event.stopPropagation(); event.preventDefault(); if (this._needSmallSpaceBehaviour()) { this.setVisibility(!this.getVisibility()); } } /** * Menu item hover handler. * @private */ _menuItemHoverHandler() { if (this._needSmallSpaceBehaviour()) { return; } this.setVisibility(true); } /** * Menu item hover out handler. * @private */ _menuItemHoverOutHandler() { if (this._needSmallSpaceBehaviour()) { return; } this._hideOtherSubPanels(); } /** * Menu item key handler. * @param {Event} event * @private */ _menuItemKeyHandler(event) { // In small sizes te down key will focus on the panel. if (event.key === 'ArrowUp' || (event.key === 'ArrowDown' && !this._needSmallSpaceBehaviour())) { this.setVisibility(false); return; } // Keys to move focus to the panel. let focusPanel = false; if (event.key === 'ArrowRight' || event.key === 'ArrowLeft' || (event.key === 'Tab' && !event.shiftKey)) { focusPanel = true; } if ((event.key === 'Enter' || event.key === ' ')) { focusPanel = true; } // In extra small screen the panel is shown below the item. if (event.key === 'ArrowDown' && this._needSmallSpaceBehaviour() && this.getVisibility()) { focusPanel = true; } if (focusPanel) { event.stopPropagation(); event.preventDefault(); this.setVisibility(true); this._focusPanelContent(); } } /** * Sub panel content key handler. * @param {Event} event * @private */ _panelContentKeyHandler(event) { // In extra small devices the panel is displayed under the menu item // so the arrow up/down switch between subpanel and the menu item. const canLoop = !this._needSmallSpaceBehaviour(); let isBrowsingSubPanel = false; let newFocus = null; if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') { newFocus = this.menuItem; } // Acording to WCAG Esc and Tab are similar to arrow navigation but they // force the subpanel to be closed. if (event.key === 'Escape' || (event.key === 'Tab' && event.shiftKey)) { newFocus = this.menuItem; this.setVisibility(false); this.showPreviewOnFocus = false; } if (event.key === 'ArrowUp') { newFocus = previousFocusableElement(this.panelContent, canLoop); isBrowsingSubPanel = true; } if (event.key === 'ArrowDown') { newFocus = nextFocusableElement(this.panelContent, canLoop); isBrowsingSubPanel = true; } if (event.key === 'Home') { newFocus = firstFocusableElement(this.panelContent); isBrowsingSubPanel = true; } if (event.key === 'End') { newFocus = lastFocusableElement(this.panelContent); isBrowsingSubPanel = true; } // If the user cannot loop and arrive to the start/end of the subpanel // we focus on the menu item. if (newFocus === null && isBrowsingSubPanel && !canLoop) { newFocus = this.menuItem; } if (newFocus !== null) { event.stopPropagation(); event.preventDefault(); newFocus.focus(); } } /** * Focus on the first focusable element of the subpanel. * @private */ _focusPanelContent() { const pendingPromise = new Pending('core/action_menu/subpanel:focuscontent'); // Some Bootstrap events are triggered after the click event. // To prevent this from affecting the focus we wait a bit. setTimeout(() => { const firstFocusable = firstFocusableElement(this.panelContent); if (firstFocusable) { firstFocusable.focus(); } pendingPromise.resolve(); }, 100); } /** * Set the visibility of a subpanel. * @param {Boolean} visible true if the subpanel should be visible. */ setVisibility(visible) { if (visible) { this._hideOtherSubPanels(); } // Aria hidden/unhidden can alter the focus, we only want to do it when needed. if (!visible && this.getVisibility) { hide(this.panelContent); } if (visible && !this.getVisibility) { unhide(this.panelContent); } this.menuItem.setAttribute('aria-expanded', visible ? 'true' : 'false'); this.panelContent.classList.toggle('show', visible); this.element.classList.toggle(Classes.contentDisplayed, visible); } /** * Hide all other subpanels in the parent menu. * @private */ _hideOtherSubPanels() { const dropdown = this.element.closest(Selectors.mainMenu); dropdown.querySelectorAll(`${Selectors.subPanelContent}.show`).forEach(visibleSubPanel => { const dropdownSubPanel = visibleSubPanel.closest(Selectors.subPanel); if (dropdownSubPanel === this.element) { return; } const subPanel = new SubPanel(dropdownSubPanel); subPanel.setVisibility(false); }); } /** * Get the visibility of a subpanel. * @returns {Boolean} true if the subpanel is visible. */ getVisibility() { return this.menuItem.getAttribute('aria-expanded') === 'true'; } /** * Update the panels position depending on the screen size and panel position. */ updatePosition() { const dropdownRight = this._needDropdownRight(); if (this._needSmallSpaceBehaviour()) { this.element.classList.remove(Classes.dropRight); this.element.classList.remove(Classes.dropLeft); this.element.classList.add(Classes.dropDown); this.element.classList.toggle(Classes.forceLeft, dropdownRight); return; } this.element.classList.remove(Classes.dropDown); this.element.classList.remove(Classes.forceLeft); this.element.classList.toggle(Classes.dropRight, !dropdownRight); this.element.classList.toggle(Classes.dropLeft, dropdownRight); } } /** * Initialise module for given report * * @method * @param {string} selector The query selector to init. */ export const init = (selector) => { initPageEvents(); const subMenu = document.querySelector(selector); if (!subMenu) { throw new Error(`Sub panel element not found: ${selector}`); } const subPanel = new SubPanel(subMenu); subPanel.init(); };
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка