azure-devops-ui
Version:
React components for building web UI in Azure DevOps
1 lines • 18.9 kB
JavaScript
import"../../CommonImports";import"../../Core/core.css";import"./DropdownList.css";import"./List.css";import"./ListDropIndicator.css";import*as React from"react";import*as Utils_Accessibility from"../../Core/Util/Accessibility";import{ObservableArray,ObservableLike}from"../../Core/Observable";import{FocusWithin}from"../../FocusWithin";import{FocusZone,FocusZoneContext,FocusZoneDirection}from"../../FocusZone";import{IntersectionContext}from"../../Intersection";import{getDefaultLinkProps}from"../../Link";import{Observer,UncheckedObserver}from"../../Observer";import*as Resources from"../../Resources.Widgets";import{css,eventTargetContainsNode,getSafeId,KeyCode}from"../../Util";import{EventDispatch}from"../../Utilities/Dispatch";import{getDragInProgress}from"../../Utilities/DragDrop";import{getTabIndex}from"../../Utilities/Focus";class DropdownList extends React.Component{constructor(e){super(e),this.bodyElement=React.createRef(),this.listElement=React.createRef(),this.spacerElements={},this.scrollToIndex=-1,this.scrollToOptions=void 0,this.selectOnFocus=!0,this.focusIndex=-1,this.pivotIndex=-1,this.onVirtualizeKeyDown=e=>{this.state.virtualize&&e.ctrlKey&&e.altKey&&"v"===e.key&&(e=this.props.itemProvider.length,this.setState({virtualize:!1,lastMaterialized:e-1,lastRendered:e-1,firstMaterialized:0,firstRendered:0}),Utils_Accessibility.announce(Resources.VirtualizationDisabled))},this.onBlur=()=>{this.focusIndex=-1},this.onClick=e=>{var t,i;this.onDispatch(e),e.defaultPrevented||e.altKey&&this.props.selectableText||this.listElement.current&&({cellElement:i,rowIndex:t}=cellFromEvent(e),i&&eventTargetContainsNode(e,["A"],i)||(i=ObservableLike.getValue(this.state.rows[t]),0<=t&&i&&(i={data:i,index:t},this.props.selectRowOnClick&&this.processSelectionEvent(e,i),this.props.singleClickActivation)&&this.rowActivated(e,i)))},this.onDispatch=e=>{this.state.eventDispatch.dispatchEvent(e)},this.onDoubleClick=e=>{var t,i;this.onDispatch(e),e.defaultPrevented||this.props.singleClickActivation||(t=cellFromEvent(e).rowIndex,i=ObservableLike.getValue(this.state.rows[t]),0<=t&&i&&this.rowActivated(e,{data:i,index:t}))},this.onFocusBody=e=>{var t,i;this.selectOnFocus&&(t=this.props["selection"],t&&!t.selectOnFocus||0<=(t=this.focusIndex)&&(i=ObservableLike.getValue(this.state.rows[t]))&&this.processSelectionEvent(e,{data:i,index:t}),this.selectOnFocus=!1)},this.onFocusItem=(e,t)=>{var i=this["focusIndex"];i!==e&&(0<=i?delete this.state.renderedRows[i]:delete this.state.renderedRows[this.getInitialTabbableRow()],delete this.state.renderedRows[e],this.focusIndex=e,i=ObservableLike.getValue(this.state.rows[e]))&&this.rowFocused(t,{data:i,index:e})},this.onKeyDown=t=>{if(this.onDispatch(t),!t.defaultPrevented){var e=t.target.nodeName;if("INPUT"!==e&&"TEXTAREA"!==e){const i=this["focusIndex"];var e=ObservableLike.getValue(this.state.rows[i]);e&&(t.which===KeyCode.enter?0<=i&&!eventTargetContainsNode(t,["A"])&&this.rowActivated(t,{data:e,index:i}):t.which===KeyCode.space?(this.processSelectionEvent(t,{data:e,index:i}),t.preventDefault()):t.which===KeyCode.upArrow||t.which===KeyCode.downArrow?(e=this.props["selection"],e&&(!e.selectOnFocus||!t.shiftKey&&t.ctrlKey)||(t.persist(),window.setTimeout(()=>{var e;this.focusIndex!=i&&(e=ObservableLike.getValue(this.state.rows[this.focusIndex]))&&this.processSelectionEvent(t,{data:e,index:this.focusIndex})},0))):t.which===KeyCode.pageDown?(e=this.getStats(),this.focusRow(Math.min(i+(e.lastRendered-e.firstRendered),this.state.rowCount-1),1),t.preventDefault()):t.which===KeyCode.pageUp?(e=this.getStats(),this.focusRow(Math.max(i-(e.lastRendered-e.firstRendered),0),-1),t.preventDefault()):t.which===KeyCode.home?(this.focusRow(0,1),t.preventDefault()):t.which===KeyCode.end&&(this.focusRow(this.state.rowCount-1,-1),t.preventDefault()))}}},this.onIntersect=e=>{if(this.state.virtualize){var l=this.context.root["scrollTop"],{firstRendered:c,firstMaterialized:h,lastRendered:d,lastMaterialized:u,rowCount:p,rowProportion:m}=this.state;let n=this.state["rowHeight"];if((l===this.state.scrollTop||!e.length)&&this.listElement.current&&this.bodyElement.current){var v=this.bodyElement.current.children;if(0===n){if(0<v.length){let t=0,i=0;for(let e=0;e<v.length;e++){var g=this.bodyElement.current.children[e].getBoundingClientRect().height;0<g&&(t+=g,i++)}0<i&&(n=t/i)}if(0===n)return;if(-1!==this.scrollToIndex)return void this.setState({firstMaterialized:Math.max(0,this.scrollToIndex-this.state.pageSize),lastMaterialized:this.scrollToIndex+Math.min(this.props.initialPageCount*this.state.pageSize,p-1),rowHeight:n})}var f=this.context.root.getBoundingClientRect();let t,i,s=h,o=u,r=o,a=s;for(let e=0;e<v.length;e++){var w=v[e],b=getAttributeAsNumber(w,"data-row-index"),x=w.getBoundingClientRect();h<=b&&b<=u&&(x.bottom<f.top-this.state.pageSize*(m*n)?s++:x.top>f.bottom+this.state.pageSize*(m*n)&&o--,b===h&&(t=w),b===u)&&(i=w),-1<b&&x.top<f.bottom&&x.bottom>f.top&&(a=Math.max(a,b),r=Math.min(r,b))}m<1?s>o||r===s||a===o?a>=p-1?s=Math.ceil(o-(f.height/n+this.state.pageSize)):(e=l-(this.listElement.current.offsetTop-this.context.root.offsetTop),s=Math.max(0,Math.min(p-1,Math.floor(e/(m*n)))-this.state.pageSize),o=Math.min(p-1,s+Math.ceil(f.height/(m*n)+this.state.pageSize-1)),a=-1,r=-1):(s=Math.min(s,r-this.state.pageSize),o=Math.max(o,a+this.state.pageSize-1),a=-1,r=-1):s>o?(e=l-(this.listElement.current.offsetTop-this.context.root.offsetTop),s=Math.max(0,Math.min(p-1,Math.floor(e/n))-this.state.pageSize),o=Math.min(p-1,s+Math.ceil(f.height/n+this.state.pageSize-1)),a=-1,r=-1):(s===h&&t&&0<(e=t.getBoundingClientRect().top-f.top)&&(s-=Math.ceil(e/n)),o===u&&i&&(e=i.getBoundingClientRect(),0<(e=f.bottom-e.bottom))&&(o+=Math.ceil(e/n))),s=Math.max(s,0),o=Math.min(o,p-1),s===h&&r===c&&o===u&&a===d&&n===this.state.rowHeight&&l===this.state.scrollTop||this.setState({firstMaterialized:s,firstRendered:r,lastMaterialized:o,lastRendered:a,rowHeight:n,scrollTop:l})}}},this.onPointerDownBody=e=>{this.selectOnFocus=!1},this.getInitialTabbableRow=()=>{var{defaultTabbableRow:e,itemProvider:t,selection:i}=this.props;if(e)return e;if(i)for(let e=0;e<t.length;e++)if(i.selectable(e))return e;return 0},this.getHeight=(e,t)=>{let i=0;var s=this.props.rowHeights||[],o=t?this.state.rowCount-e:0,r=t?this.state.rowCount:e;for(let e=o;e<r;e++)i+=s[e]||this.state.rowHeight;return i};var t=e.itemProvider.length,i=e.pageSize;if(this.state={columnCount:1,eventDispatch:e.eventDispatch||new EventDispatch,firstMaterialized:0,firstRendered:0,itemProvider:e.itemProvider,lastMaterialized:this.props.virtualize?Math.min(e.initialPageCount*i,t-1):t-1,lastRendered:this.props.virtualize?Math.min(e.initialPageCount*i,t-1):t-1,overlays:new ObservableArray,pageSize:i,renderedRows:{},rowCount:t,rowHeight:e.rowHeight||0,rowProportion:e.rowHeight&&e.maxHeight?Math.min(1,e.maxHeight/(e.rowHeight*t)):1,rows:{},scrollTop:0,virtualize:!!e.virtualize},e.behaviors)for(const s of e.behaviors)s.initialize&&s.initialize(e,this,this.state.eventDispatch)}static getDerivedStateFromProps(e,t){var i=e.itemProvider.length;let s=t.firstMaterialized,o=t.lastMaterialized;i!==t.rowCount&&(s=Math.max(0,Math.min(t.firstMaterialized,i)),o=t.virtualize?Math.max(s,Math.min(t.lastMaterialized+(t.lastMaterialized===t.rowCount-1||t.lastMaterialized===t.rowCount?e.pageSize:0),i-1)):i-1);i={firstMaterialized:s,itemProvider:e.itemProvider,lastMaterialized:o,pageSize:e.pageSize,rowCount:i,rowProportion:Math.min(1,(e.maxHeight||1e5)/(t.rowHeight*(i-(o-s))))};return e.itemProvider===t.itemProvider&&e.columnCount===t.columnCount||(i.columnCount=e.columnCount,i.renderedRows={},i.rows={}),i}render(){const{ariaRowOffset:i,className:s,focuszoneProps:o,id:r,maxWidth:a,minWidth:n,width:l}=this.props;var{firstMaterialized:t,lastMaterialized:c,rowCount:e,rowProportion:h}=this.state,d=this["focusIndex"];const u=this.props.role||(this.props.selection?"listbox":"list"),p="table"===u||"grid"===u||"treegrid"===u,m=[];let v=0,g=t,f=Math.max(0,e-c-1),w=0,b=Number.MAX_SAFE_INTEGER,x=0;if(-1!==d&&(b=Math.max(0,d-3),x=Math.min(e,d+3),b<t?(x=Math.min(x,t-1),v=b,g=t-x-1):x>c&&(b=Math.max(b,c+1),f=b-c-1,w=Math.max(0,e-x-1))),h<1&&(g+=Math.min(this.state.pageSize,t)),m.push(this.renderSpacer("st1",v)),b<t)for(let e=b;e<=x;e++)m.push(this.renderRow(e));m.push(this.renderSpacer("st2",g));for(let e=t;e<=c;e++)m.push(this.renderRow(e));if(m.push(this.renderSpacer("sb2",f,{countFromBottom:!0,estimateRowHeight:!this.props.rowHeights})),x>c)for(let e=b;e<=x;e++)m.push(this.renderRow(e));return m.push(this.renderSpacer("sb1",w,{countFromBottom:!0,estimateRowHeight:!this.props.rowHeights})),React.createElement(UncheckedObserver,{itemProvider:{filter:(e,t)=>{this.props.selection&&this.props.selection.onItemsChanged(e,t);var i,t={renderedRows:{},rows:{}};return e.removedItems&&this.focusIndex>=e.index&&e.index+e.removedItems.length>=this.focusIndex&&(this.focusIndex=-1),-1!==this.state.rowCount&&(i=(e.addedItems?e.addedItems.length:0)-(e.removedItems?e.removedItems.length:0))&&(t.rowCount=this.state.rowCount+i,t.firstMaterialized=Math.max(0,Math.min(this.state.firstMaterialized,t.rowCount-1)),t.lastMaterialized=this.state.virtualize?Math.max(t.firstMaterialized,Math.min(this.state.lastMaterialized+(e.index>=this.state.firstMaterialized&&e.index<=this.state.lastMaterialized+1?Math.min(this.state.pageSize,i):0),t.rowCount-1)):t.rowCount-1),this.setState(t),!1},observableValue:this.props.itemProvider}},React.createElement(FocusWithin,{onBlur:this.onBlur},e=>{let t=React.createElement("ul",{"aria-colcount":p?this.props.ariaColumnCount||this.props.columnCount:void 0,"aria-label":this.props.ariaLabel||Resources.DropdownSelection,"aria-rowcount":p?this.state.itemProvider.length+i:void 0,className:css(s,"bolt-list body-m relative",this.props.showScroll?void 0:"scroll-hidden"),id:getSafeId(r),onBlur:e.onBlur,onClick:this.onClick,onContextMenu:this.onDispatch,onDoubleClick:this.onDoubleClick,onDragEnd:this.onDispatch,onDragEnter:this.onDispatch,onDragExit:this.onDispatch,onDragOver:this.onDispatch,onDragStart:this.onDispatch,onDrop:this.onDispatch,onFocus:e.onFocus,onKeyDown:this.onKeyDown,onKeyUp:this.onDispatch,onPointerDown:this.onDispatch,ref:this.listElement,role:u,style:{maxWidth:a,minWidth:n,width:l},tabIndex:0},this.props.renderHeader&&this.props.renderHeader(),React.createElement("div",{className:css("relative","listbox"),onFocus:this.onFocusBody,onPointerDown:this.onPointerDownBody,ref:this.bodyElement,role:"listbox"===u||"list"===u||"menu"===u||"tree"===u?"presentation":void 0},this.renderOverlay(this.listElement),m));return t=o?React.createElement(FocusZone,Object.assign({},o,{skipHiddenCheck:!0}),t):t}))}componentDidMount(){this.context.register(this.onIntersect),this.props.virtualize&&document.addEventListener("keydown",this.onVirtualizeKeyDown)}componentDidUpdate(){var{scrollToIndex:t,onScrollComplete:e}=this;if(-1!==t&&this.state.rowHeight){var i=this.bodyElement.current,{firstMaterialized:s,lastMaterialized:o}=this.state;if(s<=t&&t<=o&&i)for(let e=0;e<i.children.length;e++){var r=i.children[e];if(cellFromElement(r).rowIndex===t){r.scrollIntoView(this.scrollToOptions);break}}this.onScrollComplete=void 0,this.scrollToIndex=-1,this.scrollToOptions=void 0,e&&e(t)}}componentWillUnmount(){this.context.unregister(this.onIntersect),this.props.virtualize&&document.removeEventListener("keydown",this.onVirtualizeKeyDown)}addOverlay(t,e,i,s=0,o){var r=this.state["overlays"],a=r.value.findIndex(e=>e.id===t),i={render:i,id:t,rowIndex:e,zIndex:s+1,columnIndex:o};0<=a?r.change(a,i):r.push(i)}removeOverlay(t){var e=this.state["overlays"],i=e.value.findIndex(e=>e.id===t);0<=i&&e.splice(i,1)}getFocusIndex(){return this.focusIndex}getStats(){return{firstMaterialized:this.state.firstMaterialized,firstRendered:this.state.firstRendered,lastMaterialized:this.state.lastMaterialized,lastRendered:this.state.lastRendered}}scrollIntoView(t,i,e){var{firstMaterialized:s,lastMaterialized:o,pageSize:r,rowCount:a,rowHeight:n,rowProportion:l}=this.state;if(0<=t&&t<this.state.rowCount){var c=this.bodyElement.current;if(s<=t&&t<=o&&c){for(let e=0;e<c.children.length;e++){var h=c.children[e];if(cellFromElement(h).rowIndex===t){h.scrollIntoView(i);break}}e&&e(t)}else{this.onScrollComplete&&this.onScrollComplete(-1),this.onScrollComplete=e,this.scrollToIndex=t,this.scrollToOptions=i;s=l<1?r:0;n&&this.setState({firstMaterialized:Math.max(0,t-s),lastMaterialized:Math.min(a-1,t+s)})}}}focusRow(s,o=1){return new Promise(i=>{this.scrollIntoView(s,{block:"center"},e=>{var t;e===s&&this.bodyElement.current&&(t=this.bodyElement.current.querySelector("[data-row-index='"+e+"']"))&&(t.getAttribute("tabindex")?t.focus():(t=Math.min(this.state.rowCount-1,Math.max(0,e+o)))!==e?this.focusRow(t,o):t!==this.focusIndex&&this.focusRow(t,-o)),i()})})}processSelectionEvent(e,t){var i,s,{selection:o,enforceSingleSelect:r}=this.props;o&&!o.selectable(t.index)||(o&&(i=t["index"],r=!r&&o.multiSelect,0<=this.pivotIndex&&e.shiftKey&&r?o.select(Math.min(this.pivotIndex,i),Math.abs(this.pivotIndex-i)+1,e.ctrlKey||e.metaKey,r):(s=e.which===KeyCode.space,(e.ctrlKey||e.metaKey||o.alwaysMerge||s)&&r?o.toggle(i,!0,r):o.select(i,1,!1,r)),e.shiftKey||(this.pivotIndex=i)),this.rowSelected(e,t))}renderLoadingRow(e,t){return React.createElement(DropdownListItem,{className:"bolt-list-row-loading",details:t,index:e},React.createElement("div",{className:"shimmer shimmer-line",style:{width:80*Math.random()+20+"%"}}," "))}renderOverlay(o){const{firstMaterialized:r,lastMaterialized:a,overlays:e}=this.state;return React.createElement(Observer,{overlays:e},e=>{const s=this.bodyElement.current;return 0<e.overlays.length&&s?React.createElement("div",{className:"bolt-list-overlay-container absolute"},e.overlays.map(e=>{var t,i;return(-1===e.rowIndex||!(e.rowIndex<r||e.rowIndex>a)||getDragInProgress())&&(t=o.current&&o.current.querySelector("[data-row-index='"+e.rowIndex+"']"),t=this.props.overlay?null==t?void 0:t.querySelector(this.props.overlay):t,i=null==(i=o.current)?void 0:i.querySelector("[data-column-index='"+e.columnIndex+"']"),t)?i?React.createElement("div",{className:"bolt-list-overlay flex-row absolute",id:getSafeId(e.id),key:e.id,style:{height:t.offsetHeight,width:i.offsetWidth,top:t.getBoundingClientRect().top-s.getBoundingClientRect().top,left:i.getBoundingClientRect().left-s.getBoundingClientRect().left,zIndex:10*e.zIndex}},e.render({rowElement:i})):React.createElement("div",{className:"bolt-list-overlay flex-row absolute",id:getSafeId(e.id),key:e.id,style:{height:t.offsetHeight,top:t.getBoundingClientRect().top-s.getBoundingClientRect().top,zIndex:10*e.zIndex}},e.render({rowElement:t})):null})):null})}renderRow(n){var t=this.props["itemProvider"],{renderedRows:i,rows:s}=this.state;let o=i[n];if(!o){let a=s[n];if(!(a=a||(t.getItem?t.getItem(n):t.value[n])))return null;s[n]=a;i=this.props["selection"];let e;i&&(e={observableValue:i,filter:e=>{for(const t of e)if(n>=t.beginIndex&&n<=t.endIndex)return!0;return!1}}),o=React.createElement(UncheckedObserver,{item:a,key:n,selection:e},e=>{var{selectableText:t,renderRow:i,renderLoadingRow:s}=this.props,o=this["focusIndex"],o=0<=o?o:this.getInitialTabbableRow(),r=ObservableLike.getValue(a),t={selectableText:t,ariaBusy:!e.item,ariaRowOffset:this.props.ariaRowOffset+1,data:r,eventDispatch:this.state.eventDispatch,excludeTabStop:this.props.excludeTabStop||o!==n,listProps:this.props,onFocusItem:this.onFocusItem,singleClickActivation:this.props.onActivate&&this.props.singleClickActivation};return e.item?i(n,e.item,t):s?s(n,t):this.renderLoadingRow(n,t)}),this.state.renderedRows[n]=o}return o}renderSpacer(i,e,t){var s=null!=t&&t.estimateRowHeight||null==(s=this.props.rowHeights)||!s.length?e*this.state.rowHeight*this.state.rowProportion:this.getHeight(e,null==t?void 0:t.countFromBottom);return React.createElement("div",{className:"bolt-list-row-spacer invisible",key:i,ref:e=>{var t=this.spacerElements[i];e?t!==e&&(t&&this.context.unobserve(e),this.context.observe(e),this.spacerElements[i]=e):t&&(this.context.unobserve(t),delete this.spacerElements[i])},role:"presentation"},React.createElement("div",{className:"bolt-list-cell-spacer invisible",style:{height:s+"px"}}))}rowActivated(e,t){this.state.eventDispatch.dispatchEvent(e,t,"activate"),this.props.onActivate&&this.props.onActivate(e,t)}rowSelected(e,t){this.state.eventDispatch.dispatchEvent(e,t,"select"),this.props.onSelect&&this.props.onSelect(e,t)}rowFocused(e,t){this.state.eventDispatch.dispatchEvent(e,t,"focus"),this.props.onFocus&&this.props.onFocus(e,t)}}function DropdownListItem(i){const{children:s,details:o,index:r,linkProps:a,itemId:n,tabIndex:l}=i,{selectableText:c,ariaBusy:h,ariaDescribedBy:d,ariaLabel:u,ariaPosInSet:p,ariaSetSize:m,excludeFocusZone:v}=o,{selection:g,singleClickActivation:f}=o.listProps;return React.createElement(FocusWithin,{onFocus:function(e){i.details.onFocusItem(i.index,e)}},t=>React.createElement(FocusZoneContext.Consumer,null,e=>{e={"aria-busy":h,"aria-describedby":d,"aria-label":u,"aria-posinset":void 0===p?r+1:null===p?void 0:p,"aria-selected":g&&g.selected(r),"aria-setsize":void 0===m?i.details.listProps.itemProvider.length:null===m?void 0:m,className:css(i.className,"bolt-list-row",0===r&&"first-row",a&&"bolt-link",g&&g.selected(r)&&"selected",t.hasFocus&&"focused",f&&"single-click-activation",c&&"selectable-text"),"data-focuszone":v||g&&!g.selectable(r)?void 0:e.focuszoneId,"data-row-index":r,"data-itemid":n,tabIndex:null!==l&&void 0!==l?l:getTabIndex(o),onBlur:t.onBlur,onFocus:t.onFocus,role:g?"option":"listitem"};return React.createElement(FocusZone,{direction:FocusZoneDirection.Horizontal},a?React.createElement("a",Object.assign({},getDefaultLinkProps(a),e),React.createElement("div",{className:"bolt-list-cell","data-column-index":0},React.createElement("div",{className:"bolt-list-cell-content flex-row"},s))):React.createElement("li",Object.assign({},e),React.createElement("span",{className:"bolt-list-cell","data-column-index":0},React.createElement("div",{className:"bolt-list-cell-content flex-row"},s))))}))}function getAttributeAsNumber(e,t){e=e.getAttribute(t);return e?parseInt(e,10):-1}function cellFromElement(e){var t;let i=-1,s=-1,o=null;for(;e;){if(-1!==(t=getAttributeAsNumber(e,"data-column-index"))&&(i=t,o=e),-1!==(t=getAttributeAsNumber(e,"data-row-index"))){s=t;break}if(e.classList.contains("bolt-list")){e=null;break}e=e.parentElement}return{cellElement:o,cellIndex:i,rowElement:e,rowIndex:s}}function cellFromEvent(e){return cellFromElement(e.target)}DropdownList.contextType=IntersectionContext,DropdownList.defaultProps={ariaRowOffset:0,columnCount:1,focuszoneProps:{direction:FocusZoneDirection.Vertical},initialPageCount:3,maxHeight:1e5,pageSize:10,singleClickActivation:!1,selectRowOnClick:!0,virtualize:!0};export{DropdownList,DropdownListItem};