UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

1 lines 13.1 kB
import"../../CommonImports";import"../../Core/core.css";import"./DropdownList.css";import"./List.css";import"./ListDropIndicator.css";import*as React from"react";import{ObservableLike,ObservableValue}from"../../Core/Observable";import{FocusWithin}from"../../FocusWithin";import{FocusZone,FocusZoneContext,FocusZoneDirection}from"../../FocusZone";import{IntersectionContext}from"../../Intersection";import{Observer,UncheckedObserver}from"../../Observer";import{css,eventTargetContainsNode,getSafeId,KeyCode}from"../../Util";import{EventDispatch}from"../../Utilities/Dispatch";class FixedHeightList extends React.Component{constructor(t){super(t),this.intersectionElements={},this.bodyElement=React.createRef(),this.listElement=React.createRef(),this.scrollToIndex=-1,this.scrollToOptions=void 0,this.selectOnFocus=!0,this.focusIndex=new ObservableValue(-1),this.pivotIndex=-1,this.onBlur=()=>{this.focusIndex.value=-1},this.onClick=t=>{var e,i;this.onDispatch(t),t.defaultPrevented||this.listElement.current&&({cellElement:i,rowIndex:e}=rowFromEvent(t),i||(i=ObservableLike.getValue(this.state.rows[e]),0<=e&&i&&(i={data:i,index:e},this.props.selectRowOnClick&&this.processSelectionEvent(t,i),this.props.singleClickActivation)&&this.rowActivated(t,i)))},this.onDispatch=t=>{this.state.eventDispatch.dispatchEvent(t)},this.onDoubleClick=t=>{var e,i;this.onDispatch(t),t.defaultPrevented||this.props.singleClickActivation||(e=rowFromEvent(t).rowIndex,i=ObservableLike.getValue(this.state.rows[e]),0<=e&&i&&this.rowActivated(t,{data:i,index:e}))},this.onFocusBody=t=>{var e,i;this.selectOnFocus&&(e=this.props["selection"],e&&!e.selectOnFocus||0<=(e=this.focusIndex.value)&&(i=ObservableLike.getValue(this.state.rows[e]))&&this.processSelectionEvent(t,{data:i,index:e}),this.selectOnFocus=!1)},this.onFocusItem=(t,e)=>{var i=this["focusIndex"];i.value!==t&&(this.focusRow(t,2),0<=i.value?delete this.state.renderedRows[i.value]:void 0!==this.props.defaultTabbableRow&&delete this.state.renderedRows[this.props.defaultTabbableRow],delete this.state.renderedRows[t],this.focusIndex.value=t,i=ObservableLike.getValue(this.state.rows[t]))&&this.rowFocused(e,{data:i,index:t})},this.onKeyDown=t=>{if(this.onDispatch(t),!t.defaultPrevented){var e=t.target.nodeName;if("INPUT"!==e&&"TEXTAREA"!==e){const i=this["focusIndex"],s=ObservableLike.getValue(this.state.rows[i.value]);s&&(t.which===KeyCode.enter?0<=i.value&&!eventTargetContainsNode(t,["A"])&&this.rowActivated(t,{data:s,index:i.value}):t.which===KeyCode.space?(this.processSelectionEvent(t,{data:s,index:i.value}),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(()=>{this.focusIndex.value!=i.value&&this.processSelectionEvent(t,{data:s,index:this.focusIndex.value})},0))):t.which===KeyCode.pageDown?(this.focusRow(Math.min(i.value+this.props.pageSize,this.state.rowCount-1),1),t.preventDefault()):t.which===KeyCode.pageUp?(this.focusRow(Math.max(i.value-this.props.pageSize,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=i=>{var s=this.context.root["scrollTop"],o=this.state["rowCount"],{firstMaterialized:r,lastMaterialized:a}=this.state,{rowHeight:n,rowProportion:l}=this.state;if((s===this.state.scrollTop||!i.length)&&this.listElement.current&&this.bodyElement.current){var i=this.context.root.getBoundingClientRect(),h=Math.max(0,s+this.context.root.offsetTop-this.listElement.current.offsetTop);let t=Math.max(0,Math.min(o-1,Math.floor(h/(n*l)))),e=Math.min(o-1,t+Math.ceil(i.height/n));h+(e-t)*n>this.state.maxHeight&&(e=o-1,t=Math.max(0,e-Math.ceil(i.height/n))),t===r&&e===a&&n===this.state.rowHeight&&s===this.state.scrollTop&&h===this.state.scrollTopRect||this.setState({firstMaterialized:t,lastMaterialized:e,rowHeight:n,scrollTop:s,scrollTopRect:h})}},this.onMouseDownBody=t=>{this.selectOnFocus=!1};var e=t.itemProvider.length;this.state={eventDispatch:t.eventDispatch||new EventDispatch,firstMaterialized:0,itemProvider:t.itemProvider,lastMaterialized:0,maxHeight:this.props.maxHeight||1e6,focusRows:{},renderedRows:{},rowCount:e,rowHeight:t.rowHeight||0,rowProportion:t.rowHeight&&t.maxHeight?Math.min(1,t.maxHeight/(t.rowHeight*e)):1,rows:{},scrollTop:0,scrollTopRect:0}}static getDerivedStateFromProps(t,e){var i=t.itemProvider.length;let s=e.firstMaterialized,o=e.lastMaterialized;i!==e.rowCount&&(s=Math.max(0,Math.min(e.firstMaterialized,i)),o=Math.max(s,Math.min(e.lastMaterialized+(e.lastMaterialized===e.rowCount-1?t.pageSize:0),i-1)));i={firstMaterialized:s,itemProvider:t.itemProvider,lastMaterialized:o,rowCount:i,rowProportion:Math.min(1,e.maxHeight/(e.rowHeight*i))};return t.itemProvider!==e.itemProvider&&(i.renderedRows={},i.rows={}),i}getListRole(){return this.props.role||(this.props.selection?"listbox":"list")}getItemRole(){switch(this.getListRole()){case"tree":case"group":return"treeitem";case"list":return"listitem";case"listbox":return"option";case"radiogroup":return"radio";default:return null}}render(){var{className:t,focuszoneProps:e,id:i,width:s}=this.props,{firstMaterialized:o,lastMaterialized:r,maxHeight:a,rowCount:n,rowHeight:l}=this.state,h=this.getListRole(),c=[],d=Math.max(0,this.focusIndex.value-3),u=Math.min(n,this.focusIndex.value+3);if(c.push(this.renderIntersectionBounds(!0)),-1!==this.focusIndex.value&&d<o)for(let t=d;t<=Math.min(u,o-1);t++)c.push(this.renderRow(t,!1));for(let t=o;t<=r;t++)c.push(this.renderRow(t,!0));if(-1!==this.focusIndex.value&&r<u&&0<r)for(let t=Math.max(d,r+1);t<=u;t++)c.push(this.renderRow(t,!1));c.push(this.renderIntersectionBounds(!1));n=Math.min(a,l*this.state.rowCount);let p=React.createElement("div",{"aria-label":this.props.ariaLabel,className:css(t,"bolt-fixed-height-list relative"),id:getSafeId(i),onBlur:this.onBlur,onClick:this.onClick,onDoubleClick:this.onDoubleClick,onDragEnd:this.onDispatch,onDragEnter:this.onDispatch,onDragExit:this.onDispatch,onDragOver:this.onDispatch,onDragStart:this.onDispatch,onDrop:this.onDispatch,onKeyUp:this.onDispatch,onMouseDown:this.onDispatch,onTouchStart:this.onDispatch,ref:this.listElement,role:h,style:{width:s,height:n}},React.createElement("div",{className:"relative",onFocus:this.onFocusBody,onKeyDown:this.onKeyDown,onMouseDown:this.onMouseDownBody,ref:this.bodyElement,style:{width:s,height:n}},c));return p=React.createElement(FocusZone,Object.assign({direction:FocusZoneDirection.Vertical,skipHiddenCheck:!0},e),p),React.createElement(Observer,{itemProvider:{filter:(t,e)=>{this.props.selection&&this.props.selection.onItemsChanged(t,e);var i,e={renderedRows:{},focusRows:{},rows:{}};return-1!==this.state.rowCount&&(i=(t.addedItems?t.addedItems.length:0)-(t.removedItems?t.removedItems.length:0))&&(e.rowCount=this.state.rowCount+i,e.firstMaterialized=Math.max(0,Math.min(this.state.firstMaterialized,e.rowCount-1)),e.lastMaterialized=Math.max(e.firstMaterialized,Math.min(this.state.lastMaterialized+(t.index>=this.state.firstMaterialized&&t.index<=this.state.lastMaterialized+1?i:0),e.rowCount-1))),this.setState(e),!1},observableValue:this.props.itemProvider}},()=>p)}componentDidMount(){this.onIntersect([]),this.context.register(this.onIntersect)}componentDidUpdate(t,e){var{scrollToIndex:i,onScrollComplete:s}=this;if(this.state.rowCount!==e.rowCount&&this.onIntersect([]),-1!==i&&this.state.rowHeight){var o=this.bodyElement.current,{firstMaterialized:e,lastMaterialized:r}=this.state;if(e<=i&&i<=r&&o)for(let t=0;t<o.children.length;t++){var a=o.children[t];if(rowFromElement(a).rowIndex===i){a.scrollIntoView(this.scrollToOptions);break}}this.onScrollComplete=void 0,this.scrollToIndex=-1,this.scrollToOptions=void 0,s&&s(i)}}componentWillUnmount(){this.context.unregister(this.onIntersect)}getFocusIndex(){return this.focusIndex.value}getStats(){return{firstMaterialized:this.state.firstMaterialized,lastMaterialized:this.state.lastMaterialized}}scrollIntoView(e,i,t){var{}=this.props,{firstMaterialized:s,lastMaterialized:o,rowCount:r}=this.state;if(0<=e&&e<this.state.rowCount){var a=this.bodyElement.current;if(s<=e&&e<=o&&a){for(let t=0;t<a.children.length;t++){var n=a.children[t];if(rowFromElement(n).rowIndex===e){n.scrollIntoView(i);break}}t&&t(e)}else this.onScrollComplete&&this.onScrollComplete(-1),this.onScrollComplete=t,this.scrollToIndex=e,this.scrollToOptions=i,this.setState({firstMaterialized:Math.max(0,e-Math.floor((o-s)/2)),lastMaterialized:Math.min(r-1,Math.ceil(e+(o-s)/2))})}}focusRow(i,s){this.scrollIntoView(i,{block:"nearest"},t=>{var e;t===i&&this.bodyElement.current&&(e=this.bodyElement.current.querySelector("[data-row-index='"+t+"']"))&&(e.getAttribute("tabindex")?e.focus():(e=Math.min(this.state.rowCount-1,Math.max(0,t+s)))!==t?this.focusRow(e,s):e!==this.focusIndex.value&&this.focusRow(e,-s))})}processSelectionEvent(i,s){var o,r=this.props["selection"];if(!r||r.selectable(s.index)){let t=!1,e=!0;r&&(o=s["index"],t=r.selected(o),0<=this.pivotIndex&&i.shiftKey&&r.multiSelect?r.select(Math.min(this.pivotIndex,o),Math.abs(this.pivotIndex-o)+1,i.ctrlKey||i.metaKey):(i.ctrlKey||i.metaKey||r.alwaysMerge)&&r.multiSelect?(r.toggle(o,!0),e=!1):r.select(o,1,!1),i.shiftKey||(this.pivotIndex=o)),t!==e&&this.rowSelected(i,s)}}renderLoadingRow(t,e){return React.createElement("div",{className:"bolt-list-row-loading"},React.createElement("div",{className:"shimmer shimmer-line",style:{width:80*Math.random()+20+"%"}}," "))}renderIntersectionBounds(t){var{firstMaterialized:e,lastMaterialized:i,rowHeight:s,rowProportion:o}=this.state;const r=t?"topobserv":"bottomobserv";let a=0;return e*s*o+(i-e)*s>this.state.maxHeight?t?(a=this.state.maxHeight,a-=(i-e)*s*o+s,a--):a=this.state.maxHeight-1:a=t?e*s*o-1:e*s*o+(1+i-e)*s+1,React.createElement("div",{className:"bolt-list-row-spacer invisible absolute",key:r,ref:t=>{var e=this.intersectionElements[r];t?e!==t&&(e&&this.context.unobserve(t),this.context.observe(t),this.intersectionElements[r]=t):e&&(this.context.unobserve(e),delete this.intersectionElements[r])},role:"presentation",style:{top:a+"px",height:"1px"}})}renderRow(u,e){var i=this.props["itemProvider"];const{focusRows:t,renderedRows:s,firstMaterialized:p,lastMaterialized:m,rowProportion:v,rows:o}=this.state,w=this.getItemRole();let r=(e?s:t)[u];if(!r||1!==v){let d=o[u];if(!(d=d||(i.getItem?i.getItem(u):i.value[u])))return null;o[u]=d;i=this.props["selection"];let t;i&&(t={observableValue:i,filter:t=>{for(const e of t)if(u>=e.beginIndex&&u<=e.endIndex)return!0;return!1}});const f=t=>{this.onFocusItem(u,t)};r=React.createElement(UncheckedObserver,{item:d,key:u,selection:t,focusIndex:this.focusIndex},t=>{var{renderRow:e,renderLoadingRow:i}=this.props,{rowHeight:s,rowCount:o}=this.state,r=ObservableLike.getValue(d),r={ariaBusy:!t.item,ariaRowOffset:1,data:r,eventDispatch:this.state.eventDispatch,itemProvider:this.props.itemProvider,listProps:this.props,onFocusItem:this.onFocusItem,singleClickActivation:this.props.onActivate&&this.props.singleClickActivation};let a,n=(a=t.item?e(u,t.item,r):i?i(u,r):this.renderLoadingRow(u,r),0),l=0;u>=p&&u<=m&&(l=s),p*s*v+(m-p)*s>this.state.maxHeight?(n=this.state.maxHeight,n=(n-=(o-m)*s*v)-(m-u)*s):0===l?n=u*s*v:(n=p*s*v,n+=(u-p)*s);e=null===r?void 0:r.data;const h=null==(t=null==e?void 0:e.underlyingItem)?void 0:t.childItems,c=null==(i=null==e?void 0:e.underlyingItem)?void 0:i.expanded;return React.createElement(FocusWithin,{onFocus:f},e=>React.createElement(FocusZoneContext.Consumer,null,t=>React.createElement(FocusZone,{direction:FocusZoneDirection.Horizontal},React.createElement("div",{className:css("bolt-fixed-height-list-row scroll-hidden absolute",this.focusIndex.value===u&&"focused"),style:{height:l+"px",top:n+"px"},"data-focuszone":t.focuszoneId,"data-row-index":u,tabIndex:0==u||h?0:-1,onBlur:e.onBlur,onFocus:e.onFocus,role:w,"aria-expanded":(!h||void 0!==c)&&c,"aria-labelledby":"rowContent-"+u},a))))}),e?this.state.renderedRows[u]=r:this.state.focusRows[u]=r}return r}rowActivated(t,e){this.state.eventDispatch.dispatchEvent(t,e,"activate"),this.props.onActivate&&this.props.onActivate(t,e)}rowSelected(t,e){this.state.eventDispatch.dispatchEvent(t,e,"select"),this.props.onSelect&&this.props.onSelect(t,e)}rowFocused(t,e){this.state.eventDispatch.dispatchEvent(t,e,"focus"),this.props.onFocus&&this.props.onFocus(t,e)}}function getAttributeAsNumber(t,e){t=t.getAttribute(e);return t?parseInt(t,10):-1}function rowFromElement(t){var e;let i=-1;for(;t;){if(-1!==(e=getAttributeAsNumber(t,"data-row-index"))){i=e;break}if(t.classList.contains("bolt-fixed-height-list")){t=null;break}t=t.parentElement}return{cellElement:null,cellIndex:-1,rowElement:t,rowIndex:i}}function rowFromEvent(t){return rowFromElement(t.target)}FixedHeightList.contextType=IntersectionContext,FixedHeightList.defaultProps={defaultTabbableRow:0,focuszoneProps:{direction:FocusZoneDirection.Vertical},maxHeight:1e6};export{FixedHeightList,rowFromElement,rowFromEvent};