Skip to content

Commit 050abbb

Browse files
authored
fix: scroll on open (#157)
* fix: scroll on open * test: Add test case
1 parent 1617dcc commit 050abbb

File tree

4 files changed

+85
-18
lines changed

4 files changed

+85
-18
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"dayjs": "^1.8.30",
4545
"moment": "^2.24.0",
4646
"rc-trigger": "^5.0.4",
47-
"rc-util": "^5.0.1",
47+
"rc-util": "^5.4.0",
4848
"shallowequal": "^1.1.0"
4949
},
5050
"engines": {
@@ -76,7 +76,7 @@
7676
"react": "^16.0.0",
7777
"react-dom": "^16.0.0",
7878
"react-test-renderer": "^16.0.0",
79-
"typescript": "^4.0.2"
79+
"typescript": "^3.9.0"
8080
},
8181
"cnpm": {
8282
"mode": "npm"

src/panels/TimePanel/TimeUnitColumn.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
2+
import { useRef, useLayoutEffect } from 'react';
23
import classNames from 'classnames';
3-
import { scrollTo } from '../../utils/uiUtil';
4+
import { scrollTo, waitElementReady } from '../../utils/uiUtil';
45
import PanelContext from '../../PanelContext';
56

67
export interface Unit {
@@ -19,35 +20,35 @@ export interface TimeUnitColumnProps {
1920
}
2021

2122
function TimeUnitColumn(props: TimeUnitColumnProps) {
22-
const {
23-
prefixCls,
24-
units,
25-
onSelect,
26-
value,
27-
active,
28-
hideDisabledOptions,
29-
} = props;
23+
const { prefixCls, units, onSelect, value, active, hideDisabledOptions } = props;
3024
const cellPrefixCls = `${prefixCls}-cell`;
3125
const { open } = React.useContext(PanelContext);
3226

33-
const ulRef = React.useRef<HTMLUListElement>(null);
34-
const liRefs = React.useRef<Map<number, HTMLElement | null>>(new Map());
27+
const ulRef = useRef<HTMLUListElement>(null);
28+
const liRefs = useRef<Map<number, HTMLElement | null>>(new Map());
29+
const scrollRef = useRef<Function>();
3530

3631
// `useLayoutEffect` here to avoid blink by duration is 0
37-
React.useLayoutEffect(() => {
32+
useLayoutEffect(() => {
3833
const li = liRefs.current.get(value!);
3934
if (li && open !== false) {
4035
scrollTo(ulRef.current!, li.offsetTop, 120);
4136
}
4237
}, [value]);
4338

44-
React.useLayoutEffect(() => {
39+
useLayoutEffect(() => {
4540
if (open) {
4641
const li = liRefs.current.get(value!);
4742
if (li) {
48-
scrollTo(ulRef.current!, li.offsetTop, 0);
43+
scrollRef.current = waitElementReady(li, () => {
44+
scrollTo(ulRef.current!, li.offsetTop, 0);
45+
});
4946
}
5047
}
48+
49+
return () => {
50+
scrollRef.current?.();
51+
};
5152
}, [open]);
5253

5354
return (
@@ -58,15 +59,15 @@ function TimeUnitColumn(props: TimeUnitColumnProps) {
5859
ref={ulRef}
5960
style={{ position: 'relative' }}
6061
>
61-
{units!.map(unit => {
62+
{units!.map((unit) => {
6263
if (hideDisabledOptions && unit.disabled) {
6364
return null;
6465
}
6566

6667
return (
6768
<li
6869
key={unit.value}
69-
ref={element => {
70+
ref={(element) => {
7071
liRefs.current.set(unit.value, element);
7172
}}
7273
className={classNames(cellPrefixCls, {

src/utils/uiUtil.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
import KeyCode from 'rc-util/lib/KeyCode';
2+
import raf from 'rc-util/lib/raf';
3+
import isVisible from 'rc-util/lib/Dom/isVisible';
24
import { GenerateConfig } from '../generate';
35
import { CustomFormat, PanelMode, PickerMode } from '../interface';
46

57
const scrollIds = new Map<HTMLElement, number>();
68

9+
/** Trigger when element is visible in view */
10+
export function waitElementReady(element: HTMLElement, callback: () => void): () => void {
11+
let id: number;
12+
13+
function tryOrNextFrame() {
14+
if (isVisible(element)) {
15+
callback();
16+
} else {
17+
id = raf(() => {
18+
tryOrNextFrame();
19+
});
20+
}
21+
}
22+
23+
tryOrNextFrame();
24+
25+
return () => {
26+
raf.cancel(id);
27+
};
28+
}
29+
730
/* eslint-disable no-param-reassign */
831
export function scrollTo(element: HTMLElement, to: number, duration: number) {
932
if (scrollIds.get(element)) {

tests/picker.spec.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,4 +823,47 @@ describe('Picker.Basic', () => {
823823
expect(wrapper.find('.rc-picker-input').hasClass('rc-picker-input-placeholder')).toBeFalsy();
824824
});
825825
});
826+
827+
describe('time picker open to scroll', () => {
828+
let domMock: ReturnType<typeof spyElementPrototypes>;
829+
let canBeSeen = false;
830+
let triggered = false;
831+
832+
beforeAll(() => {
833+
domMock = spyElementPrototypes(HTMLElement, {
834+
offsetParent: {
835+
get: () => {
836+
if (canBeSeen) {
837+
return {};
838+
}
839+
canBeSeen = true;
840+
return null;
841+
},
842+
},
843+
scrollTop: {
844+
get: () => 0,
845+
set: () => {
846+
triggered = true;
847+
},
848+
},
849+
});
850+
});
851+
852+
afterAll(() => {
853+
domMock.mockRestore();
854+
});
855+
856+
it('work', () => {
857+
jest.useFakeTimers();
858+
const wrapper = mount(
859+
<MomentPicker picker="time" defaultValue={getMoment('2020-07-22 09:03:28')} open />,
860+
);
861+
jest.runAllTimers();
862+
863+
expect(triggered).toBeTruthy();
864+
865+
jest.useRealTimers();
866+
wrapper.unmount();
867+
});
868+
});
826869
});

0 commit comments

Comments
 (0)