Initial Drupal 11 with DDEV setup
This commit is contained in:
		
							
								
								
									
										265
									
								
								web/core/modules/navigation/js/arrow-navigation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								web/core/modules/navigation/js/arrow-navigation.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,265 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Keyboard navigation component.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
((Drupal, once, { focusable }) => {
 | 
			
		||||
  /**
 | 
			
		||||
   * Attaches the keyboard navigation functionality.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches the behavior to the `.admin-toolbar` element.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.keyboardNavigation = {
 | 
			
		||||
    attach: (context) => {
 | 
			
		||||
      once('keyboard-processed', '.admin-toolbar', context).forEach(
 | 
			
		||||
        (sidebar) => {
 | 
			
		||||
          const IS_RTL = document.documentElement.dir === 'rtl';
 | 
			
		||||
 | 
			
		||||
          const isInteractive = (element) =>
 | 
			
		||||
            element.getAttribute('aria-expanded');
 | 
			
		||||
 | 
			
		||||
          const getFocusableGroup = (element) =>
 | 
			
		||||
            element.closest('[class*="toolbar-menu--level-"]') ||
 | 
			
		||||
            element.closest('[data-toolbar-popover-wrapper]') ||
 | 
			
		||||
            element.closest('.admin-toolbar');
 | 
			
		||||
 | 
			
		||||
          const findFirstElementByChar = (focusableElements, targetChar) => {
 | 
			
		||||
            const elementWIthChar = Array.prototype.find.call(
 | 
			
		||||
              focusableElements,
 | 
			
		||||
              (element) => {
 | 
			
		||||
                const dataText = element.dataset.indexText;
 | 
			
		||||
                return dataText && dataText[0] === targetChar;
 | 
			
		||||
              },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return elementWIthChar;
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const checkChar = ({ key, target }) => {
 | 
			
		||||
            const currentGroup = getFocusableGroup(target);
 | 
			
		||||
            const foundElementWithIndexChar = findFirstElementByChar(
 | 
			
		||||
              focusable(currentGroup),
 | 
			
		||||
              key,
 | 
			
		||||
            );
 | 
			
		||||
            if (foundElementWithIndexChar) {
 | 
			
		||||
              foundElementWithIndexChar.focus();
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const focusFirstInGroup = (focusableElements) => {
 | 
			
		||||
            focusableElements[0].focus();
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const focusLastInGroup = (focusableElements) => {
 | 
			
		||||
            focusableElements[focusableElements.length - 1].focus();
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const focusNextInGroup = (focusableElements, element) => {
 | 
			
		||||
            const currentIndex = Array.prototype.indexOf.call(
 | 
			
		||||
              focusableElements,
 | 
			
		||||
              element,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (currentIndex === focusableElements.length - 1) {
 | 
			
		||||
              focusableElements[0].focus();
 | 
			
		||||
            } else {
 | 
			
		||||
              focusableElements[currentIndex + 1].focus();
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const focusPreviousInGroup = (focusableElements, element) => {
 | 
			
		||||
            const currentIndex = Array.prototype.indexOf.call(
 | 
			
		||||
              focusableElements,
 | 
			
		||||
              element,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (currentIndex === 0) {
 | 
			
		||||
              focusableElements[focusableElements.length - 1].focus();
 | 
			
		||||
            } else {
 | 
			
		||||
              focusableElements[currentIndex - 1].focus();
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const toggleMenu = (element, state) =>
 | 
			
		||||
            element.dispatchEvent(
 | 
			
		||||
              new CustomEvent('toolbar-menu-set-toggle', {
 | 
			
		||||
                bubbles: false,
 | 
			
		||||
                detail: {
 | 
			
		||||
                  state,
 | 
			
		||||
                },
 | 
			
		||||
              }),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
          const closePopover = (element) =>
 | 
			
		||||
            element.dispatchEvent(
 | 
			
		||||
              new CustomEvent('toolbar-popover-close', { bubbles: true }),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
          const openPopover = (element) =>
 | 
			
		||||
            element.dispatchEvent(
 | 
			
		||||
              new CustomEvent('toolbar-popover-open', { bubbles: true }),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
          const focusClosestPopoverTrigger = (element) => {
 | 
			
		||||
            element
 | 
			
		||||
              .closest('[data-toolbar-popover]')
 | 
			
		||||
              ?.querySelector('[data-toolbar-popover-control]')
 | 
			
		||||
              ?.focus();
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const focusFirstMenuElement = (element) => {
 | 
			
		||||
            const elements = focusable(
 | 
			
		||||
              element
 | 
			
		||||
                .closest('.toolbar-menu__item')
 | 
			
		||||
                ?.querySelector('.toolbar-menu'),
 | 
			
		||||
            );
 | 
			
		||||
            if (elements?.length) {
 | 
			
		||||
              elements[0].focus();
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const focusFirstPopoverElement = (element) => {
 | 
			
		||||
            // Zero is always popover trigger.
 | 
			
		||||
            // And Popover header can be not interactive.
 | 
			
		||||
            const elements = focusable(
 | 
			
		||||
              element.closest('[data-toolbar-popover]'),
 | 
			
		||||
            );
 | 
			
		||||
            if (elements?.length >= 1) {
 | 
			
		||||
              elements[1].focus();
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const focusLastPopoverElement = (element) => {
 | 
			
		||||
            const elements = focusable(
 | 
			
		||||
              element.closest('[data-toolbar-popover]'),
 | 
			
		||||
            );
 | 
			
		||||
            if (elements?.length > 0) {
 | 
			
		||||
              elements[elements.length - 1].focus();
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const closeNonInteractiveElement = (element) => {
 | 
			
		||||
            // If we are inside submenus.
 | 
			
		||||
            if (element.closest('[class*="toolbar-menu--level-"]')) {
 | 
			
		||||
              const trigger =
 | 
			
		||||
                element.closest('.toolbar-menu')?.previousElementSibling;
 | 
			
		||||
              toggleMenu(trigger, false);
 | 
			
		||||
              trigger.focus();
 | 
			
		||||
            } else {
 | 
			
		||||
              closePopover(element);
 | 
			
		||||
              focusClosestPopoverTrigger(element);
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const openInteractiveElement = (element) => {
 | 
			
		||||
            // If menu button.
 | 
			
		||||
            if (element.hasAttribute('data-toolbar-menu-trigger')) {
 | 
			
		||||
              toggleMenu(element, true);
 | 
			
		||||
              focusFirstMenuElement(element);
 | 
			
		||||
            }
 | 
			
		||||
            // If popover trigger.
 | 
			
		||||
            if (element.hasAttribute('data-toolbar-popover-control')) {
 | 
			
		||||
              openPopover(element);
 | 
			
		||||
              focusFirstPopoverElement(element);
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const closeInteractiveElement = (element) => {
 | 
			
		||||
            // If menu button.
 | 
			
		||||
            if (element.hasAttribute('data-toolbar-menu-trigger')) {
 | 
			
		||||
              if (element.getAttribute('aria-expanded') === 'false') {
 | 
			
		||||
                closeNonInteractiveElement(element);
 | 
			
		||||
              } else {
 | 
			
		||||
                toggleMenu(element, false);
 | 
			
		||||
                focusFirstMenuElement(element);
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            // If popover trigger.
 | 
			
		||||
            if (element.hasAttribute('data-toolbar-popover-control')) {
 | 
			
		||||
              openPopover(element);
 | 
			
		||||
              focusLastPopoverElement(element);
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const arrowsSideControl = ({ key, target }) => {
 | 
			
		||||
            if (
 | 
			
		||||
              (key === 'ArrowRight' && !IS_RTL) ||
 | 
			
		||||
              (key === 'ArrowLeft' && IS_RTL)
 | 
			
		||||
            ) {
 | 
			
		||||
              if (isInteractive(target)) {
 | 
			
		||||
                openInteractiveElement(target);
 | 
			
		||||
                // If also we want to care about expand button.
 | 
			
		||||
                if (
 | 
			
		||||
                  target.getAttribute('aria-controls') === 'admin-toolbar' &&
 | 
			
		||||
                  target.getAttribute('aria-expanded') === 'false'
 | 
			
		||||
                ) {
 | 
			
		||||
                  target.click();
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            } else if (
 | 
			
		||||
              (key === 'ArrowRight' && IS_RTL) ||
 | 
			
		||||
              (key === 'ArrowLeft' && !IS_RTL)
 | 
			
		||||
            ) {
 | 
			
		||||
              if (isInteractive(target)) {
 | 
			
		||||
                closeInteractiveElement(target);
 | 
			
		||||
 | 
			
		||||
                // If also we want to care about expand button.
 | 
			
		||||
                if (
 | 
			
		||||
                  target.getAttribute('aria-controls') === 'admin-toolbar' &&
 | 
			
		||||
                  target.getAttribute('aria-expanded') !== 'false'
 | 
			
		||||
                ) {
 | 
			
		||||
                  target.click();
 | 
			
		||||
                }
 | 
			
		||||
              } else {
 | 
			
		||||
                closeNonInteractiveElement(target);
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          const arrowsDirectionControl = ({ key, target }) => {
 | 
			
		||||
            const focusableElements = focusable(getFocusableGroup(target));
 | 
			
		||||
            if (key === 'ArrowUp') {
 | 
			
		||||
              focusPreviousInGroup(focusableElements, target);
 | 
			
		||||
            } else if (key === 'ArrowDown') {
 | 
			
		||||
              focusNextInGroup(focusableElements, target);
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          sidebar.addEventListener('keydown', (e) => {
 | 
			
		||||
            switch (e.key) {
 | 
			
		||||
              case 'Escape':
 | 
			
		||||
                closePopover(e.target);
 | 
			
		||||
                focusClosestPopoverTrigger(e.target);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
              case 'ArrowLeft':
 | 
			
		||||
              case 'ArrowRight':
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                arrowsSideControl(e);
 | 
			
		||||
                break;
 | 
			
		||||
              case 'ArrowDown':
 | 
			
		||||
              case 'ArrowUp':
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                arrowsDirectionControl(e);
 | 
			
		||||
                break;
 | 
			
		||||
              case 'Home':
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                focusFirstInGroup(getFocusableGroup(e.target));
 | 
			
		||||
                break;
 | 
			
		||||
              case 'End':
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                focusLastInGroup(getFocusableGroup(e.target));
 | 
			
		||||
                break;
 | 
			
		||||
              default:
 | 
			
		||||
                checkChar(e);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
})(Drupal, once, window.tabbable);
 | 
			
		||||
		Reference in New Issue
	
	Block a user