11import { useMemo } from 'react' ;
22import { AnalyticalTableScaleWidthMode } from '../../../enums/AnalyticalTableScaleWidthMode.js' ;
3- import { DEFAULT_COLUMN_WIDTH } from '../defaults/Column/index.js' ;
4- import type { AnalyticalTableColumnDefinition , ReactTableHooks , TableInstance } from '../types/index.js' ;
3+ import type {
4+ AnalyticalTableColumnDefinition ,
5+ ColumnType ,
6+ ReactTableHooks ,
7+ TableInstance ,
8+ RowType ,
9+ } from '../types/index.js' ;
510
611interface IColumnMeta {
712 contentPxAvg : number ;
813 headerPx : number ;
914 headerDefinesWidth ?: boolean ;
10- //todo: not optional?
1115 maxWidth ?: number ;
1216}
1317
1418const ROW_SAMPLE_SIZE = 20 ;
1519const MAX_WIDTH = 700 ;
1620export const CELL_PADDING_PX = 18 ; /* padding left and right 0.5rem each (16px) + borders (1px) + buffer (1px) */
1721
18- function findLongestString ( str1 , str2 ) {
19- if ( typeof str1 !== 'string' || typeof str2 !== 'string' ) {
20- return str1 || str2 || undefined ;
21- }
22+ function getContentPxLongest ( rowSample : RowType [ ] , columnIdOrAccessor : string , uniqueId ) {
23+ return rowSample . reduce ( ( max , item ) => {
24+ const dataPoint = item . values ?. [ columnIdOrAccessor ] ;
25+
26+ if ( dataPoint ) {
27+ const val = stringToPx ( dataPoint , uniqueId ) + CELL_PADDING_PX ;
28+ return Math . max ( max , val ) ;
29+ }
2230
23- return str1 . length > str2 . length ? str1 : str2 ;
31+ return max ;
32+ } , 0 ) ;
2433}
2534
2635function getContentPxAvg ( rowSample , columnIdOrAccessor , uniqueId ) {
@@ -193,7 +202,12 @@ function calculateDefaultColumnWidths(tableWidth: number, columns: AnalyticalTab
193202 return result ;
194203}
195204
196- const calculateSmartColumns = ( columns : AnalyticalTableColumnDefinition [ ] , instance , hiddenColumns ) => {
205+ const calculateSmartAndGrowColumns = (
206+ columns : AnalyticalTableColumnDefinition [ ] ,
207+ instance : TableInstance ,
208+ hiddenColumns : ColumnType ,
209+ isGrow = false ,
210+ ) => {
197211 const { rows, state, webComponentsReactProperties } = instance ;
198212 const rowSample = rows . slice ( 0 , ROW_SAMPLE_SIZE ) ;
199213 const { tableClientWidth : totalWidth } = state ;
@@ -217,13 +231,15 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
217231 return metadata ;
218232 }
219233
220- let headerPx : number , contentPxAvg : number ;
234+ let headerPx : number , contentPxLength : number ;
221235
222236 if ( column . scaleWidthModeOptions ?. cellString ) {
223- contentPxAvg =
237+ contentPxLength =
224238 stringToPx ( column . scaleWidthModeOptions . cellString , webComponentsReactProperties . uniqueId ) + CELL_PADDING_PX ;
225239 } else {
226- contentPxAvg = getContentPxAvg ( rowSample , columnIdOrAccessor , webComponentsReactProperties . uniqueId ) ;
240+ contentPxLength = isGrow
241+ ? getContentPxLongest ( rowSample , columnIdOrAccessor , webComponentsReactProperties . uniqueId )
242+ : getContentPxAvg ( rowSample , columnIdOrAccessor , webComponentsReactProperties . uniqueId ) ;
227243 }
228244
229245 if ( column . scaleWidthModeOptions ?. headerString ) {
@@ -241,8 +257,9 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
241257
242258 metadata [ columnIdOrAccessor ] = {
243259 headerPx,
244- contentPxAvg,
245- maxWidth : column . maxWidth ,
260+ contentPxAvg : contentPxLength ,
261+ // When Grow mode is active, static max width should be applied
262+ maxWidth : column . maxWidth ?? ( isGrow ? MAX_WIDTH : undefined ) ,
246263 } ;
247264 return metadata ;
248265 } ,
@@ -252,11 +269,25 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
252269 let totalContentPxAvgPrio1 = 0 ;
253270 let totalNumberColPrio2 = 0 ;
254271
255- // width reserved by predefined widths or columns defined by header
272+ /**
273+ * Width reserved by predefined widths or columns defined by header
274+ * Grow: full content width or header width (if wider) if not restricted by maxWidth
275+ */
256276 const reservedWidth : number = visibleColumns . reduce ( ( acc , column ) => {
257277 const columnIdOrAccessor = ( column . id ?? column . accessor ) as string ;
258278 const { contentPxAvg, headerPx } = columnMeta [ columnIdOrAccessor ] ;
259-
279+ if ( isGrow ) {
280+ totalContentPxAvgPrio1 += columnMeta [ columnIdOrAccessor ] . contentPxAvg ;
281+ const targetWidth = Math . min (
282+ Math . max ( column . minWidth ?? 0 , column . width ?? 0 , contentPxAvg , headerPx ) ,
283+ column . maxWidth ?? MAX_WIDTH ,
284+ ) ;
285+
286+ if ( targetWidth !== column . maxWidth ) {
287+ totalNumberColPrio2 ++ ;
288+ }
289+ return acc + targetWidth ;
290+ }
260291 if ( contentPxAvg > headerPx ) {
261292 if ( ! column . minWidth && ! column . width ) {
262293 totalContentPxAvgPrio1 += columnMeta [ columnIdOrAccessor ] . contentPxAvg ;
@@ -278,22 +309,29 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
278309 const availableWidthPrio1 = totalWidth - reservedWidth ;
279310 let availableWidthPrio2 = availableWidthPrio1 ;
280311
281- // Step 1: Give columns defined by content more space (priority 1)
312+ /**
313+ * Step 1: Give columns defined by content more space (priority 1)
314+ * Grow: Give all columns the required space necessary to display the full content (up to the maxWidth)
315+ */
282316 const visibleColumnsAdaptedPrio1 = visibleColumns . map ( ( column ) => {
283317 const columnIdOrAccessor = ( column . id ?? column . accessor ) as string ;
284318 const meta = columnMeta [ columnIdOrAccessor ] ;
285319 if ( meta && ! column . minWidth && ! column . width && ! meta . headerDefinesWidth ) {
286- let targetWidth ;
320+ let targetWidth : number ;
287321 const { contentPxAvg, headerPx } = meta ;
288322
289- if ( availableWidthPrio1 > 0 ) {
323+ if ( isGrow ) {
324+ targetWidth = Math . min ( Math . max ( contentPxAvg , headerPx ) , meta . maxWidth ) ;
325+ } else if ( availableWidthPrio1 > 0 ) {
290326 const factor = contentPxAvg / totalContentPxAvgPrio1 ;
291327 targetWidth = Math . max ( Math . min ( availableWidthPrio1 * factor , contentPxAvg ) , headerPx ) ;
292328 availableWidthPrio2 -= targetWidth ;
293329 }
330+
294331 return {
295332 ...column ,
296333 nextWidth : targetWidth || headerPx ,
334+ maxWidth : isGrow ? ( column ?. maxWidth ?? MAX_WIDTH ) : Infinity ,
297335 } ;
298336 }
299337 return column ;
@@ -302,19 +340,29 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
302340 const columnWithMaxWidthIndex : number [ ] = [ ] ;
303341 let maxWidthDifference = 0 ;
304342 let fullWidthOfAllColumns = 0 ;
305- // Step 2: Give all columns more space (priority 2)
343+ let lessThanMaxWidthCount = 0 ;
344+
345+ /**
346+ * Step 2: Give all columns more space (priority 2)
347+ */
306348 const visibleColumnsAdaptedPrio2 = visibleColumnsAdaptedPrio1 . map ( ( column , index ) => {
307349 const columnIdOrAccessor = ( column . id ?? column . accessor ) as string ;
308350 const meta = columnMeta [ columnIdOrAccessor ] ;
309351 const { headerPx } = meta ;
310352
311- if ( column . maxWidth ) {
353+ if ( column . maxWidth && column . maxWidth !== Infinity ) {
312354 columnWithMaxWidthIndex . push ( index ) ;
313355 }
314356 if ( meta && ! column . minWidth && ! column . width ) {
315357 let targetWidth = column . nextWidth || headerPx ;
316358 if ( availableWidthPrio2 > 0 ) {
317- targetWidth = targetWidth + availableWidthPrio2 * ( 1 / totalNumberColPrio2 ) ;
359+ targetWidth = Math . min (
360+ targetWidth + availableWidthPrio2 * ( 1 / totalNumberColPrio2 ) ,
361+ column . maxWidth ?? Infinity ,
362+ ) ;
363+ if ( targetWidth < ( column . maxWidth ?? Infinity ) ) {
364+ lessThanMaxWidthCount ++ ;
365+ }
318366 }
319367 fullWidthOfAllColumns += targetWidth ;
320368 if ( targetWidth >= column . maxWidth ) {
@@ -334,7 +382,22 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
334382 }
335383 } ) ;
336384
337- // Step 3: Only if any column has maxWidth defined and there is still space to distribute, distribute the exceeding width to the other columns (priority 3)
385+ // In Grow mode, all columns implement a maxWidth
386+ if ( isGrow ) {
387+ const remainingWidth = totalWidth - fullWidthOfAllColumns ;
388+ if ( remainingWidth > 0 ) {
389+ //Step 3 (Grow): Distribute remaining width to all columns that are not at their maxWidth
390+ return visibleColumnsAdaptedPrio2 . map ( ( column ) => {
391+ if ( column . width !== column . maxWidth ) {
392+ return { ...column , width : column . width + remainingWidth / lessThanMaxWidthCount } ;
393+ }
394+ return column ;
395+ } ) ;
396+ }
397+ return visibleColumnsAdaptedPrio2 ;
398+ }
399+
400+ // Step 3 (Smart): Only if any column has maxWidth defined and there is still space to distribute, distribute the exceeding width to the other columns (priority 3)
338401 if ( columnWithMaxWidthIndex . length && totalWidth >= fullWidthOfAllColumns ) {
339402 return visibleColumnsAdaptedPrio2 . map ( ( column , index , arr ) => {
340403 if ( ! columnWithMaxWidthIndex . includes ( index ) ) {
@@ -390,9 +453,9 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
390453 if ( ! instance . state || ! instance . rows ) {
391454 return columns ;
392455 }
393- const { rows , state } = instance ;
456+ const { state } = instance ;
394457 const { hiddenColumns, tableClientWidth : totalWidth } = state ;
395- const { scaleWidthMode, loading, uniqueId } = instance . webComponentsReactProperties ;
458+ const { scaleWidthMode, loading } = instance . webComponentsReactProperties ;
396459
397460 if ( columns . length === 0 || ! totalWidth || ! AnalyticalTableScaleWidthMode [ scaleWidthMode ] ) {
398461 return columns ;
@@ -412,8 +475,13 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
412475 return column ?? false ;
413476 } )
414477 . filter ( Boolean ) as TableInstance [ 'columns' ] ;
478+
415479 if ( scaleWidthMode === AnalyticalTableScaleWidthMode . Smart ) {
416- return calculateSmartColumns ( columns , instance , hiddenColumns ) ;
480+ return calculateSmartAndGrowColumns ( columns , instance , hiddenColumns ) ;
481+ }
482+
483+ if ( scaleWidthMode === AnalyticalTableScaleWidthMode . Grow ) {
484+ return calculateSmartAndGrowColumns ( columns , instance , hiddenColumns , true ) ;
417485 }
418486
419487 const hasData = instance . data . length > 0 ;
@@ -431,101 +499,6 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
431499 return { ...column , width : calculatedWidth } ;
432500 } ) ;
433501 }
434-
435- // AnalyticalTableScaleWidthMode.Grow
436-
437- const rowSample = rows . slice ( 0 , ROW_SAMPLE_SIZE ) ;
438-
439- const columnMeta = visibleColumns . reduce ( ( acc , column ) => {
440- const columnIdOrAccessor = ( column . id ?? column . accessor ) as string ;
441- if (
442- column . id === '__ui5wcr__internal_selection_column' ||
443- column . id === '__ui5wcr__internal_highlight_column' ||
444- column . id === '__ui5wcr__internal_navigation_column'
445- ) {
446- acc [ columnIdOrAccessor ] = {
447- minHeaderWidth : column . width ,
448- fullWidth : column . width ,
449- } ;
450- return acc ;
451- }
452-
453- const smartWidth = findLongestString (
454- column . scaleWidthModeOptions ?. headerString ,
455- column . scaleWidthModeOptions ?. cellString ,
456- ) ;
457-
458- if ( smartWidth ) {
459- const width = Math . max ( stringToPx ( smartWidth , uniqueId ) + CELL_PADDING_PX , 60 ) ;
460- acc [ columnIdOrAccessor ] = {
461- minHeaderWidth : width ,
462- fullWidth : width ,
463- } ;
464- return acc ;
465- }
466-
467- const minHeaderWidth =
468- typeof column . Header === 'string'
469- ? stringToPx ( column . Header , uniqueId , true ) + CELL_PADDING_PX
470- : DEFAULT_COLUMN_WIDTH ;
471-
472- acc [ columnIdOrAccessor ] = {
473- minHeaderWidth,
474- fullWidth : Math . max ( minHeaderWidth , getContentPxAvg ( rowSample , columnIdOrAccessor , uniqueId ) ) ,
475- } ;
476- return acc ;
477- } , { } ) ;
478-
479- let reservedWidth = visibleColumns . reduce ( ( acc , column ) => {
480- const { minHeaderWidth, fullWidth } = columnMeta [ column . id ?? column . accessor ] ;
481- return acc + Math . max ( column . minWidth || 0 , column . width || 0 , minHeaderWidth || 0 , fullWidth ) || 0 ;
482- } , 0 ) ;
483-
484- let availableWidth = totalWidth - reservedWidth ;
485-
486- if ( availableWidth > 0 ) {
487- let notReservedCount = 0 ;
488- reservedWidth = visibleColumns . reduce ( ( acc , column ) => {
489- const reserved = Math . max ( column . minWidth || 0 , column . width || 0 ) || 0 ;
490- if ( ! reserved ) {
491- notReservedCount ++ ;
492- }
493- return acc + reserved ;
494- } , 0 ) ;
495- availableWidth = totalWidth - reservedWidth ;
496-
497- return columns . map ( ( column ) => {
498- const isColumnVisible = ( column . isVisible ?? true ) && ! hiddenColumns . includes ( column . id ?? column . accessor ) ;
499- const meta = columnMeta [ column . id ?? ( column . accessor as string ) ] ;
500-
501- if ( isColumnVisible && meta ) {
502- const { minHeaderWidth } = meta ;
503-
504- const targetWidth = availableWidth / notReservedCount ;
505-
506- return {
507- ...column ,
508- width : column . width ?? Math . min ( targetWidth , MAX_WIDTH ) ,
509- minWidth : column . minWidth ?? minHeaderWidth ,
510- } ;
511- }
512- return column ;
513- } ) ;
514- }
515-
516- return columns . map ( ( column ) => {
517- const isColumnVisible = ( column . isVisible ?? true ) && ! hiddenColumns . includes ( column . id ?? column . accessor ) ;
518- const meta = columnMeta [ column . id ?? ( column . accessor as string ) ] ;
519- if ( isColumnVisible && meta ) {
520- const { fullWidth } = meta ;
521- return {
522- ...column ,
523- width : column . width ?? fullWidth ,
524- maxWidth : column . maxWidth ?? MAX_WIDTH ,
525- } ;
526- }
527- return column ;
528- } ) ;
529502} ;
530503
531504export const useDynamicColumnWidths = ( hooks : ReactTableHooks ) => {
0 commit comments