|
| 1 | +import{defaultasTable,TableProps,ColumnType}from"antd/es/table"; |
| 2 | +importReact,{useCallback,useMemo,useRef,useState}from"react"; |
| 3 | +import{Resizable}from"react-resizable"; |
| 4 | +importstyledfrom"styled-components"; |
| 5 | +import_from"lodash"; |
| 6 | +import{useUserViewMode}from"util/hooks"; |
| 7 | +import{ReactRef,ResizeHandleAxis}from"layout/gridLayoutPropTypes"; |
| 8 | +import{COL_MIN_WIDTH,RecordType,CustomColumnType}from"./tableUtils"; |
| 9 | +import{RowColorViewType,RowHeightViewType}from"./tableTypes"; |
| 10 | +import{TableColumnStyleType,TableColumnLinkStyleType}from"comps/controls/styleControlConstants"; |
| 11 | +import{CellColorViewType}from"./column/tableColumnComp"; |
| 12 | +import{TableCellView}from"./TableCell"; |
| 13 | +import{TableRowView}from"./TableRow"; |
| 14 | + |
| 15 | +constTitleResizeHandle=styled.span` |
| 16 | + position: absolute; |
| 17 | + top: 0; |
| 18 | + right: -5px; |
| 19 | + width: 10px; |
| 20 | + height: 100%; |
| 21 | + cursor: col-resize; |
| 22 | + z-index: 1; |
| 23 | +`; |
| 24 | + |
| 25 | +constTableTh=styled.th<{width?:number}>` |
| 26 | + overflow: hidden; |
| 27 | +
|
| 28 | + > div { |
| 29 | + overflow: hidden; |
| 30 | + white-space: pre; |
| 31 | + text-overflow: ellipsis; |
| 32 | + } |
| 33 | +
|
| 34 | +${(props)=>props.width&&`width:${props.width}px`}; |
| 35 | +`; |
| 36 | + |
| 37 | +constResizeableTitle=React.forwardRef<HTMLTableHeaderCellElement,any>((props,ref)=>{ |
| 38 | +const{ onResize, onResizeStop, width, viewModeResizable, ...restProps}=props; |
| 39 | +const[childWidth,setChildWidth]=useState(0); |
| 40 | +constresizeRef=useRef<HTMLTableHeaderCellElement>(null); |
| 41 | +constisUserViewMode=useUserViewMode(); |
| 42 | + |
| 43 | +constupdateChildWidth=useCallback(()=>{ |
| 44 | +if(resizeRef.current){ |
| 45 | +constwidth=resizeRef.current.getBoundingClientRect().width; |
| 46 | +setChildWidth(width); |
| 47 | +} |
| 48 | +},[]); |
| 49 | + |
| 50 | +React.useEffect(()=>{ |
| 51 | +updateChildWidth(); |
| 52 | +constresizeObserver=newResizeObserver(()=>{ |
| 53 | +updateChildWidth(); |
| 54 | +}); |
| 55 | + |
| 56 | +if(resizeRef.current){ |
| 57 | +resizeObserver.observe(resizeRef.current); |
| 58 | +} |
| 59 | + |
| 60 | +return()=>{ |
| 61 | +resizeObserver.disconnect(); |
| 62 | +}; |
| 63 | +},[updateChildWidth]); |
| 64 | + |
| 65 | +React.useImperativeHandle(ref,()=>resizeRef.current!,[]); |
| 66 | + |
| 67 | +constisNotDataColumn=_.isNil(restProps.title); |
| 68 | +if((isUserViewMode&&!restProps.viewModeResizable)||isNotDataColumn){ |
| 69 | +return<TableThref={resizeRef}{...restProps}width={width}/>; |
| 70 | +} |
| 71 | + |
| 72 | +return( |
| 73 | +<Resizable |
| 74 | +width={width>0 ?width :childWidth} |
| 75 | +height={0} |
| 76 | +onResize={(e:React.SyntheticEvent,{ size}:{size:{width:number}})=>{ |
| 77 | +e.stopPropagation(); |
| 78 | +onResize(size.width); |
| 79 | +}} |
| 80 | +onResizeStart={(e:React.SyntheticEvent)=>{ |
| 81 | +updateChildWidth(); |
| 82 | +e.stopPropagation(); |
| 83 | +e.preventDefault(); |
| 84 | +}} |
| 85 | +onResizeStop={onResizeStop} |
| 86 | +draggableOpts={{enableUserSelectHack:false}} |
| 87 | +handle={(axis:ResizeHandleAxis,ref:ReactRef<HTMLDivElement>)=>( |
| 88 | +<TitleResizeHandle |
| 89 | +ref={ref} |
| 90 | +onClick={(e)=>{ |
| 91 | +e.preventDefault(); |
| 92 | +e.stopPropagation(); |
| 93 | +}} |
| 94 | +/> |
| 95 | +)} |
| 96 | +> |
| 97 | +<TableThref={resizeRef}{...restProps}title=""/> |
| 98 | +</Resizable> |
| 99 | +); |
| 100 | +}); |
| 101 | + |
| 102 | +typeCustomTableProps<RecordType>=Omit<TableProps<RecordType>,"components"|"columns">&{ |
| 103 | +columns:CustomColumnType<RecordType>[]; |
| 104 | +viewModeResizable:boolean; |
| 105 | +visibleResizables:boolean; |
| 106 | +rowColorFn:RowColorViewType; |
| 107 | +rowHeightFn:RowHeightViewType; |
| 108 | +columnsStyle:TableColumnStyleType; |
| 109 | +size?:string; |
| 110 | +rowAutoHeight?:boolean; |
| 111 | +customLoading?:boolean; |
| 112 | +onCellClick:(columnName:string,dataIndex:string)=>void; |
| 113 | +virtual?:boolean; |
| 114 | +scroll?:{ |
| 115 | +x?:number|string; |
| 116 | +y?:number|string; |
| 117 | +}; |
| 118 | +}; |
| 119 | + |
| 120 | +functionResizeableTableComp<RecordTypeextendsobject>(props:CustomTableProps<RecordType>){ |
| 121 | +const{ |
| 122 | + columns, |
| 123 | + viewModeResizable, |
| 124 | + visibleResizables, |
| 125 | + rowColorFn, |
| 126 | + rowHeightFn, |
| 127 | + columnsStyle, |
| 128 | + size, |
| 129 | + rowAutoHeight, |
| 130 | + customLoading, |
| 131 | + onCellClick, |
| 132 | + ...restProps |
| 133 | +}=props; |
| 134 | +const[resizeData,setResizeData]=useState({index:-1,width:-1}); |
| 135 | + |
| 136 | +// Memoize resize handlers |
| 137 | +consthandleResize=useCallback((width:number,index:number)=>{ |
| 138 | +setResizeData({ index, width}); |
| 139 | +},[]); |
| 140 | + |
| 141 | +consthandleResizeStop=useCallback((width:number,index:number,onWidthResize?:(width:number)=>void)=>{ |
| 142 | +setResizeData({index:-1,width:-1}); |
| 143 | +if(onWidthResize){ |
| 144 | +onWidthResize(width); |
| 145 | +} |
| 146 | +},[]); |
| 147 | + |
| 148 | +// Memoize cell handlers |
| 149 | +constcreateCellHandler=useCallback((col:CustomColumnType<RecordType>)=>{ |
| 150 | +return(record:RecordType,index:number)=>({ |
| 151 | + record, |
| 152 | +title:String(col.dataIndex), |
| 153 | + rowColorFn, |
| 154 | + rowHeightFn, |
| 155 | +cellColorFn:col.cellColorFn, |
| 156 | +rowIndex:index, |
| 157 | + columnsStyle, |
| 158 | +columnStyle:col.style, |
| 159 | +linkStyle:col.linkStyle, |
| 160 | +tableSize:size, |
| 161 | +autoHeight:rowAutoHeight, |
| 162 | +onClick:()=>onCellClick(col.titleText,String(col.dataIndex)), |
| 163 | +loading:customLoading, |
| 164 | +customAlign:col.align, |
| 165 | +}); |
| 166 | +},[rowColorFn,rowHeightFn,columnsStyle,size,rowAutoHeight,onCellClick,customLoading]); |
| 167 | + |
| 168 | +// Memoize header cell handlers |
| 169 | +constcreateHeaderCellHandler=useCallback((col:CustomColumnType<RecordType>,index:number,resizeWidth:number)=>{ |
| 170 | +return()=>({ |
| 171 | +width:resizeWidth, |
| 172 | +title:col.titleText, |
| 173 | + viewModeResizable, |
| 174 | +onResize:(width:React.SyntheticEvent)=>{ |
| 175 | +if(width){ |
| 176 | +handleResize(Number(width),index); |
| 177 | +} |
| 178 | +}, |
| 179 | +onResizeStop:(e:React.SyntheticEvent,{ size}:{size:{width:number}})=>{ |
| 180 | +handleResizeStop(size.width,index,col.onWidthResize); |
| 181 | +}, |
| 182 | +}); |
| 183 | +},[viewModeResizable,handleResize,handleResizeStop]); |
| 184 | + |
| 185 | +// Memoize columns to prevent unnecessary re-renders |
| 186 | +constmemoizedColumns=useMemo(()=>{ |
| 187 | +returncolumns.map((col,index)=>{ |
| 188 | +const{ width, style, linkStyle, cellColorFn, onWidthResize, ...restCol}=col; |
| 189 | +constresizeWidth=(resizeData.index===index ?resizeData.width :col.width)??0; |
| 190 | + |
| 191 | +constcolumn:ColumnType<RecordType>={ |
| 192 | + ...restCol, |
| 193 | +width:typeofresizeWidth==="number"&&resizeWidth>0 ?resizeWidth :undefined, |
| 194 | +minWidth:typeofresizeWidth==="number"&&resizeWidth>0 ?undefined :COL_MIN_WIDTH, |
| 195 | +onCell:(record:RecordType,index?:number)=>createCellHandler(col)(record,index??0), |
| 196 | +onHeaderCell:()=>createHeaderCellHandler(col,index,Number(resizeWidth))(), |
| 197 | +}; |
| 198 | +returncolumn; |
| 199 | +}); |
| 200 | +},[columns,resizeData,createCellHandler,createHeaderCellHandler]); |
| 201 | + |
| 202 | +return( |
| 203 | +<Table<RecordType> |
| 204 | +components={{ |
| 205 | +header:{ |
| 206 | +cell:ResizeableTitle, |
| 207 | +}, |
| 208 | +body:{ |
| 209 | +cell:TableCellView, |
| 210 | +row:TableRowView, |
| 211 | +}, |
| 212 | +}} |
| 213 | +{...restProps} |
| 214 | +pagination={false} |
| 215 | +columns={memoizedColumns} |
| 216 | +/> |
| 217 | +); |
| 218 | +} |
| 219 | +ResizeableTableComp.whyDidYouRender=true; |
| 220 | + |
| 221 | +exportconstResizeableTable=React.memo(ResizeableTableComp)astypeofResizeableTableComp; |
| 222 | +exporttype{CustomTableProps}; |