Skip to content

Commit ef25e95

Browse files
authored
fix: Scroll to with SCU (#11)
* fix: Scroll to with SCU * add comiple ci * typo * fix compiple
1 parent 83c96b4 commit ef25e95

File tree

6 files changed

+157
-124
lines changed

6 files changed

+157
-124
lines changed

.circleci/config.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,24 @@ jobs:
2929
- node_modules
3030
key: v1-dependencies-{{ checksum "package.json" }}
3131
- run: npm test -- --coverage && bash <(curl -s https://codecov.io/bash)
32+
compile:
33+
docker:
34+
- image: circleci/node:latest
35+
steps:
36+
- checkout
37+
- restore_cache:
38+
keys:
39+
- v1-dependencies-{{ checksum "package.json" }}
40+
- run: npm install
41+
- save_cache:
42+
paths:
43+
- node_modules
44+
key: v1-dependencies-{{ checksum "package.json" }}
45+
- run: npm run compile
3246
workflows:
3347
version: 2
3448
build_and_test:
3549
jobs:
3650
- lint
37-
- test
51+
- test
52+
- compile

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ module.exports = {
88
'react/no-did-update-set-state': 0,
99
'react/no-find-dom-node': 0,
1010
'no-dupe-class-members': 0,
11+
'react/sort-comp': 0,
1112
},
1213
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"react-dom": "*"
4343
},
4444
"devDependencies": {
45+
"@types/jest": "^25.1.3",
4546
"@types/react": "^16.8.19",
4647
"@types/react-dom": "^16.8.4",
4748
"@types/warning": "^3.0.0",

src/List.tsx

Lines changed: 124 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -456,143 +456,145 @@ class List<T = any> extends React.Component<ListProps<T>, ListState<T>> {
456456
};
457457

458458
public scrollTo = (arg0: number | ScrollConfig) => {
459-
// Number top
460-
if (typeof arg0 === 'object') {
461-
const { isVirtual } = this.state;
462-
const { height, itemHeight, data } = this.props;
463-
const { align = 'auto' } = arg0;
464-
465-
let index = 0;
466-
if ('index' in arg0) {
467-
({ index } = arg0);
468-
} else if ('key' in arg0) {
469-
const { key } = arg0;
470-
index = data.findIndex(item => this.getItemKey(item) === key);
471-
}
459+
setTimeout(() => {
460+
// Number top
461+
if (typeof arg0 === 'object') {
462+
const { isVirtual } = this.state;
463+
const { height, itemHeight, data } = this.props;
464+
const { align = 'auto' } = arg0;
465+
466+
let index = 0;
467+
if ('index' in arg0) {
468+
({ index } = arg0);
469+
} else if ('key' in arg0) {
470+
const { key } = arg0;
471+
index = data.findIndex(item => this.getItemKey(item) === key);
472+
}
472473

473-
const visibleCount = Math.ceil(height / itemHeight);
474-
const item = data[index];
475-
if (item) {
476-
const { clientHeight } = this.listRef.current;
477-
478-
if (isVirtual) {
479-
// Calculate related data
480-
const { itemIndex, itemOffsetPtg } = this.state;
481-
const { scrollTop } = this.listRef.current;
482-
const scrollPtg = getElementScrollPercentage(this.listRef.current);
483-
484-
const relativeLocatedItemTop = getItemRelativeTop({
485-
itemIndex,
486-
itemOffsetPtg,
487-
itemElementHeights: this.itemElementHeights,
488-
scrollPtg,
489-
clientHeight,
490-
getItemKey: this.getIndexKey,
491-
});
492-
493-
// We will force render related items to collect height for re-location
494-
this.setState(
495-
{
496-
startIndex: Math.max(0, index - visibleCount),
497-
endIndex: Math.min(data.length - 1, index + visibleCount),
498-
},
499-
() => {
500-
this.collectItemHeights();
501-
502-
// Calculate related top
503-
let relativeTop: number;
504-
let mergedAlgin = align;
505-
506-
if (align === 'auto') {
507-
let shouldChange = true;
508-
509-
// Check if exist in the visible range
510-
if (Math.abs(itemIndex - index) < visibleCount) {
511-
let itemTop = relativeLocatedItemTop;
512-
if (index < itemIndex) {
513-
for (let i = index; i < itemIndex; i += 1) {
514-
const eleKey = this.getIndexKey(i);
515-
itemTop -= this.itemElementHeights[eleKey] || 0;
516-
}
517-
} else {
518-
for (let i = itemIndex; i <= index; i += 1) {
519-
const eleKey = this.getIndexKey(i);
520-
itemTop += this.itemElementHeights[eleKey] || 0;
474+
const visibleCount = Math.ceil(height / itemHeight);
475+
const item = data[index];
476+
if (item) {
477+
const { clientHeight } = this.listRef.current;
478+
479+
if (isVirtual) {
480+
// Calculate related data
481+
const { itemIndex, itemOffsetPtg } = this.state;
482+
const { scrollTop } = this.listRef.current;
483+
const scrollPtg = getElementScrollPercentage(this.listRef.current);
484+
485+
const relativeLocatedItemTop = getItemRelativeTop({
486+
itemIndex,
487+
itemOffsetPtg,
488+
itemElementHeights: this.itemElementHeights,
489+
scrollPtg,
490+
clientHeight,
491+
getItemKey: this.getIndexKey,
492+
});
493+
494+
// We will force render related items to collect height for re-location
495+
this.setState(
496+
{
497+
startIndex: Math.max(0, index - visibleCount),
498+
endIndex: Math.min(data.length - 1, index + visibleCount),
499+
},
500+
() => {
501+
this.collectItemHeights();
502+
503+
// Calculate related top
504+
let relativeTop: number;
505+
let mergedAlgin = align;
506+
507+
if (align === 'auto') {
508+
let shouldChange = true;
509+
510+
// Check if exist in the visible range
511+
if (Math.abs(itemIndex - index) < visibleCount) {
512+
let itemTop = relativeLocatedItemTop;
513+
if (index < itemIndex) {
514+
for (let i = index; i < itemIndex; i += 1) {
515+
const eleKey = this.getIndexKey(i);
516+
itemTop -= this.itemElementHeights[eleKey] || 0;
517+
}
518+
} else {
519+
for (let i = itemIndex; i <= index; i += 1) {
520+
const eleKey = this.getIndexKey(i);
521+
itemTop += this.itemElementHeights[eleKey] || 0;
522+
}
521523
}
524+
525+
shouldChange = itemTop <= 0 || itemTop >= clientHeight;
522526
}
523527

524-
shouldChange = itemTop <= 0 || itemTop >= clientHeight;
528+
if (shouldChange) {
529+
// Out of range will fall back to position align
530+
mergedAlgin = index < itemIndex ? 'top' : 'bottom';
531+
} else {
532+
const {
533+
itemIndex: nextIndex,
534+
itemOffsetPtg: newOffsetPtg,
535+
startIndex,
536+
endIndex,
537+
} = getRangeIndex(scrollPtg, data.length, visibleCount);
538+
539+
this.setState({
540+
scrollTop,
541+
itemIndex: nextIndex,
542+
itemOffsetPtg: newOffsetPtg,
543+
startIndex,
544+
endIndex,
545+
});
546+
return;
547+
}
525548
}
526549

527-
if (shouldChange) {
528-
// Out of range will fall back to position align
529-
mergedAlgin = index < itemIndex ? 'top' : 'bottom';
530-
} else {
531-
const {
532-
itemIndex: nextIndex,
533-
itemOffsetPtg: newOffsetPtg,
534-
startIndex,
535-
endIndex,
536-
} = getRangeIndex(scrollPtg, data.length, visibleCount);
537-
538-
this.setState({
539-
scrollTop,
540-
itemIndex: nextIndex,
541-
itemOffsetPtg: newOffsetPtg,
542-
startIndex,
543-
endIndex,
544-
});
545-
return;
550+
// Align with position should make scroll happen
551+
if (mergedAlgin === 'top') {
552+
relativeTop = 0;
553+
} else if (mergedAlgin === 'bottom') {
554+
const eleKey = this.getItemKey(item);
555+
556+
relativeTop = clientHeight - this.itemElementHeights[eleKey] || 0;
546557
}
547-
}
548558

549-
// Align with position should make scroll happen
550-
if (mergedAlgin === 'top') {
551-
relativeTop = 0;
552-
} else if (mergedAlgin === 'bottom') {
553-
const eleKey = this.getItemKey(item);
559+
this.internalScrollTo({
560+
itemIndex: index,
561+
relativeTop,
562+
});
563+
},
564+
);
565+
} else {
566+
// Raw list without virtual scroll set position directly
567+
this.collectItemHeights({ startIndex: 0, endIndex: data.length - 1 });
568+
let mergedAlgin = align;
569+
570+
// Collection index item position
571+
const indexItemHeight = this.itemElementHeights[this.getIndexKey(index)];
572+
let itemTop = 0;
573+
for (let i = 0; i < index; i += 1) {
574+
const eleKey = this.getIndexKey(i);
575+
itemTop += this.itemElementHeights[eleKey] || 0;
576+
}
577+
const itemBottom = itemTop + indexItemHeight;
554578

555-
relativeTop = clientHeight - this.itemElementHeights[eleKey] || 0;
579+
if (mergedAlgin === 'auto') {
580+
if (itemTop < this.listRef.current.scrollTop) {
581+
mergedAlgin = 'top';
582+
} else if (itemBottom > this.listRef.current.scrollTop + clientHeight) {
583+
mergedAlgin = 'bottom';
556584
}
557-
558-
this.internalScrollTo({
559-
itemIndex: index,
560-
relativeTop,
561-
});
562-
},
563-
);
564-
} else {
565-
// Raw list without virtual scroll set position directly
566-
this.collectItemHeights({ startIndex: 0, endIndex: data.length - 1 });
567-
let mergedAlgin = align;
568-
569-
// Collection index item position
570-
const indexItemHeight = this.itemElementHeights[this.getIndexKey(index)];
571-
let itemTop = 0;
572-
for (let i = 0; i < index; i += 1) {
573-
const eleKey = this.getIndexKey(i);
574-
itemTop += this.itemElementHeights[eleKey] || 0;
575-
}
576-
const itemBottom = itemTop + indexItemHeight;
577-
578-
if (mergedAlgin === 'auto') {
579-
if (itemTop < this.listRef.current.scrollTop) {
580-
mergedAlgin = 'top';
581-
} else if (itemBottom > this.listRef.current.scrollTop + clientHeight) {
582-
mergedAlgin = 'bottom';
583585
}
584-
}
585586

586-
if (mergedAlgin === 'top') {
587-
this.listRef.current.scrollTop = itemTop;
588-
} else if (mergedAlgin === 'bottom') {
589-
this.listRef.current.scrollTop = itemTop - (clientHeight - indexItemHeight);
587+
if (mergedAlgin === 'top') {
588+
this.listRef.current.scrollTop = itemTop;
589+
} else if (mergedAlgin === 'bottom') {
590+
this.listRef.current.scrollTop = itemTop - (clientHeight - indexItemHeight);
591+
}
590592
}
591593
}
594+
} else {
595+
this.listRef.current.scrollTop = arg0;
592596
}
593-
} else {
594-
this.listRef.current.scrollTop = arg0;
595-
}
597+
});
596598
};
597599

598600
public internalScrollTo(relativeScroll: RelativeScroll): void {

src/utils/itemUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export function getElementScrollPercentage(element: HTMLElement | null) {
8181
* But if not provided, downgrade to `findDOMNode` to get the real dom element.
8282
*/
8383
export function getNodeHeight(node: HTMLElement) {
84-
const element = findDOMNode(node);
84+
const element = findDOMNode<HTMLElement>(node);
8585
return element ? element.offsetHeight : 0;
8686
}
8787

tests/scroll.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ function genData(count) {
88
}
99

1010
describe('List.Scroll', () => {
11+
beforeEach(() => {
12+
jest.useFakeTimers();
13+
});
14+
15+
afterEach(() => {
16+
jest.useRealTimers();
17+
});
18+
1119
function genList(props) {
1220
let node = (
1321
<List component="ul" itemKey="id" {...props}>
@@ -28,6 +36,7 @@ describe('List.Scroll', () => {
2836

2937
it('value scroll', () => {
3038
listRef.current.scrollTo(903);
39+
jest.runAllTimers();
3140
expect(wrapper.find('ul').instance().scrollTop).toEqual(903);
3241
});
3342
});
@@ -82,10 +91,12 @@ describe('List.Scroll', () => {
8291

8392
it('top', () => {
8493
listRef.current.scrollTo({ ...scrollConfig, align: 'top' });
94+
jest.runAllTimers();
8595
expect(scrollTop).toEqual(200);
8696
});
8797
it('bottom', () => {
8898
listRef.current.scrollTo({ ...scrollConfig, align: 'bottom' });
99+
jest.runAllTimers();
89100
expect(scrollTop).toEqual(120);
90101
});
91102
describe('auto', () => {
@@ -97,6 +108,7 @@ describe('List.Scroll', () => {
97108
.simulate('scroll');
98109
expect(onScroll).toHaveBeenCalled();
99110
listRef.current.scrollTo({ ...scrollConfig, align: 'auto' });
111+
jest.runAllTimers();
100112
expect(scrollTop).toEqual(200);
101113
});
102114
it('lower of', () => {
@@ -107,6 +119,7 @@ describe('List.Scroll', () => {
107119
.simulate('scroll');
108120
expect(onScroll).toHaveBeenCalled();
109121
listRef.current.scrollTo({ ...scrollConfig, align: 'auto' });
122+
jest.runAllTimers();
110123
expect(scrollTop).toEqual(120);
111124
});
112125
it('in range', () => {
@@ -117,6 +130,7 @@ describe('List.Scroll', () => {
117130
.simulate('scroll');
118131
expect(onScroll).toHaveBeenCalled();
119132
listRef.current.scrollTo({ ...scrollConfig, align: 'auto' });
133+
jest.runAllTimers();
120134
expect(scrollTop).toEqual(150);
121135
});
122136
});

0 commit comments

Comments
 (0)