azure-devops-ui
Version:
React components for building web UI in Azure DevOps
1 lines • 13.1 kB
JavaScript
import"../../CommonImports";import"../../Core/core.css";import"./Menu.css";import"./MenuButton.css";import*as React from"react";import{ObservableCollection,ObservableLike,ObservableValue}from"../../Core/Observable";import{Callout}from"../../Callout";import{Checkbox}from"../../Checkbox";import{FocusWithin}from"../../FocusWithin";import{FocusZone,FocusZoneContext,FocusZoneDirection,FocusZoneKeyStroke}from"../../FocusZone";import{Icon,IconSize}from"../../Icon";import{List}from"../../List";import{MouseWithin}from"../../MouseWithin";import{Observer}from"../../Observer";import{css,getSafeId,getSafeIdSelector,isArrowKey,KeyCode,preventDefault,setFocusVisible}from"../../Util";import{Location}from"../../Utilities/Position";import{ArrayItemProvider}from"../../Utilities/Provider";import{MenuCell,MenuItemType}from"./Menu.Props";function groupMenuItems(e,t){const n={};let o=0;var s=[],t=t||[];if(0<t.length){o=t.reduce((e,t)=>t.rank||e<0?t.rank:e,0)||0;for(const r of t)n[r.key]={key:r.key,rank:void 0===r.rank?++o:r.rank,items:[]}}for(const a of e)a.groupKey?n[a.groupKey]?n[a.groupKey].items.push(a):n[a.groupKey]={key:a.groupKey,rank:++o,items:[a]}:s.push(a);t=Object.keys(n).map(e=>n[e]);t.sort((e,t)=>(e.rank||Number.MAX_VALUE)-(t.rank||Number.MAX_VALUE)),t.push({key:"ungrouped",rank:++o,items:s}),t.forEach(e=>{for(var t=e.items;0<t.length&&t[0].itemType===MenuItemType.Divider;)t.shift();for(;0<t.length&&t[t.length-1].itemType===MenuItemType.Divider;)t.pop()}),e=[];let i=!0;for(const l of t)0!==l.items.length&&(i||e.push({id:"divider_"+l.key,itemType:MenuItemType.Divider}),i=i&&!1,e=e.concat(l.items));return e}class MenuItemProvider extends ArrayItemProvider{constructor(s,i){super(s),this.positions=[];let r=[];if(s){let e=!1,t=!1,n=MenuItemType.Divider,o;for(const a of s)if(!a.hidden){if(a.itemType===MenuItemType.Divider){if(a.itemType===n)continue;o=a}else o&&(r.push(o),o=void 0),r.push(a);n=a.itemType||MenuItemType.Normal,e=!!a.groupKey||e,t=0<=a.rank||t}t&&r.sort((e,t)=>{return(e.rank||Number.MAX_VALUE)-(t.rank||Number.MAX_VALUE)}),e&&(r=groupMenuItems(r,i))}this.items=r}getCount(){if(void 0===this.count){this.count=0;for(const e of this.items)e.itemType===MenuItemType.Divider||e.itemType===MenuItemType.Header?this.positions.push(-1):this.positions.push(++this.count)}return this.count}getItem(e){return this.items[e]}getPosition(e){return this.positions.length||this.getCount(),this.positions[e]}}class Menu extends React.Component{constructor(e){super(e),this.containerElement=React.createRef(),this.expandItem=(t,n)=>{if((t=t||-1===this.state.expandedIndex.value?t:this.itemProvider.getItem(this.state.expandedIndex.value))&&t.subMenuProps)for(let e=0;e<this.itemProvider.length;e++)if(t===this.itemProvider.getItem(e)){this.state.expandedIndex.value=n?e:-1;break}},this.focus=()=>{this.containerElement.current&&this.containerElement.current.focus()},this.getParent=()=>this.props.parentMenu,this.onActivate=(e,t)=>{this.props.onActivate&&this.props.onActivate(e,t)},this.renderMenuItem=(e,t,n)=>{var n=n["onFocusItem"],o={expandedIndex:this.state.expandedIndex,menu:this,menuProps:this.props,onActivate:this.onActivate,onFocusItem:n,position:this.itemProvider.getPosition(e),setSize:this.itemProvider.getCount()};if(t.renderMenuItem)return t.renderMenuItem(e,t,o);var s=t.id;switch(t.itemType){case MenuItemType.Divider:return MenuDivider(e,t);case MenuItemType.Header:return MenuHeader(e,t);default:return React.createElement(MenuItem,{key:s,index:e,menuItem:t,details:o})}},this.state={expandedIndex:new ObservableValue(-1)}}render(){return React.createElement(Observer,{items:this.props.items},e=>(this.itemProvider=new MenuItemProvider(e.items,this.props.groups),this.renderList()))}renderList(){return React.createElement("div",{className:"bolt-menu-container no-outline",ref:this.containerElement,tabIndex:-1},0<this.itemProvider.length&&React.createElement(React.Fragment,null,React.createElement("div",{className:"bolt-menu-spacer",onMouseDown:preventDefault}),React.createElement(List,{ariaLabel:this.props.ariaLabel,className:css(this.props.className,"bolt-menu"),columnCount:7,focuszoneProps:null,id:this.props.id,itemProvider:this.itemProvider,renderRow:this.renderMenuItem,role:"menu",virtualize:!1}),React.createElement("div",{className:"bolt-menu-spacer",onMouseDown:preventDefault})))}}function MenuDivider(e,t){return React.createElement("tr",{"aria-hidden":"true",className:css(t.className,"bolt-menuitem-row bolt-list-row bolt-menuitem-divider"),key:t.id||"divider-"+e,onMouseDown:preventDefault},React.createElement("td",{className:"bolt-menuitem-cell bolt-list-cell"}),React.createElement("td",{className:"bolt-menuitem-cell bolt-list-cell bolt-menuitem-divider-column",colSpan:5},React.createElement("div",{className:"bolt-menuitem-divider-content"})),React.createElement("td",{className:"bolt-menuitem-cell bolt-list-cell"}))}function MenuHeader(e,t){return React.createElement("tr",{className:css(t.className,"bolt-menuitem-row bolt-list-row bolt-menuitem-header"),key:t.id||"header-"+e,onMouseDown:preventDefault,role:"separator"},React.createElement("td",{className:"bolt-menuitem-cell bolt-list-cell"}),React.createElement("td",{className:"bolt-menuitem-cell bolt-list-cell",colSpan:3},React.createElement("div",{className:"bolt-menuitem-cell-content bolt-menuitem-cell-text"},t.text)),React.createElement("td",{className:"bolt-menuitem-cell bolt-list-cell",colSpan:3}))}class MenuItem extends React.Component{constructor(){super(...arguments),this.localKeyStroke=!1,this.expanded=!1,this.element=React.createRef(),this.handleClick=t=>{var n=this.props.menuItem;if(n.disabled)t.preventDefault();else if(!this.expanded){let e=!1;(e=n.onActivate?n.onActivate(n,t):e)||(n.href||t.preventDefault(),n.subMenuProps?this.props.details.menu.expandItem(n,!0):(n.href||void 0===n.checked||n.readonly)&&this.props.details.onActivate(n,t))}},this.onClick=e=>{e.defaultPrevented||this.handleClick(e)},this.onDismissSubMenu=e=>{!e&&this.element.current&&this.props.details.menu.expandItem(this.props.menuItem,!1)},this.onExpandedChange=e=>this.expanded&&e!==this.props.index||!this.expanded&&e===this.props.index,this.onFocus=e=>{this.element.current===document.activeElement&&this.props.details.onFocusItem(this.props.index,e)},this.onKeyDown=e=>{var t;this.localKeyStroke=!0,e.defaultPrevented||(t=this.props.menuItem,e.which===KeyCode.tab||e.which===KeyCode.space?e.preventDefault():e.which===KeyCode.rightArrow&&t.subMenuProps&&(e.preventDefault(),this.props.details.menu.expandItem(t,!0)))},this.onKeyUp=e=>{!this.localKeyStroke||e.defaultPrevented||e.which!==KeyCode.enter&&e.which!==KeyCode.space||this.handleClick(e)},this.onMouseDown=e=>{e.defaultPrevented||!this.props.menuItem.disabled&&this.props.details.expandedIndex.value!==this.props.index||e.preventDefault()},this.onMouseEnter=()=>{this.props.menuItem.disabled||(this.element.current&&this.element.current.focus(),this.props.details.menu.expandItem(this.props.menuItem,!0),setFocusVisible(!1))},this.onMouseLeave=()=>{this.onDismissSubMenu(!1)}}render(){const{index:e,menuItem:s,details:i}=this.props,{menu:r,position:a,setSize:l}=i,{ariaLabel:c,checked:t,className:m,disabled:u,href:p,iconProps:d,readonly:h,secondaryText:v,subMenuProps:b,target:f}=s;let{id:M,rel:x,text:y}=s;const I=p?"div":"td",E=p?"a":"tr";return p&&f&&!x&&(x="noopener"),React.createElement(Observer,{checked:t,expandedIndex:{observableValue:this.props.details.expandedIndex,filter:this.onExpandedChange}},o=>(this.expanded=o.expandedIndex===e,React.createElement(MouseWithin,{enterDelay:250,leaveDelay:250,onMouseEnter:this.onMouseEnter,onMouseLeave:this.onMouseLeave},n=>React.createElement(FocusZoneContext.Consumer,null,t=>React.createElement(FocusWithin,{onFocus:this.onFocus},e=>React.createElement(FocusZone,{direction:FocusZoneDirection.Horizontal},React.createElement(E,{"aria-label":c,"aria-checked":!0===o.checked||void 0,"aria-controls":this.expanded&&b?getSafeId(b.id):void 0,"aria-disabled":u?"true":void 0,"aria-expanded":b?this.expanded:void 0,"aria-haspopup":!!b||void 0,"aria-posinset":a,"aria-setsize":l,className:css(m,"bolt-menuitem-row bolt-list-row bolt-menuitem-row-normal cursor-pointer",u&&"disabled",this.expanded&&"expanded",e.hasFocus&&"focused"),"data-focuszone":u?void 0:t.focuszoneId,href:p,id:getSafeId(M),role:void 0!==o.checked?"menuitemcheckbox":"menuitem",onBlur:e.onBlur,onClick:this.onClick,onFocus:e.onFocus,onKeyDown:this.onKeyDown,onKeyUp:this.onKeyUp,onMouseDown:this.onMouseDown,onMouseEnter:n.onMouseEnter,onMouseLeave:n.onMouseLeave,ref:this.element,rel:x,tabIndex:u?void 0:-1,target:f},React.createElement(I,{className:"bolt-menuitem-cell bolt-list-cell"},React.createElement("div",{className:"bolt-menuitem-cell-content flex-row"})),React.createElement(I,{className:"bolt-menuitem-cell bolt-list-cell"},void 0!==o.checked&&(s.renderMenuCell&&s.renderMenuCell(MenuCell.State,s,i)||React.createElement("div",{className:"bolt-menuitem-cell-content bolt-menuitem-cell-state flex-row"},!0===h?Icon({className:css(!o.checked&&"invisible"),iconName:"CheckMark"}):React.createElement(Checkbox,{checked:o.checked,disabled:u,excludeFocusZone:!0,excludeTabStop:!0,onChange:this.onClick})))),React.createElement(I,{className:"bolt-menuitem-cell bolt-list-cell"},s.renderMenuCell&&s.renderMenuCell(MenuCell.Icon,s,i)||d&&React.createElement("div",{className:"bolt-menuitem-cell-content bolt-menuitem-cell-icon flex-row"},Icon(d))),React.createElement(I,{className:"bolt-menuitem-cell bolt-list-cell"},s.renderMenuCell&&s.renderMenuCell(MenuCell.PrimaryText,s,i)||React.createElement("div",{id:getSafeId(M+"-text"),className:"bolt-menuitem-cell-content bolt-menuitem-cell-text flex-row"},y?React.createElement(React.Fragment,null," ",y," "):React.createElement("div",null," "))),React.createElement(I,{className:"bolt-menuitem-cell bolt-list-cell"},s.renderMenuCell&&s.renderMenuCell(MenuCell.SecondaryText,s,i)||v&&React.createElement("div",{className:"bolt-menuitem-cell-content bolt-menuitem-cell-secondary flex-row"},v)),React.createElement(I,{className:"bolt-menuitem-cell bolt-list-cell"},s.renderMenuCell&&s.renderMenuCell(MenuCell.Action,s,i)||b&&React.createElement("div",{className:"bolt-menuitem-cell-content bolt-menuitem-cell-submenu flex-row"},Icon({iconName:"ChevronRightMed",size:IconSize.small}),this.expanded&&this.element.current&&React.createElement(ContextualMenu,{anchorElement:this.element.current,anchorOffset:{horizontal:0,vertical:-8},anchorOrigin:{horizontal:Location.end,vertical:Location.start},subMenu:!0,menuOrigin:{horizontal:Location.start,vertical:Location.start},menuProps:b,onActivate:this.props.details.onActivate,onDismiss:this.onDismissSubMenu,parentMenu:r}))),React.createElement(I,{className:"bolt-menuitem-cell bolt-list-cell"},React.createElement("div",{className:"bolt-menuitem-cell-content flex-row"})))))))))}}class ContextualMenu extends React.Component{constructor(){super(...arguments),this.calloutRef=React.createRef(),this.onDismiss=()=>{this.props.onDismiss&&this.props.onDismiss(!1)},this.onKeyDown=e=>{e.defaultPrevented||(e.which===KeyCode.escape||e.which===KeyCode.tab||e.which===KeyCode.leftArrow&&this.props.subMenu)&&(e.preventDefault(),this.props.onDismiss)&&this.props.onDismiss(!1)},this.onActivate=(e,t)=>{this.props.menuProps.onActivate&&this.props.menuProps.onActivate(e,t),this.props.onActivate&&this.props.onActivate(e,t),this.props.onDismiss&&this.props.onDismiss(!0)},this.preprocessKeyStroke=e=>isArrowKey(e)?FocusZoneKeyStroke.IgnoreParents:FocusZoneKeyStroke.IgnoreNone}render(){let t=".bolt-menu-container",e=ObservableLike.getValue(this.props.menuProps.items);var n=(e=this.props.menuProps.items instanceof ObservableCollection?e.slice():e).sort((e,t)=>(e.rank||Number.MAX_VALUE)-(t.rank||Number.MAX_VALUE));for(let e=0;e<n.length;e++)if(n[e].itemType===MenuItemType.Normal||void 0===n[e].itemType){var o=n[e].id;if(o&&!n[e].disabled){t=getSafeIdSelector(o);break}}return React.createElement(Observer,{menuItems:{observableValue:this.props.menuProps.items}},()=>React.createElement(Callout,{ref:this.calloutRef,anchorElement:this.props.anchorElement,anchorOffset:this.props.anchorOffset,anchorOrigin:this.props.anchorOrigin,anchorPoint:this.props.anchorPoint,blurDismiss:!0,calloutOrigin:this.props.menuOrigin,className:this.props.className,contentClassName:css("bolt-contextual-menu flex-column custom-scrollbar depth-8",this.props.subMenu&&"bolt-contextual-submenu"),contentShadow:!0,onDismiss:this.onDismiss,fixedLayout:this.props.fixedLayout,focuszoneProps:{defaultActiveElement:t,direction:FocusZoneDirection.Vertical,focusOnMount:!0,preprocessKeyStroke:this.preprocessKeyStroke,circularNavigation:!0},id:this.props.menuProps.id+"-callout",portalProps:{className:"bolt-menu-portal"},updateLayout:!0},React.createElement("div",{className:"bolt-contextualmenu-container",onKeyDown:this.onKeyDown},React.createElement(Menu,Object.assign({},this.props.menuProps,{onActivate:this.onActivate,parentMenu:this.props.parentMenu})))))}}export{groupMenuItems,Menu,MenuDivider,MenuHeader,MenuItem,ContextualMenu};