SoftContextMenu.js 9.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. export default class SoftContextMenu{constructor(items,itemSelectedCallback,parentMenu){this._items=items;this._itemSelectedCallback=itemSelectedCallback;this._parentMenu=parentMenu;this._highlightedMenuItemElement=null;}
  2. show(document,anchorBox){if(!this._items.length){return;}
  3. this._document=document;this._glassPane=new UI.GlassPane();this._glassPane.setPointerEventsBehavior(this._parentMenu?UI.GlassPane.PointerEventsBehavior.PierceGlassPane:UI.GlassPane.PointerEventsBehavior.BlockedByGlassPane);this._glassPane.registerRequiredCSS('ui/softContextMenu.css');this._glassPane.setContentAnchorBox(anchorBox);this._glassPane.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent);this._glassPane.setMarginBehavior(UI.GlassPane.MarginBehavior.NoMargin);this._glassPane.setAnchorBehavior(this._parentMenu?UI.GlassPane.AnchorBehavior.PreferRight:UI.GlassPane.AnchorBehavior.PreferBottom);this._contextMenuElement=this._glassPane.contentElement.createChild('div','soft-context-menu');this._contextMenuElement.tabIndex=-1;UI.ARIAUtils.markAsMenu(this._contextMenuElement);this._contextMenuElement.addEventListener('mouseup',e=>e.consume(),false);this._contextMenuElement.addEventListener('keydown',this._menuKeyDown.bind(this),false);for(let i=0;i<this._items.length;++i){this._contextMenuElement.appendChild(this._createMenuItem(this._items[i]));}
  4. this._glassPane.show(document);this._focusRestorer=new UI.ElementFocusRestorer(this._contextMenuElement);if(!this._parentMenu){this._hideOnUserGesture=event=>{let subMenu=this._subMenu;while(subMenu){if(subMenu._contextMenuElement===event.path[0]){return;}
  5. subMenu=subMenu._subMenu;}
  6. this.discard();event.consume(true);};this._document.body.addEventListener('mousedown',this._hideOnUserGesture,false);this._document.defaultView.addEventListener('resize',this._hideOnUserGesture,false);}}
  7. discard(){if(this._subMenu){this._subMenu.discard();}
  8. if(this._focusRestorer){this._focusRestorer.restore();}
  9. if(this._glassPane){this._glassPane.hide();delete this._glassPane;if(this._hideOnUserGesture){this._document.body.removeEventListener('mousedown',this._hideOnUserGesture,false);this._document.defaultView.removeEventListener('resize',this._hideOnUserGesture,false);delete this._hideOnUserGesture;}}
  10. if(this._parentMenu){delete this._parentMenu._subMenu;}}
  11. _createMenuItem(item){if(item.type==='separator'){return this._createSeparator();}
  12. if(item.type==='subMenu'){return this._createSubMenu(item);}
  13. const menuItemElement=createElementWithClass('div','soft-context-menu-item');menuItemElement.tabIndex=-1;UI.ARIAUtils.markAsMenuItem(menuItemElement);const checkMarkElement=UI.Icon.create('smallicon-checkmark','checkmark');menuItemElement.appendChild(checkMarkElement);if(!item.checked){checkMarkElement.style.opacity='0';}
  14. if(item.element){const wrapper=menuItemElement.createChild('div','soft-context-menu-custom-item');wrapper.appendChild(item.element);menuItemElement._customElement=item.element;return menuItemElement;}
  15. if(!item.enabled){menuItemElement.classList.add('soft-context-menu-disabled');}
  16. menuItemElement.createTextChild(item.label);menuItemElement.createChild('span','soft-context-menu-shortcut').textContent=item.shortcut;menuItemElement.addEventListener('mousedown',this._menuItemMouseDown.bind(this),false);menuItemElement.addEventListener('mouseup',this._menuItemMouseUp.bind(this),false);menuItemElement.addEventListener('mouseover',this._menuItemMouseOver.bind(this),false);menuItemElement.addEventListener('mouseleave',this._menuItemMouseLeave.bind(this),false);menuItemElement._actionId=item.id;let accessibleName=item.label;if(item.type==='checkbox'){const checkedState=item.checked?ls`checked`:ls`unchecked`;if(item.shortcut){accessibleName=ls`${item.label}, ${item.shortcut}, ${checkedState}`;}else{accessibleName=ls`${item.label}, ${checkedState}`;}}else if(item.shortcut){accessibleName=ls`${item.label}, ${item.shortcut}`;}
  17. UI.ARIAUtils.setAccessibleName(menuItemElement,accessibleName);return menuItemElement;}
  18. _createSubMenu(item){const menuItemElement=createElementWithClass('div','soft-context-menu-item');menuItemElement._subItems=item.subItems;menuItemElement.tabIndex=-1;UI.ARIAUtils.markAsMenuItemSubMenu(menuItemElement);const checkMarkElement=UI.Icon.create('smallicon-checkmark','soft-context-menu-item-checkmark');checkMarkElement.classList.add('checkmark');menuItemElement.appendChild(checkMarkElement);checkMarkElement.style.opacity='0';menuItemElement.createTextChild(item.label);if(Host.isMac()&&!UI.themeSupport.hasTheme()){const subMenuArrowElement=menuItemElement.createChild('span','soft-context-menu-item-submenu-arrow');subMenuArrowElement.textContent='\u25B6';}else{const subMenuArrowElement=UI.Icon.create('smallicon-triangle-right','soft-context-menu-item-submenu-arrow');menuItemElement.appendChild(subMenuArrowElement);}
  19. menuItemElement.addEventListener('mousedown',this._menuItemMouseDown.bind(this),false);menuItemElement.addEventListener('mouseup',this._menuItemMouseUp.bind(this),false);menuItemElement.addEventListener('mouseover',this._menuItemMouseOver.bind(this),false);menuItemElement.addEventListener('mouseleave',this._menuItemMouseLeave.bind(this),false);return menuItemElement;}
  20. _createSeparator(){const separatorElement=createElementWithClass('div','soft-context-menu-separator');separatorElement._isSeparator=true;separatorElement.createChild('div','separator-line');return separatorElement;}
  21. _menuItemMouseDown(event){event.consume(true);}
  22. _menuItemMouseUp(event){this._triggerAction(event.target,event);event.consume();}
  23. _root(){let root=this;while(root._parentMenu){root=root._parentMenu;}
  24. return root;}
  25. _triggerAction(menuItemElement,event){if(!menuItemElement._subItems){this._root().discard();event.consume(true);if(typeof menuItemElement._actionId!=='undefined'){this._itemSelectedCallback(menuItemElement._actionId);delete menuItemElement._actionId;}
  26. return;}
  27. this._showSubMenu(menuItemElement);event.consume();}
  28. _showSubMenu(menuItemElement){if(menuItemElement._subMenuTimer){clearTimeout(menuItemElement._subMenuTimer);delete menuItemElement._subMenuTimer;}
  29. if(this._subMenu){return;}
  30. this._subMenu=new SoftContextMenu(menuItemElement._subItems,this._itemSelectedCallback,this);const anchorBox=menuItemElement.boxInWindow();anchorBox.y-=5;anchorBox.x+=3;anchorBox.width-=6;anchorBox.height+=10;this._subMenu.show(this._document,anchorBox);}
  31. _menuItemMouseOver(event){this._highlightMenuItem(event.target,true);}
  32. _menuItemMouseLeave(event){if(!this._subMenu||!event.relatedTarget){this._highlightMenuItem(null,true);return;}
  33. const relatedTarget=event.relatedTarget;if(relatedTarget===this._contextMenuElement){this._highlightMenuItem(null,true);}}
  34. _highlightMenuItem(menuItemElement,scheduleSubMenu){if(this._highlightedMenuItemElement===menuItemElement){return;}
  35. if(this._subMenu){this._subMenu.discard();}
  36. if(this._highlightedMenuItemElement){this._highlightedMenuItemElement.classList.remove('force-white-icons');this._highlightedMenuItemElement.classList.remove('soft-context-menu-item-mouse-over');if(this._highlightedMenuItemElement._subItems&&this._highlightedMenuItemElement._subMenuTimer){clearTimeout(this._highlightedMenuItemElement._subMenuTimer);delete this._highlightedMenuItemElement._subMenuTimer;}}
  37. this._highlightedMenuItemElement=menuItemElement;if(this._highlightedMenuItemElement){if(UI.themeSupport.hasTheme()||Host.isMac()){this._highlightedMenuItemElement.classList.add('force-white-icons');}
  38. this._highlightedMenuItemElement.classList.add('soft-context-menu-item-mouse-over');if(this._highlightedMenuItemElement._customElement){this._highlightedMenuItemElement._customElement.focus();}else{this._highlightedMenuItemElement.focus();}
  39. if(scheduleSubMenu&&this._highlightedMenuItemElement._subItems&&!this._highlightedMenuItemElement._subMenuTimer){this._highlightedMenuItemElement._subMenuTimer=setTimeout(this._showSubMenu.bind(this,this._highlightedMenuItemElement),150);}}}
  40. _highlightPrevious(){let menuItemElement=this._highlightedMenuItemElement?this._highlightedMenuItemElement.previousSibling:this._contextMenuElement.lastChild;while(menuItemElement&&(menuItemElement._isSeparator||menuItemElement.classList.contains('soft-context-menu-disabled'))){menuItemElement=menuItemElement.previousSibling;}
  41. if(menuItemElement){this._highlightMenuItem(menuItemElement,false);}}
  42. _highlightNext(){let menuItemElement=this._highlightedMenuItemElement?this._highlightedMenuItemElement.nextSibling:this._contextMenuElement.firstChild;while(menuItemElement&&(menuItemElement._isSeparator||menuItemElement.classList.contains('soft-context-menu-disabled'))){menuItemElement=menuItemElement.nextSibling;}
  43. if(menuItemElement){this._highlightMenuItem(menuItemElement,false);}}
  44. _menuKeyDown(event){switch(event.key){case'ArrowUp':this._highlightPrevious();break;case'ArrowDown':this._highlightNext();break;case'ArrowLeft':if(this._parentMenu){this._highlightMenuItem(null,false);this.discard();}
  45. break;case'ArrowRight':if(!this._highlightedMenuItemElement){break;}
  46. if(this._highlightedMenuItemElement._subItems){this._showSubMenu(this._highlightedMenuItemElement);this._subMenu._highlightNext();}
  47. break;case'Escape':this.discard();break;case'Enter':if(!isEnterKey(event)){return;}
  48. case' ':if(!this._highlightedMenuItemElement||this._highlightedMenuItemElement._customElement){return;}
  49. this._triggerAction(this._highlightedMenuItemElement,event);if(this._highlightedMenuItemElement._subItems){this._subMenu._highlightNext();}
  50. break;}
  51. event.consume(true);}}
  52. self.UI=self.UI||{};UI=UI||{};UI.SoftContextMenu=SoftContextMenu;