@@ -48,6 +48,9 @@ const INNER_STYLE = {
48
48
minWidth : '100%'
49
49
} ;
50
50
51
+ const WIDTH_EPSILON = 0.5 ;
52
+ const MAX_WIDTH_ITERATIONS = 30 ;
53
+
51
54
export default class ControlledTable extends PureComponent < ControlledTableProps > {
52
55
private readonly menuRef = React . createRef < HTMLDivElement > ( ) ;
53
56
private readonly stylesheet : Stylesheet = new Stylesheet ( `#${ this . props . id } ` ) ;
@@ -134,7 +137,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
134
137
setProps ( { active_cell : selected_cells [ 0 ] } ) ;
135
138
}
136
139
137
- this . applyStyle ( ) ;
140
+ this . updateUiViewport ( ) ;
138
141
this . handleResize ( ) ;
139
142
}
140
143
@@ -146,7 +149,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
146
149
147
150
componentDidUpdate ( ) {
148
151
this . updateStylesheet ( ) ;
149
- this . applyStyle ( ) ;
152
+ this . updateUiViewport ( ) ;
150
153
this . handleResize ( ) ;
151
154
this . handleDropdown ( ) ;
152
155
this . adjustTooltipPosition ( ) ;
@@ -226,6 +229,8 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
226
229
227
230
handleResize = ( force : boolean = false ) => {
228
231
const {
232
+ fixed_columns,
233
+ fixed_rows,
229
234
forcedResizeOnly,
230
235
setState
231
236
} = this . props ;
@@ -244,6 +249,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
244
249
245
250
const { r0c0, r0c1, r1c0, r1c1 } = this . refs as { [ key : string ] : HTMLElement } ;
246
251
252
+
247
253
// Adjust [fixed columns/fixed rows combo] to fixed rows height
248
254
let trs = r0c1 . querySelectorAll ( 'tr' ) ;
249
255
Array . from ( r0c0 . querySelectorAll ( 'tr' ) ) . forEach ( ( tr , index ) => {
@@ -261,12 +267,77 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
261
267
tr . style . height = getComputedStyle ( tr2 ) . height ;
262
268
} ) ;
263
269
264
- // Adjust fixed columns data to data height
265
- const contentTd = r1c1 . querySelector ( 'tr > td:first-of-type' ) ;
266
- if ( contentTd ) {
267
- const contentTr = contentTd . parentElement as HTMLElement ;
270
+ if ( fixed_columns ) {
271
+ const r1c0Table = r1c0 . querySelector ( 'table' ) as HTMLElement ;
272
+ const r1c1Table = r1c0 . querySelector ( 'table' ) as HTMLElement ;
273
+
274
+ r1c0Table . style . width = getComputedStyle ( r1c1Table ) . width ;
275
+
276
+ const lastVisibleTd = r1c0 . querySelector ( `tr:first-of-type > *:nth-of-type(${ fixed_columns } )` ) ;
277
+
278
+ let it = 0 ;
279
+ let currentWidth = r1c0 . getBoundingClientRect ( ) . width ;
280
+ let lastWidth = currentWidth ;
281
+
282
+ do {
283
+ lastWidth = currentWidth
284
+
285
+ // Force first column containers width to match visible portion of table
286
+ if ( lastVisibleTd ) {
287
+ const r1c0FragmentBounds = r1c0 . getBoundingClientRect ( ) ;
288
+ const lastTdBounds = lastVisibleTd . getBoundingClientRect ( ) ;
289
+ currentWidth = lastTdBounds . right - r1c0FragmentBounds . left ;
290
+
291
+ const width = `${ currentWidth } px` ;
292
+
293
+ r0c0 . style . width = width ;
294
+ r1c0 . style . width = width ;
295
+ }
296
+
297
+ // Force second column containers width to match visible portion of table
298
+ const firstVisibleTd = r1c1 . querySelector ( `tr:first-of-type > *:nth-of-type(${ fixed_columns + 1 } )` ) ;
299
+ if ( firstVisibleTd ) {
300
+ const r1c1FragmentBounds = r1c1 . getBoundingClientRect ( ) ;
301
+ const firstTdBounds = firstVisibleTd . getBoundingClientRect ( ) ;
302
+
303
+ const width = firstTdBounds . left - r1c1FragmentBounds . left ;
304
+ r0c1 . style . marginLeft = `-${ width } px` ;
305
+ r0c1 . style . marginRight = `${ width } px` ;
306
+ r1c1 . style . marginLeft = `-${ width } px` ;
307
+ r1c1 . style . marginRight = `${ width } px` ;
308
+ }
268
309
269
- this . stylesheet . setRule ( '.dash-fixed-column tr' , `height: ${ getComputedStyle ( contentTr ) . height } ;` ) ;
310
+ it ++ ;
311
+ } while (
312
+ Math . abs ( currentWidth - lastWidth ) > WIDTH_EPSILON ||
313
+ it < MAX_WIDTH_ITERATIONS
314
+ )
315
+ }
316
+
317
+ if ( fixed_columns || fixed_rows ) {
318
+ const r1c0CellWidths = Array . from (
319
+ r1c0 . querySelectorAll ( 'table.cell-table > tbody > tr:first-of-type > *' )
320
+ ) . map ( c => c . getBoundingClientRect ( ) . width ) ;
321
+
322
+ const r1c1CellWidths = Array . from (
323
+ r1c1 . querySelectorAll ( 'table.cell-table > tbody > tr:first-of-type > *' )
324
+ ) . map ( c => c . getBoundingClientRect ( ) . width ) ;
325
+
326
+ Array . from < HTMLElement > (
327
+ r0c0 . querySelectorAll ( 'table.cell-table > tbody > tr:first-of-type > *' )
328
+ ) . forEach ( ( c , i ) => this . setCellWidth ( c , r1c0CellWidths [ i ] ) ) ;
329
+
330
+ Array . from < HTMLElement > (
331
+ r0c0 . querySelectorAll ( 'table.cell-table > tbody > tr:last-of-type > *' )
332
+ ) . forEach ( ( c , i ) => this . setCellWidth ( c , r1c0CellWidths [ i ] ) ) ;
333
+
334
+ Array . from < HTMLElement > (
335
+ r0c1 . querySelectorAll ( 'table.cell-table > tbody > tr:first-of-type > *' )
336
+ ) . forEach ( ( c , i ) => this . setCellWidth ( c , r1c1CellWidths [ i ] ) ) ;
337
+
338
+ Array . from < HTMLElement > (
339
+ r0c1 . querySelectorAll ( 'table.cell-table > tbody > tr:last-of-type > *' )
340
+ ) . forEach ( ( c , i ) => this . setCellWidth ( c , r1c1CellWidths [ i ] ) ) ;
270
341
}
271
342
}
272
343
@@ -627,98 +698,20 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
627
698
) || page_action === TableAction . Custom ;
628
699
}
629
700
630
- applyStyle = ( ) => {
631
- const {
632
- fixed_columns,
633
- fixed_rows,
634
- row_deletable,
635
- row_selectable
636
- } = this . props ;
637
-
638
- const { r1c0, r1c1 } = this . refs as { [ key : string ] : HTMLElement } ;
639
-
640
- this . updateUiViewport ( ) ;
641
-
642
- if ( row_deletable ) {
643
- this . stylesheet . setRule (
644
- `.dash-spreadsheet-inner td.dash-delete-cell` ,
645
- `width: 30px; max-width: 30px; min-width: 30px;`
646
- ) ;
647
- }
648
-
649
- if ( row_selectable ) {
650
- this . stylesheet . setRule (
651
- `.dash-spreadsheet-inner td.dash-select-cell` ,
652
- `width: 30px; max-width: 30px; min-width: 30px;`
653
- ) ;
654
- }
655
-
656
- // Adjust the width of the fixed row header
657
- if ( fixed_rows ) {
658
- Array . from ( r1c1 . querySelectorAll ( 'tr:first-of-type td.dash-cell, tr:first-of-type th.dash-header' ) ) . forEach ( td => {
659
- const classname = td . className . split ( ' ' ) [ 1 ] ;
660
- const style = getComputedStyle ( td ) ;
661
- const width = style . width ;
662
-
663
- this . stylesheet . setRule (
664
- `.dash-fixed-row:not(.dash-fixed-column) th.${ classname } ` ,
665
- `width: ${ width } !important; min-width: ${ width } !important; max-width: ${ width } !important;`
666
- ) ;
667
- } ) ;
668
- }
669
-
670
- // Adjust the width of the fixed row / fixed columns header
671
- if ( fixed_columns && fixed_rows ) {
672
- Array . from ( r1c0 . querySelectorAll ( 'tr:first-of-type td.dash-cell, tr:first-of-type th.dash-header' ) ) . forEach ( td => {
673
- const classname = td . className . split ( ' ' ) [ 1 ] ;
674
- const style = getComputedStyle ( td ) ;
675
- const width = style . width ;
676
-
677
- this . stylesheet . setRule (
678
- `.dash-fixed-column.dash-fixed-row th.${ classname } ` ,
679
- `width: ${ width } !important; min-width: ${ width } !important; max-width: ${ width } !important;`
680
- ) ;
681
- } ) ;
682
- }
683
-
684
- // Adjust widths of row deletable/row selectable headers
685
- const subTable = fixed_rows && ! fixed_columns ? r1c1 : r1c0 ;
686
-
687
- if ( row_deletable ) {
688
- Array . from ( subTable . querySelectorAll ( 'tr:first-of-type td.dash-delete-cell' ) ) . forEach ( td => {
689
- const style = getComputedStyle ( td ) ;
690
- const width = style . width ;
691
-
692
- this . stylesheet . setRule (
693
- 'th.dash-delete-header' ,
694
- `width: ${ width } !important; min-width: ${ width } !important; max-width: ${ width } !important;`
695
- ) ;
696
- } ) ;
697
- }
698
- if ( row_selectable ) {
699
- Array . from ( subTable . querySelectorAll ( 'tr:first-of-type td.dash-select-cell' ) ) . forEach ( td => {
700
- const style = getComputedStyle ( td ) ;
701
- const width = style . width ;
702
-
703
- this . stylesheet . setRule (
704
- 'th.dash-select-header' ,
705
- `width: ${ width } !important; min-width: ${ width } !important; max-width: ${ width } !important;`
706
- ) ;
707
- } ) ;
708
- }
709
- }
710
-
711
701
handleDropdown = ( ) => {
712
702
const { r1c1 } = this . refs as { [ key : string ] : HTMLElement } ;
713
703
714
704
dropdownHelper ( r1c1 . querySelector ( '.Select-menu-outer' ) ) ;
715
705
}
716
706
717
707
onScroll = ( ev : any ) => {
718
- const { r0c1 } = this . refs as { [ key : string ] : HTMLElement } ;
708
+ const { r0c0 , r0c1 } = this . refs as { [ key : string ] : HTMLElement } ;
719
709
720
710
Logger . trace ( `ControlledTable fragment scrolled to (left,top)=(${ ev . target . scrollLeft } ,${ ev . target . scrollTop } )` ) ;
721
- r0c1 . style . marginLeft = `${ - ev . target . scrollLeft } px` ;
711
+
712
+ const margin = parseFloat ( ev . target . scrollLeft ) + parseFloat ( r0c0 . style . width ) ;
713
+
714
+ r0c1 . style . marginLeft = `${ - margin } px` ;
722
715
723
716
this . updateUiViewport ( ) ;
724
717
this . handleDropdown ( ) ;
@@ -952,6 +945,25 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
952
945
}
953
946
}
954
947
948
+ private setCellWidth ( cell : HTMLElement , width : number ) {
949
+ cell . style . width = `${ width } px` ;
950
+ cell . style . minWidth = `${ width } px` ;
951
+ cell . style . maxWidth = `${ width } px` ;
952
+ cell . style . boxSizing = 'border-box' ;
953
+
954
+ /**
955
+ * Some browsers handle `th` and `td` size inconsistently.
956
+ * Checking the size delta and adjusting for it (different handling of padding and borders)
957
+ * allows the table to make sure all sections are correctly aligned.
958
+ */
959
+ const delta = cell . getBoundingClientRect ( ) . width - width ;
960
+ if ( delta ) {
961
+ cell . style . width = `${ width - delta } px` ;
962
+ cell . style . minWidth = `${ width - delta } px` ;
963
+ cell . style . maxWidth = `${ width - delta } px` ;
964
+ }
965
+ }
966
+
955
967
private get showToggleColumns ( ) : boolean {
956
968
const {
957
969
columns,
0 commit comments