maidr 2.28.0 → 2.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/maidr.js CHANGED
@@ -11910,118 +11910,131 @@ class Control {
11910
11910
 
11911
11911
  class Goto {
11912
11912
  //locations = ['Max Value', 'Min Value', 'Mean', 'Median'];
11913
- locations = ['Max Value', 'Min Value'];
11913
+ static options = ['Max Value', 'Min Value'];
11914
11914
 
11915
11915
  constructor() {
11916
- this.popupOpen = false;
11917
- this.popupIndex = 0;
11918
- this.attachBootstrapModal();
11916
+ this.menuOpen = false;
11917
+ this.initMenu();
11919
11918
  this.attachEventListeners();
11920
11919
  }
11921
11920
 
11922
- attachBootstrapModal() {
11923
- // Create modal container
11924
- const modalHtml = `
11925
- <div class="modal fade hidden" id="goto-modal" tabindex="-1" aria-labelledby="navigationModalLabel">
11926
- <div class="modal-dialog modal-dialog-centered">
11927
- <div class="modal-content">
11928
- <div class="modal-header">
11929
- <h5 class="modal-title" id="navigationModalLabel">Navigate to Location</h5>
11930
- <button type="button" class="close" data-dismiss="modal" aria-label="Close">
11931
- <span aria-hidden="true">&times;</span>
11932
- </button>
11921
+ initMenu() {
11922
+ this.menu = document.createElement('div');
11923
+ this.menu.id = 'goto-menu';
11924
+ this.menu.style.display = 'none';
11925
+ this.menu.innerHTML = `
11926
+ <div class="menu-container">
11927
+ <input
11928
+ type="text"
11929
+ id="menu-search"
11930
+ placeholder="Search locations..."
11931
+ aria-label="Search locations to go to"
11932
+ />
11933
+ <ul id="menu-items">
11934
+ ${Goto.options
11935
+ .map((option) => `<li><button>${option}</button></li>`)
11936
+ .join('')}
11937
+ </ul>
11933
11938
  </div>
11934
- <div class="modal-body">
11935
- <ul class="list-group" id="goto-list">
11936
- ${this.locations
11937
- .map(
11938
- (location, index) => `
11939
- <li class="list-group-item ${index === 0 ? 'active' : ''}">
11940
- <button type="button" class="btn btn-link">
11941
- ${location}
11942
- </button>
11943
- </li>`
11944
- )
11945
- .join('')}
11946
- </ul>
11947
- </div>
11948
- </div>
11949
- </div>
11950
- </div>
11951
- <div id="goto_modal_backdrop" class="modal-backdrop hidden"></div>
11952
- `;
11939
+ `;
11953
11940
 
11954
- document.body.insertAdjacentHTML('beforeend', modalHtml);
11941
+ this.menuSearch = this.menu.querySelector('#menu-search');
11942
+ this.menuItems = Array.from(
11943
+ this.menu.querySelectorAll('#menu-items button')
11944
+ );
11955
11945
 
11956
- constants.gotoModal = document.getElementById('goto-modal');
11946
+ constants.gotoMenu = this.menu;
11947
+ document.body.appendChild(this.menu);
11957
11948
  }
11958
11949
 
11959
- openPopup() {
11960
- this.popupOpen = true;
11961
- this.popupIndex = 0;
11962
- constants.tabMovement = 0;
11963
-
11950
+ openMenu() {
11964
11951
  this.whereWasMyFocus = document.activeElement;
11965
- document.getElementById('goto-modal').classList.remove('hidden');
11966
- document.getElementById('goto_modal_backdrop').classList.remove('hidden');
11967
- document.querySelector('#goto-modal .close').focus();
11968
- this.updateSelection(this.popupIndex);
11952
+ constants.tabMovement = 0; // to prevent maidr from being destroyed as we leave the chart
11953
+
11954
+ this.menuOpen = true;
11955
+ this.menu.style.display = 'block';
11956
+ this.menuSearch.focus();
11969
11957
  }
11970
11958
 
11971
- closePopup() {
11972
- this.popupOpen = false;
11973
- // close
11974
- document.getElementById('goto-modal').classList.add('hidden');
11975
- document.getElementById('goto_modal_backdrop').classList.add('hidden');
11959
+ closeMenu() {
11960
+ this.menuOpen = false;
11961
+ this.menu.style.display = 'none';
11976
11962
  this.whereWasMyFocus.focus();
11977
11963
  this.whereWasMyFocus = null;
11978
11964
  }
11979
11965
 
11980
- updateSelection(index) {
11981
- const items = document.querySelectorAll('#goto-list button');
11982
- Array.from(items).forEach((item, idx) => {
11983
- item.classList.toggle('active', idx === index);
11966
+ filterItems(query) {
11967
+ const lowerCaseQuery = query.toLowerCase();
11968
+ this.menuItems.forEach((item) => {
11969
+ if (item.textContent.toLowerCase().includes(lowerCaseQuery)) {
11970
+ item.parentElement.style.display = 'block';
11971
+ } else {
11972
+ item.parentElement.style.display = 'none';
11973
+ }
11984
11974
  });
11985
11975
  }
11986
11976
 
11987
11977
  attachEventListeners() {
11978
+ this.menuSearch.addEventListener('input', (event) =>
11979
+ this.filterItems(event.target.value)
11980
+ );
11981
+
11988
11982
  // Open the modal when the user presses 'g'
11989
11983
  constants.events.push([
11990
11984
  [constants.chart, constants.brailleInput],
11991
- 'keydown',
11985
+ 'keyup',
11992
11986
  function (event) {
11993
11987
  if (event.key === 'g' && !goto.popupOpen) {
11994
- goto.openPopup();
11988
+ goto.openMenu();
11995
11989
  }
11996
11990
  },
11997
11991
  ]);
11998
- // arrow keys to navigate the list, enter to select, escape to close
11992
+ // arrow keys to navigate the list, escape to close
11999
11993
  constants.events.push([
12000
- constants.gotoModal,
11994
+ constants.gotoMenu,
12001
11995
  'keydown',
12002
11996
  function (event) {
12003
- if (goto.popupOpen) {
11997
+ if (goto.menuOpen) {
11998
+ const focusableElements = [
11999
+ goto.menuSearch,
12000
+ ...goto.menuItems.filter(
12001
+ (item) => item.parentElement.style.display !== 'none'
12002
+ ),
12003
+ ];
12004
+ const currentIndex = focusableElements.indexOf(
12005
+ document.activeElement
12006
+ );
12007
+
12004
12008
  if (event.key === 'ArrowDown') {
12005
12009
  event.preventDefault();
12006
- goto.popupIndex = (goto.popupIndex + 1) % goto.locations.length;
12007
- goto.updateSelection(goto.popupIndex);
12010
+ const nextIndex = (currentIndex + 1) % focusableElements.length;
12011
+ focusableElements[nextIndex]?.focus();
12008
12012
  } else if (event.key === 'ArrowUp') {
12009
12013
  event.preventDefault();
12010
- goto.popupIndex =
12011
- (goto.popupIndex - 1 + goto.locations.length) %
12012
- goto.locations.length;
12013
- goto.updateSelection(goto.popupIndex);
12014
- } else if (event.key === 'Enter') {
12015
- event.preventDefault();
12016
- const selectedLocation = goto.locations[goto.popupIndex];
12017
- goto.closePopup();
12018
- goto.goToLocation(selectedLocation);
12019
- } else if (event.key === 'Escape') {
12020
- goto.closePopup();
12014
+ const prevIndex =
12015
+ (currentIndex - 1 + focusableElements.length) %
12016
+ focusableElements.length;
12017
+ focusableElements[prevIndex]?.focus();
12018
+ } else {
12019
+ if (event.key === 'Escape') {
12020
+ goto.closeMenu();
12021
+ }
12021
12022
  }
12022
12023
  }
12023
12024
  },
12024
12025
  ]);
12026
+ // enter to select, which we register as a click event so it works with screen readers
12027
+ constants.events.push([
12028
+ constants.gotoMenu,
12029
+ 'click',
12030
+ function (event) {
12031
+ if (event.target.tagName === 'BUTTON') {
12032
+ let loc = event.target.textContent;
12033
+ goto.closeMenu();
12034
+ goto.goToLocation(loc);
12035
+ }
12036
+ },
12037
+ ]);
12025
12038
  }
12026
12039
 
12027
12040
  goToLocation(loc) {