menuFoundation.ts 3.1 KB

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