menuFoundation.ts 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  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. // user may use input to be the trigger and bind some key event on it, so do not stoppropagation
  46. // handlePrevent(event);
  47. break;
  48. case 'Escape':
  49. this.handleEscape(menu);
  50. break;
  51. case 'ArrowUp':
  52. setFocusToPreviousMenuItem(this.menuItemNodes, curItem);
  53. handlePrevent(event);
  54. break;
  55. case 'ArrowDown':
  56. setFocusToNextMenuitem(this.menuItemNodes, curItem);
  57. handlePrevent(event);
  58. break;
  59. default:
  60. if (isPrintableCharacter(event.key)) {
  61. this.setFocusByFirstCharacter(curItem, event.key);
  62. // it can be an input on Dropdown, handlePrevent may affect the input of the component
  63. // handlePrevent(event);
  64. }
  65. break;
  66. }
  67. }
  68. }