@@ -10,17 +10,21 @@ import './treetable.css';
1010
1111import { ConfigProvider , Table } from 'antd' ;
1212import { ColumnType , ExpandableConfig } from 'antd/es/table/interface' ;
13+ import { Resizable } from 're-resizable' ;
1314import { default as React , useCallback , useEffect , useLayoutEffect , useMemo , useState } from 'react' ;
1415import { debounce } from 'throttle-debounce' ;
1516import { CommandDefinition , findNestedValue } from '../../../common' ;
17+ import { Commands } from '../../../manifest' ;
1618import { CDTTreeItem , CDTTreeItemResource , CDTTreeTableActionColumn , CDTTreeTableActionColumnCommand , CDTTreeTableColumnDefinition , CDTTreeTableStringColumn , CTDTreeWebviewContext } from '../types' ;
1719import ActionCell from './cells/ActionCell' ;
1820import StringCell from './cells/StringCell' ;
1921import { ExpandIcon } from './expand-icon' ;
2022import { SearchOverlay } from './search-overlay' ;
2123import { TreeNavigator } from './treetable-navigator' ;
2224import { classNames , filterTree , getAncestors , traverseTree , useClickHook } from './utils' ;
23- import { Commands } from '../../../manifest' ;
25+
26+ const COLUMN_MIN_WIDTH = 50 ;
27+ const ACTION_COLUMN_WIDTH = 16 * 5 ;
2428
2529/**
2630 * Component to render a tree table.
@@ -81,6 +85,12 @@ export type ComponentTreeTableProps<T extends CDTTreeItemResource = CDTTreeItemR
8185 }
8286} ;
8387
88+ interface ResizeableCell {
89+ resizeable ?: boolean ;
90+ maxWidth ?: number ;
91+ onDidColumnResize ?: ( event : MouseEvent | TouchEvent , width : number ) => void ;
92+ }
93+
8494interface BodyRowProps extends React . HTMLAttributes < HTMLDivElement > {
8595 'data-row-key' : string ;
8696 record : CDTTreeItem < CDTTreeItemResource > ;
@@ -91,6 +101,7 @@ const BodyRow = React.forwardRef<HTMLDivElement, BodyRowProps>((props, ref) => {
91101 return (
92102 < div
93103 ref = { ref }
104+ data-test
94105 tabIndex = { 0 }
95106 key = { props [ 'data-row-key' ] }
96107 { ...props }
@@ -99,6 +110,72 @@ const BodyRow = React.forwardRef<HTMLDivElement, BodyRowProps>((props, ref) => {
99110 ) ;
100111} ) ;
101112
113+
114+ interface BodyCellProps extends React . HTMLAttributes < HTMLDivElement > , ResizeableCell {
115+ }
116+
117+ const BodyCell = React . forwardRef < HTMLDivElement , BodyCellProps > ( ( props , ref ) => {
118+ const { resizeable, onDidColumnResize, maxWidth, className, onResize, style, ...rest } = props ;
119+ const [ width , setWidth ] = useState < string | number | undefined > ( props . style ?. width ) ;
120+
121+ useEffect ( ( ) => {
122+ if ( resizeable ) {
123+ setWidth ( props . style ?. width ) ;
124+ }
125+ } , [ props . style ?. width ] ) ;
126+
127+ const cell = < div
128+ ref = { ref }
129+ style = { { minWidth : '50px' , ...style } }
130+ { ...rest }
131+ className = { className }
132+ onResize = { onResize }
133+ /> ;
134+
135+ if ( ! resizeable ) {
136+ return cell ;
137+ }
138+
139+ return (
140+ < Resizable
141+ minWidth = { 50 }
142+ maxWidth = { maxWidth }
143+ className = { classNames ( 'ant-table-cell-resizable' , className ?? '' ) }
144+ size = { {
145+ width,
146+ } }
147+ onResizeStart = { ( _event , _direction , ref ) => {
148+ const row = ref . closest ( '.ant-table-row' ) ;
149+ row ?. classList . add ( 'ant-table-row-resizing' ) ;
150+ } }
151+ onResize = { ( event , _direction , ref , _delta ) => {
152+ onDidColumnResize ?.( event , ref . clientWidth ) ;
153+
154+ } }
155+ onResizeStop = { ( event , _direction , ref , _delta ) => {
156+ onDidColumnResize ?.( event , ref . clientWidth ) ;
157+ const row = ref . closest ( '.ant-table-row' ) ;
158+ row ?. classList . remove ( 'ant-table-row-resizing' ) ;
159+ } }
160+ handleClasses = { {
161+ right : 'resizable-handle' ,
162+ } }
163+ enable = { {
164+ bottom : false ,
165+ bottomLeft : false ,
166+ bottomRight : false ,
167+ left : false ,
168+ right : true ,
169+ top : false ,
170+ topLeft : false ,
171+ topRight : false
172+ } }
173+ { ...rest }
174+ >
175+ </ Resizable >
176+ ) ;
177+ } ) ;
178+
102179function useWindowSize ( ) {
103180 const [ size , setSize ] = useState ( { width : window . innerWidth , height : window . innerHeight } ) ;
104181 useLayoutEffect ( ( ) => {
@@ -279,6 +356,32 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
279356
280357
281358 // ==== Columns ====
359+ const [ columnWidths , setColumnWidths ] = useState < Record < string , number > > ( { } ) ;
360+ const [ prevWindowWidth , setPrevWindowWidth ] = useState ( width ) ;
361+ const availableWidth = useMemo ( ( ) => width - ACTION_COLUMN_WIDTH - COLUMN_MIN_WIDTH * ( props . columnDefinitions ?. filter ( c => c . resizable ) . length ?? 0 ) , [ width , props . columnDefinitions ] ) ;
362+
363+ const handleResize = ( field : string ) =>
364+ ( _ : MouseEvent | TouchEvent , width : number ) => {
365+ setColumnWidths ( ( prev ) => ( { ...prev , [ field ] : width } ) ) ;
366+ } ;
367+
368+ useEffect ( ( ) => {
369+ const delta = width - prevWindowWidth ;
370+ if ( delta < 0 ) {
371+ // Shrink columns that are too wide
372+ setColumnWidths ( ( prev ) => {
373+ const newWidths = { ...prev } ;
374+ for ( const key in newWidths ) {
375+ const currentWidth = newWidths [ key ] ;
376+ if ( currentWidth > availableWidth ) {
377+ newWidths [ key ] = Math . max ( currentWidth + delta , COLUMN_MIN_WIDTH ) ;
378+ }
379+ }
380+ return newWidths ;
381+ } ) ;
382+ }
383+ setPrevWindowWidth ( width ) ;
384+ } , [ width ] ) ;
282385
283386 const getActions = useCallback ( ( record : CDTTreeItem < T > , column : CDTTreeTableActionColumn ) => {
284387 const actions : CDTTreeTableActionColumnCommand [ ] = [ ] ;
@@ -344,19 +447,30 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
344447
345448 const columns = useMemo ( ( ) => {
346449 return props . columnDefinitions ?. map < ColumnType < CDTTreeItem < T > > > ( colDef => {
450+ const resizeable : ResizeableCell = {
451+ resizeable : colDef . resizable ,
452+ maxWidth : availableWidth ,
453+ onDidColumnResize : handleResize ( colDef . field )
454+ } ;
455+
347456 if ( colDef . type === 'string' ) {
348457 return {
349458 title : colDef . field ,
350459 dataIndex : [ 'columns' , colDef . field ] ,
351- width : 0 ,
460+ width : columnWidths [ colDef . field ] ?? 0 ,
352461 ellipsis : true ,
353462 render : renderStringCell ,
354463 className : colDef . field ,
355464 onCell : ( record ) => {
356465 const column = findNestedValue < CDTTreeTableStringColumn > ( record , [ 'columns' , colDef . field ] ) ;
466+
357467 return ! column || ! column . colSpan
358- ? { }
468+ ? {
469+ ...resizeable ,
470+ } as React . HTMLAttributes < unknown > & React . TdHTMLAttributes < unknown >
359471 : {
472+ ...resizeable ,
473+ resizeable : column . colSpan !== 'fill' ,
360474 colSpan : column . colSpan === 'fill' ? props . columnDefinitions ?. length : column . colSpan ,
361475 style : { zIndex : 1 }
362476 } ;
@@ -367,17 +481,18 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
367481 return {
368482 title : colDef . field ,
369483 dataIndex : [ 'columns' , colDef . field ] ,
370- width : 16 * 5 ,
484+ width : ACTION_COLUMN_WIDTH ,
371485 render : renderActionCell ,
372486 } ;
373487 }
374488 return {
489+ ...resizeable ,
375490 title : colDef . field ,
376491 dataIndex : [ 'columns' , colDef . field , 'label' ] ,
377492 width : 200 ,
378493 } ;
379494 } ) ?? [ ] ;
380- } , [ props . columnDefinitions , renderStringCell , renderActionCell ] ) ;
495+ } , [ props . columnDefinitions , columnWidths , renderStringCell , renderActionCell ] ) ;
381496
382497 // ==== Handlers ====
383498
@@ -464,7 +579,7 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
464579 ref = { tblRef }
465580 columns = { columns }
466581 dataSource = { filteredData }
467- components = { { body : { row : BodyRow } } }
582+ components = { { body : { row : BodyRow , cell : BodyCell } } }
468583 virtual
469584 scroll = { { x : width , y : height - 2 } }
470585 showHeader = { false }
0 commit comments