Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Commit 8a555ca

Browse files
Issue 803 - Fix scroll alignment on scroll/hover, edit, navigate (#806)
1 parent 8de8e0e commit 8a555ca

File tree

4 files changed

+101
-43
lines changed

4 files changed

+101
-43
lines changed

.Rbuildignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ demo/.*\.js
2020
demo/.*\.html
2121
demo/.*\.css
2222

23-
# ignore python files/folders
23+
# ignore Python files/folders
2424
setup.py
2525
usage.py
2626
setup.py

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [Unreleased]
6+
### Fixed
7+
- [#806](https://github.com/plotly/dash-table/pull/806) Fix a bug where fixed rows a misaligned after navigating or editing cells [#803](https://github.com/plotly/dash-table/issues/803)
8+
59
## [4.8.1] - 2020-06-19
610
### Fixed
711
- [#798](https://github.com/plotly/dash-table/pull/798) Fix a bug where headers are not aligned with columns after an update [#797](https://github.com/plotly/dash-table/issues/797)

src/dash-table/components/ControlledTable/index.tsx

+13-13
Original file line numberDiff line numberDiff line change
@@ -385,22 +385,22 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
385385
// Force first column containers width to match visible portion of table
386386
r0c0.style.width = `${lastFixedTdRight}px`;
387387
r1c0.style.width = `${lastFixedTdRight}px`;
388-
389-
if (!cycle) {
390-
// Force second column containers width to match visible portion of table
391-
const firstVisibleTd = r1c1.querySelector(`tr:first-of-type > *:nth-of-type(${fixed_columns + 1})`);
392-
if (firstVisibleTd) {
393-
const r1c1FragmentBounds = r1c1.getBoundingClientRect();
394-
const firstTdBounds = firstVisibleTd.getBoundingClientRect();
395-
396-
const width = firstTdBounds.left - r1c1FragmentBounds.left;
397-
r0c1.style.marginLeft = `-${width}px`;
398-
r1c1.style.marginLeft = `-${width}px`;
399-
}
400-
}
401388
}
402389
}
403390

391+
// Force second column containers width to match visible portion of table
392+
const firstVisibleTd = r1c1.querySelector(`tr:first-of-type > *:nth-of-type(${fixed_columns + 1})`);
393+
if (firstVisibleTd) {
394+
const r1c1FragmentBounds = r1c1.getBoundingClientRect();
395+
const firstTdBounds = firstVisibleTd.getBoundingClientRect();
396+
397+
const width = firstTdBounds.left - r1c1FragmentBounds.left;
398+
const { r1 } = this.refs as Refs;
399+
400+
r0c1.style.marginLeft = `-${width + r1.scrollLeft}px`;
401+
r1c1.style.marginLeft = `-${width}px`;
402+
}
403+
404404
if (!cycle) {
405405
const currentWidth = parseInt(currentTableWidth, 10);
406406
const nextWidth = parseInt(getComputedStyle(r1c1Table).width, 10);

tests/selenium/test_scrolling.py

+83-29
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,40 @@
44

55
import pandas as pd
66
import pytest
7+
from selenium.webdriver.common.keys import Keys
78

89
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/solar.csv")
910

11+
base_props = dict(
12+
id="table",
13+
columns=[{"name": i, "id": i} for i in df.columns],
14+
row_selectable="single",
15+
row_deletable=True,
16+
data=df.to_dict("records"),
17+
editable=True,
18+
fixed_rows={"headers": True, "data": 1},
19+
style_cell=dict(width=150),
20+
style_table=dict(width=500),
21+
)
22+
23+
24+
def get_margin(test):
25+
return test.driver.execute_script(
26+
"return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-1')).marginLeft);"
27+
)
28+
29+
30+
def get_scroll(test):
31+
return test.driver.execute_script(
32+
"return document.querySelector('#table .row-1').scrollLeft;"
33+
)
34+
35+
36+
def scroll_by(test, value):
37+
test.driver.execute_script(
38+
"document.querySelector('#table .row-1').scrollBy({}, 0);".format(value)
39+
)
40+
1041

1142
@pytest.mark.parametrize(
1243
"fixed_rows",
@@ -24,17 +55,6 @@
2455
"ops", [dict(), dict(row_selectable="single", row_deletable=True)]
2556
)
2657
def test_scrol001_fixed_alignment(test, fixed_rows, fixed_columns, ops):
27-
base_props = dict(
28-
id="table",
29-
columns=[{"name": i, "id": i} for i in df.columns],
30-
row_selectable="single",
31-
row_deletable=True,
32-
data=df.to_dict("records"),
33-
fixed_rows={"headers": True, "data": 1},
34-
style_cell=dict(width=150),
35-
style_table=dict(width=500),
36-
)
37-
3858
props = {**base_props, **fixed_rows, **fixed_columns, **ops}
3959

4060
app = dash.Dash(__name__)
@@ -48,32 +68,66 @@ def test_scrol001_fixed_alignment(test, fixed_rows, fixed_columns, ops):
4868
fixed_width = test.driver.execute_script(
4969
"return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-0')).width) || 0;"
5070
)
51-
margin_left = test.driver.execute_script(
52-
"return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-1')).marginLeft);"
53-
)
5471

55-
assert -margin_left == fixed_width
72+
assert -get_margin(test) == fixed_width
5673

57-
test.driver.execute_script(
58-
"document.querySelector('#table .row-1').scrollBy(200, 0);"
74+
scroll_by(test, 200)
75+
76+
wait.until(
77+
lambda: -get_margin(test) == fixed_width + 200, 3,
5978
)
6079

80+
scroll_by(test, -200)
81+
6182
wait.until(
62-
lambda: -test.driver.execute_script(
63-
"return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-1')).marginLeft);"
64-
)
65-
== fixed_width + 200,
66-
3,
83+
lambda: -get_margin(test) == fixed_width, 3,
6784
)
6885

69-
test.driver.execute_script(
70-
"document.querySelector('#table .row-1').scrollBy(-200, 0);"
86+
87+
@pytest.mark.parametrize(
88+
"fixed_rows",
89+
[dict(fixed_rows=dict(headers=True)), dict(fixed_rows=dict(headers=True, data=1))],
90+
)
91+
@pytest.mark.parametrize(
92+
"fixed_columns",
93+
[
94+
dict(),
95+
dict(fixed_columns=dict(headers=True)),
96+
dict(fixed_columns=dict(headers=True, data=1)),
97+
],
98+
)
99+
@pytest.mark.parametrize(
100+
"ops", [dict(), dict(row_selectable="single", row_deletable=True)]
101+
)
102+
def test_scrol002_edit_navigate(test, fixed_rows, fixed_columns, ops):
103+
props = {**base_props, **fixed_rows, **fixed_columns, **ops}
104+
105+
app = dash.Dash(__name__)
106+
app.layout = DataTable(**props)
107+
108+
test.start_server(app)
109+
110+
target = test.table("table")
111+
assert target.is_ready()
112+
113+
fixed_width = test.driver.execute_script(
114+
"return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-0')).width) || 0;"
71115
)
72116

117+
scroll_by(test, 200)
118+
119+
# alignment is ok after editing a cell
120+
target.cell(0, 3).click()
121+
test.send_keys("abc" + Keys.ENTER)
122+
123+
wait.until(lambda: target.cell(1, 3).is_selected(), 3)
124+
wait.until(lambda: -get_margin(test) == fixed_width + get_scroll(test), 3)
125+
126+
# alignment is ok after navigating
127+
test.send_keys(Keys.ARROW_DOWN)
128+
test.send_keys(Keys.ARROW_RIGHT)
129+
130+
wait.until(lambda: target.cell(2, 4).is_selected(), 3)
73131
wait.until(
74-
lambda: -test.driver.execute_script(
75-
"return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-1')).marginLeft);"
76-
)
77-
== fixed_width,
78-
3,
132+
lambda: -get_margin(test) == fixed_width + get_scroll(test), 3,
79133
)

0 commit comments

Comments
 (0)