Repository navigation
perf(core): a glide in a long list lays the page out once, not once per moving element #8639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5b0abfb
b476220
52c08dc
7cfe5e0
e2afc61
dc023f3
b60f171
ac58ce3
427f850
1bccf04
b894315
ea01d0c
186526e
5bcb797
7bad709
3a5f9ae
7e13143
b3826b7
f8c2db7
044b865
52863fc
1c44863
a09ce74
980ff13
2f7cedb
f97811a
52841d7
86a458b
bc32d66
ac812d7
e4b19b0
731af79
c08d59b
7c96972
7ca7a04
5695f69
d2a56cd
e43e328
24eaa87
8338da9
3ea5336
3a9db0d
aaa7e68
007f414
36c6fd8
7e54a7d
e45ef26
6ab36a8
186bd96
7743999
c128ce2
9fadd72
0744d42
73d7b43
60d3e00
71abedd
d2dfc6f
51d65e7
2ae8a54
8f177d5
9012c84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [perf] With an `animatedLayout` handle, a Collapsible or TreeList change in a long list reads every row's place before any glide starts, so the page is laid out once rather than once per gliding row. | ||
|
|
||
| @cixzhang |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -194,6 +194,43 @@ describe('the walk (spec:AST-105 FR3, Coverage)', () => { | |
| } | ||
| }); | ||
|
|
||
| it('reads every follower (place, transform, scale) before it starts any glide, so a long page is laid out once', async () => { | ||
| const {area, changed, phase} = setUp( | ||
| '<div id="area"><div id="changed"></div><div id="a"></div><div id="b"></div><div id="c"></div></div>', | ||
| ); | ||
| for (const [id, y] of [ | ||
| ['a', 0], | ||
| ['b', 40], | ||
| ['c', 80], | ||
| ] as const) { | ||
| placeAt( | ||
| document.getElementById(id) as HTMLElement, | ||
| {before: [0, y, 400, 40], after: [0, y + 80, 400, 40]}, | ||
| phase, | ||
| ); | ||
| } | ||
| // A style or layout read after a glide started lays the page out again. | ||
| const readsAfterAGlide: string[] = []; | ||
| const computed = window.getComputedStyle.bind(window); | ||
| vi.spyOn(window, 'getComputedStyle').mockImplementation( | ||
|
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 spy only sees the transform read. With |
||
| (element: Element, pseudo?: string | null) => { | ||
| if (played.some(p => p.keyframes[0].translate != null)) { | ||
| readsAfterAGlide.push((element as HTMLElement).id); | ||
| } | ||
| return computed(element, pseudo); | ||
| }, | ||
| ); | ||
| await runChange( | ||
| createAnimatedLayout(() => area), | ||
| changed, | ||
| phase, | ||
| ); | ||
| expect(played.filter(p => p.keyframes[0].translate != null)).toHaveLength( | ||
| 3, | ||
| ); | ||
| expect(readsAfterAGlide).toEqual([]); | ||
| }); | ||
|
|
||
| it('takes the children of a display: contents wrapper and skips absolute, fixed, hidden and display: none elements', async () => { | ||
| const {area, changed, phase} = setUp( | ||
| `<div id="area"><div id="changed"></div> | ||
|
|
@@ -523,6 +560,16 @@ describe('the changed element exits (spec:AST-105 FR4)', () => { | |
| expect(content.style.pointerEvents).toBe(''); | ||
| }); | ||
|
|
||
| it('guesses, then corrects, where a negative margin would collapse with its end margin', () => { | ||
| const {content, after, layout} = exitSetUp(); | ||
| after.style.marginTop = '-4px'; | ||
| const reads = vi.mocked(after.getBoundingClientRect); | ||
| reads.mockClear(); | ||
| expect(holdLayoutExit(layout, content, vi.fn())).toBe(true); | ||
| expect(reads.mock.calls.length).toBeGreaterThan(2); | ||
| expect(content.style.getPropertyValue('margin-block-end')).toBe('-88px'); | ||
| }); | ||
|
|
||
| it('stops an exit when the content opens again', () => { | ||
| const {content, layout} = exitSetUp(); | ||
| const done = vi.fn(); | ||
|
|
@@ -545,7 +592,8 @@ describe('the changed element exits (spec:AST-105 FR4)', () => { | |
| }); | ||
|
|
||
| it('lands at once where no end margin frees its space (a grid track)', () => { | ||
| const {content, after, layout} = exitSetUp(); | ||
| const {area, content, after, layout} = exitSetUp(); | ||
| area.style.display = 'grid'; | ||
| // The row after it ignores the margin: it moves only when the content | ||
| // is hidden. | ||
| vi.spyOn(after, 'getBoundingClientRect').mockImplementation( | ||
|
|
@@ -567,7 +615,9 @@ describe('the changed element exits (spec:AST-105 FR4)', () => { | |
| }); | ||
|
|
||
| it("a refused hold keeps the element's own end margin and lets the content after it take its place at once", async () => { | ||
| const {content, after, layout} = exitSetUp(); | ||
| const {area, content, after, layout} = exitSetUp(); | ||
| // A grid track, which no end margin frees. | ||
| area.style.display = 'grid'; | ||
| content.style.setProperty('margin-block-end', '12px'); | ||
| vi.spyOn(after, 'getBoundingClientRect').mockImplementation( | ||
| () => | ||
|
|
||
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.
This case can't fail as written: in Chromium the hold is refused here, on the base as well, since one correction moves
#ato 207.8 px against a 178 px target. Soif (result.held)below skips the landing check, andhiddenis read with#contentatdisplay: none, the same statetargetwas read in.expect(result.held).toBe(false)plus a check that#ahas no glide would pin the refusal; otherwise the case could go.