Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 72
Issue 649 - Fix mismatched row height for fixed columns#722
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
a58d4eb329f357ddf6079a7bacbe02a851ebdb836630f6aaac059545ca4ffb27faf7797b465c76f68a9d06f606231d3f8b1a821fecaee5e2c8a1ae14928631907bca183882f19d6fb3531ac90a717bb093dd7b2d61eb050a5845bab809fe18d1840178f8fd015327a4621d66bf131359b9967ec8523a76988ae8537c569226e6908144a6165b5ca8cf0bf0eaf0d72aa75bd7e69c043bd104ff5333db6f3034db513d0b8c859ff242207c90a99136c4File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -48,6 +48,9 @@ const INNER_STYLE = { | ||
| minWidth: '100%' | ||
| }; | ||
| const WIDTH_EPSILON = 0.5; | ||
| const MAX_WIDTH_ITERATIONS = 30; | ||
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 30 is a very high number but most of the time it takes 2-6 iterations for the table's column width to settle. | ||
| export default class ControlledTable extends PureComponent<ControlledTableProps> { | ||
| private readonly menuRef = React.createRef<HTMLDivElement>(); | ||
| private readonly stylesheet: Stylesheet = new Stylesheet(`#${this.props.id}`); | ||
| @@ -134,7 +137,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps> | ||
| setProps({ active_cell: selected_cells[0] }); | ||
| } | ||
| this.applyStyle(); | ||
| this.updateUiViewport(); | ||
| this.handleResize(); | ||
| } | ||
| @@ -146,7 +149,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps> | ||
| componentDidUpdate() { | ||
| this.updateStylesheet(); | ||
| this.applyStyle(); | ||
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
| ||
| this.updateUiViewport(); | ||
| this.handleResize(); | ||
| this.handleDropdown(); | ||
| this.adjustTooltipPosition(); | ||
| @@ -226,6 +229,8 @@ export default class ControlledTable extends PureComponent<ControlledTableProps> | ||
| handleResize = (force: boolean = false) => { | ||
| const { | ||
| fixed_columns, | ||
| fixed_rows, | ||
| forcedResizeOnly, | ||
| setState | ||
| } = this.props; | ||
| @@ -244,6 +249,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps> | ||
| const { r0c0, r0c1, r1c0, r1c1 } = this.refs as { [key: string]: HTMLElement }; | ||
| // Adjust [fixed columns/fixed rows combo] to fixed rows height | ||
| let trs = r0c1.querySelectorAll('tr'); | ||
| Array.from(r0c0.querySelectorAll('tr')).forEach((tr, index) => { | ||
| @@ -261,12 +267,77 @@ export default class ControlledTable extends PureComponent<ControlledTableProps> | ||
| tr.style.height = getComputedStyle(tr2).height; | ||
| }); | ||
| // Adjust fixed columns data to data height | ||
| const contentTd = r1c1.querySelector('tr > td:first-of-type'); | ||
| if (contentTd) { | ||
| const contentTr = contentTd.parentElement as HTMLElement; | ||
| if (fixed_columns) { | ||
| const r1c0Table = r1c0.querySelector('table') as HTMLElement; | ||
| const r1c1Table = r1c0.querySelector('table') as HTMLElement; | ||
| r1c0Table.style.width = getComputedStyle(r1c1Table).width; | ||
| const lastVisibleTd = r1c0.querySelector(`tr:first-of-type > *:nth-of-type(${fixed_columns})`); | ||
| let it = 0; | ||
| let currentWidth = r1c0.getBoundingClientRect().width; | ||
| let lastWidth = currentWidth; | ||
| do { | ||
| lastWidth = currentWidth | ||
| // Force first column containers width to match visible portion of table | ||
| if (lastVisibleTd) { | ||
| const r1c0FragmentBounds = r1c0.getBoundingClientRect(); | ||
| const lastTdBounds = lastVisibleTd.getBoundingClientRect(); | ||
| currentWidth = lastTdBounds.right - r1c0FragmentBounds.left; | ||
| const width = `${currentWidth}px`; | ||
| r0c0.style.width = width; | ||
| r1c0.style.width = width; | ||
| } | ||
| // Force second column containers width to match visible portion of table | ||
| const firstVisibleTd = r1c1.querySelector(`tr:first-of-type > *:nth-of-type(${fixed_columns + 1})`); | ||
| if (firstVisibleTd) { | ||
| const r1c1FragmentBounds = r1c1.getBoundingClientRect(); | ||
| const firstTdBounds = firstVisibleTd.getBoundingClientRect(); | ||
| const width = firstTdBounds.left - r1c1FragmentBounds.left; | ||
| r0c1.style.marginLeft = `-${width}px`; | ||
| r0c1.style.marginRight = `${width}px`; | ||
| r1c1.style.marginLeft = `-${width}px`; | ||
| r1c1.style.marginRight = `${width}px`; | ||
| } | ||
| this.stylesheet.setRule('.dash-fixed-column tr', `height: ${getComputedStyle(contentTr).height};`); | ||
| it++; | ||
| } while ( | ||
| Math.abs(currentWidth - lastWidth) > WIDTH_EPSILON || | ||
| it < MAX_WIDTH_ITERATIONS | ||
| ) | ||
| } | ||
| if (fixed_columns || fixed_rows) { | ||
| const r1c0CellWidths = Array.from( | ||
| r1c0.querySelectorAll('table.cell-table > tbody > tr:first-of-type > *') | ||
| ).map(c => c.getBoundingClientRect().width); | ||
| const r1c1CellWidths = Array.from( | ||
| r1c1.querySelectorAll('table.cell-table > tbody > tr:first-of-type > *') | ||
| ).map(c => c.getBoundingClientRect().width); | ||
| Array.from<HTMLElement>( | ||
| r0c0.querySelectorAll('table.cell-table > tbody > tr:first-of-type > *') | ||
| ).forEach((c, i) => this.setCellWidth(c, r1c0CellWidths[i])); | ||
| Array.from<HTMLElement>( | ||
| r0c0.querySelectorAll('table.cell-table > tbody > tr:last-of-type > *') | ||
| ).forEach((c, i) => this.setCellWidth(c, r1c0CellWidths[i])); | ||
| Array.from<HTMLElement>( | ||
| r0c1.querySelectorAll('table.cell-table > tbody > tr:first-of-type > *') | ||
| ).forEach((c, i) => this.setCellWidth(c, r1c1CellWidths[i])); | ||
| Array.from<HTMLElement>( | ||
| r0c1.querySelectorAll('table.cell-table > tbody > tr:last-of-type > *') | ||
| ).forEach((c, i) => this.setCellWidth(c, r1c1CellWidths[i])); | ||
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All this replaces and adds to the previous | ||
| } | ||
| } | ||
| @@ -627,98 +698,20 @@ export default class ControlledTable extends PureComponent<ControlledTableProps> | ||
| ) || page_action === TableAction.Custom; | ||
| } | ||
| applyStyle = () => { | ||
| const { | ||
| fixed_columns, | ||
| fixed_rows, | ||
| row_deletable, | ||
| row_selectable | ||
| } = this.props; | ||
| const { r1c0, r1c1 } = this.refs as { [key: string]: HTMLElement }; | ||
| this.updateUiViewport(); | ||
| if (row_deletable) { | ||
| this.stylesheet.setRule( | ||
| `.dash-spreadsheet-inner td.dash-delete-cell`, | ||
| `width: 30px; max-width: 30px; min-width: 30px;` | ||
| ); | ||
| } | ||
| if (row_selectable) { | ||
| this.stylesheet.setRule( | ||
| `.dash-spreadsheet-inner td.dash-select-cell`, | ||
| `width: 30px; max-width: 30px; min-width: 30px;` | ||
| ); | ||
| } | ||
| // Adjust the width of the fixed row header | ||
| if (fixed_rows) { | ||
| Array.from(r1c1.querySelectorAll('tr:first-of-type td.dash-cell, tr:first-of-type th.dash-header')).forEach(td => { | ||
| const classname = td.className.split(' ')[1]; | ||
| const style = getComputedStyle(td); | ||
| const width = style.width; | ||
| this.stylesheet.setRule( | ||
| `.dash-fixed-row:not(.dash-fixed-column) th.${classname}`, | ||
| `width: ${width} !important; min-width: ${width} !important; max-width: ${width} !important;` | ||
| ); | ||
| }); | ||
| } | ||
| // Adjust the width of the fixed row / fixed columns header | ||
| if (fixed_columns && fixed_rows) { | ||
| Array.from(r1c0.querySelectorAll('tr:first-of-type td.dash-cell, tr:first-of-type th.dash-header')).forEach(td => { | ||
| const classname = td.className.split(' ')[1]; | ||
| const style = getComputedStyle(td); | ||
| const width = style.width; | ||
| this.stylesheet.setRule( | ||
| `.dash-fixed-column.dash-fixed-row th.${classname}`, | ||
| `width: ${width} !important; min-width: ${width} !important; max-width: ${width} !important;` | ||
| ); | ||
| }); | ||
| } | ||
| // Adjust widths of row deletable/row selectable headers | ||
| const subTable = fixed_rows && !fixed_columns ? r1c1 : r1c0; | ||
| if (row_deletable) { | ||
| Array.from(subTable.querySelectorAll('tr:first-of-type td.dash-delete-cell')).forEach(td => { | ||
| const style = getComputedStyle(td); | ||
| const width = style.width; | ||
| this.stylesheet.setRule( | ||
| 'th.dash-delete-header', | ||
| `width: ${width} !important; min-width: ${width} !important; max-width: ${width} !important;` | ||
| ); | ||
| }); | ||
| } | ||
| if (row_selectable) { | ||
| Array.from(subTable.querySelectorAll('tr:first-of-type td.dash-select-cell')).forEach(td => { | ||
| const style = getComputedStyle(td); | ||
| const width = style.width; | ||
| this.stylesheet.setRule( | ||
| 'th.dash-select-header', | ||
| `width: ${width} !important; min-width: ${width} !important; max-width: ${width} !important;` | ||
| ); | ||
| }); | ||
| } | ||
| } | ||
| handleDropdown = () => { | ||
| const { r1c1 } = this.refs as { [key: string]: HTMLElement }; | ||
| dropdownHelper(r1c1.querySelector('.Select-menu-outer')); | ||
| } | ||
| onScroll = (ev: any) => { | ||
| const { r0c1 } = this.refs as { [key: string]: HTMLElement }; | ||
| const { r0c0, r0c1 } = this.refs as { [key: string]: HTMLElement }; | ||
| Logger.trace(`ControlledTable fragment scrolled to (left,top)=(${ev.target.scrollLeft},${ev.target.scrollTop})`); | ||
| r0c1.style.marginLeft = `${-ev.target.scrollLeft}px`; | ||
| const margin = parseFloat(ev.target.scrollLeft) + parseFloat(r0c0.style.width); | ||
| r0c1.style.marginLeft = `${-margin}px`; | ||
| this.updateUiViewport(); | ||
| this.handleDropdown(); | ||
| @@ -952,6 +945,25 @@ export default class ControlledTable extends PureComponent<ControlledTableProps> | ||
| } | ||
| } | ||
| private setCellWidth(cell: HTMLElement, width: number) { | ||
| cell.style.width = `${width}px`; | ||
| cell.style.minWidth = `${width}px`; | ||
| cell.style.maxWidth = `${width}px`; | ||
| cell.style.boxSizing = 'border-box'; | ||
| /** | ||
| * Some browsers handle `th` and `td` size inconsistently. | ||
| * Checking the size delta and adjusting for it (different handling of padding and borders) | ||
| * allows the table to make sure all sections are correctly aligned. | ||
| */ | ||
| const delta = cell.getBoundingClientRect().width - width; | ||
| if (delta) { | ||
| cell.style.width = `${width - delta}px`; | ||
| cell.style.minWidth = `${width - delta}px`; | ||
| cell.style.maxWidth = `${width - delta}px`; | ||
| } | ||
| } | ||
| private get showToggleColumns(): boolean { | ||
| const { | ||
| columns, | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -8,7 +8,7 @@ import TableClipboardHelper from 'dash-table/utils/TableClipboardHelper'; | ||
| type SetFilter = (ev: any) => void; | ||
| interface IColumnFilterProps { | ||
| classes: string; | ||
| className: string; | ||
| columnId: ColumnId; | ||
| isValid: boolean; | ||
| setFilter: SetFilter; | ||
| @@ -39,15 +39,15 @@ export default class ColumnFilter extends PureComponent<IColumnFilterProps, ISta | ||
| render() { | ||
| const { | ||
| classes, | ||
| className, | ||
| columnId, | ||
| isValid, | ||
| style, | ||
| value | ||
| } = this.props; | ||
| return (<th | ||
| className={classes + (isValid ? '' : ' invalid')} | ||
| className={className + (isValid ? '' : ' invalid')} | ||
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Standardizing prop name across all components simplifies manipulation elsewhere. | ||
| data-dash-column={columnId} | ||
| style={style} | ||
| > | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the mock to have variable height Markdown rows