PageSourceSearch

https://www.aragon.es/o/aragon-theme/js/aria/MenubarAction.js

js aragon.es collected 2026-09-24 08:28:24 UTC 9,274 bytes, 256 lines download raw bytes

1export function MenubarAction(aria) {
2  /*
3  *   This content is licensed according to the W3C Software License at
4  *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
5  *
6  *   File:   MenubarAction.js
7  *
8  *   Desc:   Menubar widget that implements ARIA Authoring Practices
9  */
10
11  /*
12  *   @constructor MenubarAction
13  *
14  *   @desc
15  *       Wrapper object for a menubar
16  *
17  *   @param domNode
18  *       The DOM element node that serves as the menubar container.
19  *       Each child element of domNode that represents a menubaritem
20  *       must be an A element.
21  */
22  (function () {
23    aria.MenubarAction = function (domNode) {
24      var msgPrefix = 'Menubar constructor argument domNode ';
25
26      // Check whether domNode is a DOM element
27      if (!domNode instanceof Element) {
28        throw new TypeError(msgPrefix + 'is not a DOM Element.');
29      }
30
31      // Check whether domNode has descendant elements
32      if (domNode.childElementCount === 0) {
33        throw new Error(msgPrefix + 'has no element children.');
34      }
35
36      // Check whether domNode's descendant elements contain A elements
37      var e = domNode.firstElementChild;
38      while (e) {
39        e = e.nextElementSibling;
40      }
41
42      this.domNode = domNode;
43
44      this.menubarItems = []; // see Menubar init method
45      this.firstChars = []; // see Menubar init method
46
47      this.firstItem = null; // see Menubar init method
48      this.lastItem = null; // see Menubar init method
49    };
50
51    /*
52    *   @method MenubarAction.prototype.init
53    *
54    *   @desc
55    *       Adds ARIA role to the menubar node
56    *       Traverse menubar children for A elements to configure each A element as an ARIA menuitem
57    *       and populate menuitems array. Initialize firstItem and lastItem properties.
58    */
59    aria.MenubarAction.prototype.init = function (actionManager) {
60      var menubarItem, menuElement, textContent, numItems;
61
62      this.actionManager = actionManager;
63
64      this.domNode.setAttribute('role', 'menubar');
65
66      this.domNode.addEventListener('focusin', this.handleFocusin.bind(this));
67      this.domNode.addEventListener('focusout', this.handleFocusout.bind(this));
68
69      // Traverse the element children of the menubar domNode: configure each with
70      // menuitem role behavior and store reference in menuitems array.
71      var e = this.domNode.firstElementChild;
72
73      while (e) {
74        menuElement = e.firstElementChild;
75
76        if (menuElement && menuElement.tagName === 'A') {
77          menubarItem = new aria.MenubarItemAction(menuElement, this);
78          menubarItem.init();
79          this.menubarItems.push(menubarItem);
80          textContent = menuElement.textContent.trim();
81          this.firstChars.push(textContent.substring(0, 1).toLowerCase());
82        }
83
84        e = e.nextElementSibling;
85      }
86
87      // Use populated menuitems array to initialize firstItem and lastItem.
88      numItems = this.menubarItems.length;
89      if (numItems > 0) {
90        this.firstItem = this.menubarItems[0];
91        this.lastItem = this.menubarItems[numItems - 1];
92      }
93      this.firstItem.domNode.tabIndex = 0;
94    };
95
96    /* FOCUS MANAGEMENT METHODS */
97
98    aria.MenubarAction.prototype.setFocusToItem = function (newItem, hover) {
99      var isOpen = false;
100      var hasFocus = this.domNode.contains(document.activeElement);
101      for (var i = 0; i < this.menubarItems.length; i++) {
102        var mbi = this.menubarItems[i];
103        isOpen = isOpen || (mbi.popupMenu && mbi.popupMenu.isOpen());
104        if (!hover || hasFocus) {
105          mbi.domNode.tabIndex = -1;
106        }
107        if (mbi.popupMenu) {
108          mbi.popupMenu.close();
109        }
110      }
111      if (!hover || hasFocus) {
112        newItem.domNode.focus();
113        newItem.domNode.tabIndex = 0;
114      }
115      if (isOpen && newItem.popupMenu) {
116        newItem.popupMenu.open();
117      }
118    };
119
120    aria.MenubarAction.prototype.setFocusToFirstItem = function () {
121      this.setFocusToItem(this.firstItem);
122    };
123    aria.MenubarAction.prototype.setFocusToLastItem = function () {
124      this.setFocusToItem(this.lastItem);
125    };
126
127    aria.MenubarAction.prototype.setFocusToPreviousItem = function (currentItem) {
128      var newItem, index;
129
130      if (currentItem === this.firstItem) {
131        newItem = this.lastItem;
132      }
133      else {
134        index = this.menubarItems.indexOf(currentItem);
135        newItem = this.menubarItems[ index - 1 ];
136      }
137
138      this.setFocusToItem(newItem);
139    };
140
141    aria.MenubarAction.prototype.setFocusToNextItem = function (currentItem) {
142      var newItem, index;
143
144      if (currentItem === this.lastItem) {
145        newItem = this.firstItem;
146      }
147      else {
148        index = this.menubarItems.indexOf(currentItem);
149        newItem = this.menubarItems[ index + 1 ];
150      }
151      this.setFocusToItem(newItem);
152    };
153
154    aria.MenubarAction.prototype.setFocusByFirstCharacter = function (currentItem, char) {
155      var start, index, char = char.toLowerCase();
156
157      // Get start index for search based on position of currentItem
158      start = this.menubarItems.indexOf(currentItem) + 1;
159      if (start === this.menubarItems.length) {
160        start = 0;
161      }
162
163      // Check remaining slots in the menu
164      index = this.getIndexFirstChars(start, char);
165
166      // If not found in remaining slots, check from beginning
167      if (index === -1) {
168        index = this.getIndexFirstChars(0, char);
169      }
170
171      // If match was found...
172      if (index > -1) {
173        this.menubarItems[index].domNode.focus();
174        this.menubarItems[index].domNode.tabIndex = 0;
175        currentItem.tabIndex = -1;
176      }
177    };
178
179    aria.MenubarAction.prototype.getIndexFirstChars = function (startIndex, char) {
180      for (var i = startIndex; i < this.firstChars.length; i++) {
181        if (char === this.firstChars[i]) {
182          return i;
183        }
184      }
185      return -1;
186    };
187
188    aria.MenubarAction.prototype.handleFocusin = function (event) {
189      // if the menubar or any of its menus has focus, add styling hook for hover
190      this.domNode.classList.add('focus');
191    };
192
193    aria.MenubarAction.prototype.handleFocusout = function (event) {
194      // if the next element to get focus is not in the menubar or its menus, then close menu
195      if (!this.domNode.contains(event.relatedTarget)) {
196        for (var i = 0; i < this.menubarItems.length; i++) {
197          var mbi = this.menubarItems[i];
198          if (mbi.popupMenu && mbi.popupMenu.isOpen()) {
199            mbi.popupMenu.close();
200          }
201        }
202      }
203      // remove styling hook for hover on menubar item
204      this.domNode.classList.remove('focus');
205    };
206
207    window.activateItemMenuBarAction = function (menuId, itemsIds) {
208      const menu = document.getElementById(menuId);
209      if (menu) {
210        const menuInstance = new aria.MenubarAction(menu);
211        menuInstance.activateElements(menuId, itemsIds);
212        return [menu, itemsIds];
213      } else {
214        console.log('There is no menu with this id in the document.');
215        return null;
216      }
217    };
218
219    aria.MenubarAction.prototype.activateElements = function (menuId, activateElements) {
220      const getAllElements = this.domNode.querySelectorAll(`#${menuId} ul > li `);
221      const filterElements = [...getAllElements].filter((element) =>
221 element.id !== '' && element.getAttribute('role') !== 'separator');
222      [...filterElements].forEach((element) => {
223        if(isActive(element)) {
224          element.setAttribute('aria-checked', 'true');
225          element.innerHTML = `<strong class="font-bold">${element.textContent}</strong>`;
226        } else {
227          const getElementParent = element.parentElement.getAttribute('role') === 'group'
228            ? element.parentElement.parentElement.parentElement.parentElement.querySelector('a')
229            : element.parentElement.parentElement.querySelector('a');
230
231          getElementParent.classList.remove('c-menubar__button--has-selection');
232          getElementParent.firstChild.innerHTML = `${getElementParent.textContent}`;
233          element.setAttribute('aria-checked', 'false');
234          element.innerHTML = `${element.textContent}`;
235        }
236      });
237      [...filterElements].forEach((element) => {
238        if(element.getAttribute("aria-checked") === "true"){
239          const getElementParent = element.parentElement.getAttribute('role') === 'group'
240            ? element.parentElement.parentElement.parentElement.parentElement.querySelector('a')
241            : element.parentElement.parentElement.querySelector('a');
242          getElementParent.classList.add('c-menubar__button--has-selection');
243          getElementParent.firstChild.innerHTML = `<strong class="font-bold">${getElementParent.textContent}</strong>`;
244        }
245      });
246
247      function isActive(element){
248        const { id } = element;
249        return typeof activateElements === "string"
250          ? id === activateElements
251          : activateElements.includes(id);
252      }
253    };
254  }());
255}
256

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.