menuFoundation.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  2. import { handlePrevent, isPrintableCharacter, findIndexByCharacter, getAncestorNodeByRole, getMenuButton, setFocusToItem, setFocusToNextMenuitem, setFocusToPreviousMenuItem } from '../utils/a11y';
  3. export default class DropdownMenuFoundation extends BaseFoundation<Partial<DefaultAdapter>> {
  4. menuItemNodes: HTMLElement[] = null;
  5. firstChars: string[] = [];
  6. handleEscape(menu: Element): void {
  7. const trigger = this._adapter.getContext('trigger');
  8. if (trigger === 'custom'){
  9. const menuButton = menu && getMenuButton(document.querySelectorAll(`[data-popupid]`), menu.id);
  10. menuButton.focus();
  11. }
  12. }
  13. setFocusByFirstCharacter(curItem: any, char: string): void {
  14. const index = findIndexByCharacter(this.menuItemNodes, curItem, this.firstChars, char);
  15. if (index >= 0) {
  16. setFocusToItem(this.menuItemNodes, this.menuItemNodes[index]);
  17. }
  18. }
  19. onMenuKeydown(event: any): void {
  20. const menu = getAncestorNodeByRole(event.target, 'tooltip');
  21. if (!this.menuItemNodes){
  22. this.menuItemNodes = [...(event.target.parentNode).getElementsByTagName('li')].filter(item => item.ariaDisabled !== "true");
  23. }
  24. if (this.firstChars.length === 0){
  25. this.menuItemNodes.forEach((item: Element) => {
  26. // the menuItemNodes can be an component and not exit textContent
  27. this.firstChars.push(item.textContent.trim()[0]?.toLowerCase());
  28. });
  29. }
  30. // get the currently focused menu item
  31. const curItem = this.menuItemNodes.find(item => item.tabIndex === 0);
  32. switch (event.key) {
  33. case ' ':
  34. case 'Enter':
  35. event.target.click();
  36. // user may use input to be the trigger and bind some key event on it, so do not stoppropagation
  37. // handlePrevent(event);
  38. break;
  39. case 'Escape':
  40. this.handleEscape(menu);
  41. break;
  42. case 'ArrowUp':
  43. setFocusToPreviousMenuItem(this.menuItemNodes, curItem);
  44. handlePrevent(event);
  45. break;
  46. case 'ArrowDown':
  47. setFocusToNextMenuitem(this.menuItemNodes, curItem);
  48. handlePrevent(event);
  49. break;
  50. default:
  51. if (isPrintableCharacter(event.key)) {
  52. this.setFocusByFirstCharacter(curItem, event.key);
  53. // it can be an input on Dropdown, handlePrevent may affect the input of the component
  54. // handlePrevent(event);
  55. }
  56. break;
  57. }
  58. }
  59. }