From eee13f18af9a748d0164aca82745c8b807ec548a Mon Sep 17 00:00:00 2001 From: zombiej Date: Mon, 2 Mar 2020 10:53:21 +0800 Subject: [PATCH 1/4] fix: Scroll to with SCU --- package.json | 1 + src/List.tsx | 246 ++++++++++++++++++++++--------------------- tests/scroll.test.js | 14 +++ 3 files changed, 139 insertions(+), 122 deletions(-) diff --git a/package.json b/package.json index 521de043..68bad834 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react-dom": "*" }, "devDependencies": { + "@types/jest": "^25.1.3", "@types/react": "^16.8.19", "@types/react-dom": "^16.8.4", "@types/warning": "^3.0.0", diff --git a/src/List.tsx b/src/List.tsx index 5bc85b52..96c5305c 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -456,143 +456,145 @@ class List extends React.Component, ListState> { }; public scrollTo = (arg0: number | ScrollConfig) => { - // Number top - if (typeof arg0 === 'object') { - const { isVirtual } = this.state; - const { height, itemHeight, data } = this.props; - const { align = 'auto' } = arg0; - - let index = 0; - if ('index' in arg0) { - ({ index } = arg0); - } else if ('key' in arg0) { - const { key } = arg0; - index = data.findIndex(item => this.getItemKey(item) === key); - } + setTimeout(() => { + // Number top + if (typeof arg0 === 'object') { + const { isVirtual } = this.state; + const { height, itemHeight, data } = this.props; + const { align = 'auto' } = arg0; + + let index = 0; + if ('index' in arg0) { + ({ index } = arg0); + } else if ('key' in arg0) { + const { key } = arg0; + index = data.findIndex(item => this.getItemKey(item) === key); + } - const visibleCount = Math.ceil(height / itemHeight); - const item = data[index]; - if (item) { - const { clientHeight } = this.listRef.current; - - if (isVirtual) { - // Calculate related data - const { itemIndex, itemOffsetPtg } = this.state; - const { scrollTop } = this.listRef.current; - const scrollPtg = getElementScrollPercentage(this.listRef.current); - - const relativeLocatedItemTop = getItemRelativeTop({ - itemIndex, - itemOffsetPtg, - itemElementHeights: this.itemElementHeights, - scrollPtg, - clientHeight, - getItemKey: this.getIndexKey, - }); - - // We will force render related items to collect height for re-location - this.setState( - { - startIndex: Math.max(0, index - visibleCount), - endIndex: Math.min(data.length - 1, index + visibleCount), - }, - () => { - this.collectItemHeights(); - - // Calculate related top - let relativeTop: number; - let mergedAlgin = align; - - if (align === 'auto') { - let shouldChange = true; - - // Check if exist in the visible range - if (Math.abs(itemIndex - index) < visibleCount) { - let itemTop = relativeLocatedItemTop; - if (index < itemIndex) { - for (let i = index; i < itemIndex; i += 1) { - const eleKey = this.getIndexKey(i); - itemTop -= this.itemElementHeights[eleKey] || 0; - } - } else { - for (let i = itemIndex; i <= index; i += 1) { - const eleKey = this.getIndexKey(i); - itemTop += this.itemElementHeights[eleKey] || 0; + const visibleCount = Math.ceil(height / itemHeight); + const item = data[index]; + if (item) { + const { clientHeight } = this.listRef.current; + + if (isVirtual) { + // Calculate related data + const { itemIndex, itemOffsetPtg } = this.state; + const { scrollTop } = this.listRef.current; + const scrollPtg = getElementScrollPercentage(this.listRef.current); + + const relativeLocatedItemTop = getItemRelativeTop({ + itemIndex, + itemOffsetPtg, + itemElementHeights: this.itemElementHeights, + scrollPtg, + clientHeight, + getItemKey: this.getIndexKey, + }); + + // We will force render related items to collect height for re-location + this.setState( + { + startIndex: Math.max(0, index - visibleCount), + endIndex: Math.min(data.length - 1, index + visibleCount), + }, + () => { + this.collectItemHeights(); + + // Calculate related top + let relativeTop: number; + let mergedAlgin = align; + + if (align === 'auto') { + let shouldChange = true; + + // Check if exist in the visible range + if (Math.abs(itemIndex - index) < visibleCount) { + let itemTop = relativeLocatedItemTop; + if (index < itemIndex) { + for (let i = index; i < itemIndex; i += 1) { + const eleKey = this.getIndexKey(i); + itemTop -= this.itemElementHeights[eleKey] || 0; + } + } else { + for (let i = itemIndex; i <= index; i += 1) { + const eleKey = this.getIndexKey(i); + itemTop += this.itemElementHeights[eleKey] || 0; + } } + + shouldChange = itemTop <= 0 || itemTop >= clientHeight; } - shouldChange = itemTop <= 0 || itemTop >= clientHeight; + if (shouldChange) { + // Out of range will fall back to position align + mergedAlgin = index < itemIndex ? 'top' : 'bottom'; + } else { + const { + itemIndex: nextIndex, + itemOffsetPtg: newOffsetPtg, + startIndex, + endIndex, + } = getRangeIndex(scrollPtg, data.length, visibleCount); + + this.setState({ + scrollTop, + itemIndex: nextIndex, + itemOffsetPtg: newOffsetPtg, + startIndex, + endIndex, + }); + return; + } } - if (shouldChange) { - // Out of range will fall back to position align - mergedAlgin = index < itemIndex ? 'top' : 'bottom'; - } else { - const { - itemIndex: nextIndex, - itemOffsetPtg: newOffsetPtg, - startIndex, - endIndex, - } = getRangeIndex(scrollPtg, data.length, visibleCount); - - this.setState({ - scrollTop, - itemIndex: nextIndex, - itemOffsetPtg: newOffsetPtg, - startIndex, - endIndex, - }); - return; + // Align with position should make scroll happen + if (mergedAlgin === 'top') { + relativeTop = 0; + } else if (mergedAlgin === 'bottom') { + const eleKey = this.getItemKey(item); + + relativeTop = clientHeight - this.itemElementHeights[eleKey] || 0; } - } - // Align with position should make scroll happen - if (mergedAlgin === 'top') { - relativeTop = 0; - } else if (mergedAlgin === 'bottom') { - const eleKey = this.getItemKey(item); + this.internalScrollTo({ + itemIndex: index, + relativeTop, + }); + }, + ); + } else { + // Raw list without virtual scroll set position directly + this.collectItemHeights({ startIndex: 0, endIndex: data.length - 1 }); + let mergedAlgin = align; + + // Collection index item position + const indexItemHeight = this.itemElementHeights[this.getIndexKey(index)]; + let itemTop = 0; + for (let i = 0; i < index; i += 1) { + const eleKey = this.getIndexKey(i); + itemTop += this.itemElementHeights[eleKey] || 0; + } + const itemBottom = itemTop + indexItemHeight; - relativeTop = clientHeight - this.itemElementHeights[eleKey] || 0; + if (mergedAlgin === 'auto') { + if (itemTop < this.listRef.current.scrollTop) { + mergedAlgin = 'top'; + } else if (itemBottom > this.listRef.current.scrollTop + clientHeight) { + mergedAlgin = 'bottom'; } - - this.internalScrollTo({ - itemIndex: index, - relativeTop, - }); - }, - ); - } else { - // Raw list without virtual scroll set position directly - this.collectItemHeights({ startIndex: 0, endIndex: data.length - 1 }); - let mergedAlgin = align; - - // Collection index item position - const indexItemHeight = this.itemElementHeights[this.getIndexKey(index)]; - let itemTop = 0; - for (let i = 0; i < index; i += 1) { - const eleKey = this.getIndexKey(i); - itemTop += this.itemElementHeights[eleKey] || 0; - } - const itemBottom = itemTop + indexItemHeight; - - if (mergedAlgin === 'auto') { - if (itemTop < this.listRef.current.scrollTop) { - mergedAlgin = 'top'; - } else if (itemBottom > this.listRef.current.scrollTop + clientHeight) { - mergedAlgin = 'bottom'; } - } - if (mergedAlgin === 'top') { - this.listRef.current.scrollTop = itemTop; - } else if (mergedAlgin === 'bottom') { - this.listRef.current.scrollTop = itemTop - (clientHeight - indexItemHeight); + if (mergedAlgin === 'top') { + this.listRef.current.scrollTop = itemTop; + } else if (mergedAlgin === 'bottom') { + this.listRef.current.scrollTop = itemTop - (clientHeight - indexItemHeight); + } } } + } else { + this.listRef.current.scrollTop = arg0; } - } else { - this.listRef.current.scrollTop = arg0; - } + }); }; public internalScrollTo(relativeScroll: RelativeScroll): void { diff --git a/tests/scroll.test.js b/tests/scroll.test.js index 416601f6..9cbf9d97 100644 --- a/tests/scroll.test.js +++ b/tests/scroll.test.js @@ -8,6 +8,14 @@ function genData(count) { } describe('List.Scroll', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + function genList(props) { let node = ( @@ -28,6 +36,7 @@ describe('List.Scroll', () => { it('value scroll', () => { listRef.current.scrollTo(903); + jest.runAllTimers(); expect(wrapper.find('ul').instance().scrollTop).toEqual(903); }); }); @@ -82,10 +91,12 @@ describe('List.Scroll', () => { it('top', () => { listRef.current.scrollTo({ ...scrollConfig, align: 'top' }); + jest.runAllTimers(); expect(scrollTop).toEqual(200); }); it('bottom', () => { listRef.current.scrollTo({ ...scrollConfig, align: 'bottom' }); + jest.runAllTimers(); expect(scrollTop).toEqual(120); }); describe('auto', () => { @@ -97,6 +108,7 @@ describe('List.Scroll', () => { .simulate('scroll'); expect(onScroll).toHaveBeenCalled(); listRef.current.scrollTo({ ...scrollConfig, align: 'auto' }); + jest.runAllTimers(); expect(scrollTop).toEqual(200); }); it('lower of', () => { @@ -107,6 +119,7 @@ describe('List.Scroll', () => { .simulate('scroll'); expect(onScroll).toHaveBeenCalled(); listRef.current.scrollTo({ ...scrollConfig, align: 'auto' }); + jest.runAllTimers(); expect(scrollTop).toEqual(120); }); it('in range', () => { @@ -117,6 +130,7 @@ describe('List.Scroll', () => { .simulate('scroll'); expect(onScroll).toHaveBeenCalled(); listRef.current.scrollTo({ ...scrollConfig, align: 'auto' }); + jest.runAllTimers(); expect(scrollTop).toEqual(150); }); }); From 97c5788221a32802e7abbdeaf2a2d7d982047f1c Mon Sep 17 00:00:00 2001 From: zombiej Date: Mon, 2 Mar 2020 10:54:31 +0800 Subject: [PATCH 2/4] add comiple ci --- .circleci/config.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c10f9d15..7e8fcac6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,9 +29,24 @@ jobs: - node_modules key: v1-dependencies-{{ checksum "package.json" }} - run: npm test -- --coverage && bash <(curl -s https://codecov.io/bash) + lint: + docker: + - image: circleci/node:latest + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }} + - run: npm install + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum "package.json" }} + - run: npm run compile workflows: version: 2 build_and_test: jobs: - lint - - test \ No newline at end of file + - test + - compile \ No newline at end of file From 062cc73b6b0a16c079fc8a5dac9631a30eaf9ea8 Mon Sep 17 00:00:00 2001 From: zombiej Date: Mon, 2 Mar 2020 10:55:08 +0800 Subject: [PATCH 3/4] typo --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7e8fcac6..7e3649d5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,7 +29,7 @@ jobs: - node_modules key: v1-dependencies-{{ checksum "package.json" }} - run: npm test -- --coverage && bash <(curl -s https://codecov.io/bash) - lint: + compile: docker: - image: circleci/node:latest steps: From 1f35a24359c6aa9bd21a34c31d00dcdfb02803b4 Mon Sep 17 00:00:00 2001 From: zombiej Date: Mon, 2 Mar 2020 11:03:30 +0800 Subject: [PATCH 4/4] fix compiple --- .eslintrc.js | 1 + src/utils/itemUtil.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 6173a624..70b4fe0f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,5 +8,6 @@ module.exports = { 'react/no-did-update-set-state': 0, 'react/no-find-dom-node': 0, 'no-dupe-class-members': 0, + 'react/sort-comp': 0, }, }; diff --git a/src/utils/itemUtil.ts b/src/utils/itemUtil.ts index d6752d7f..e6c70f74 100644 --- a/src/utils/itemUtil.ts +++ b/src/utils/itemUtil.ts @@ -81,7 +81,7 @@ export function getElementScrollPercentage(element: HTMLElement | null) { * But if not provided, downgrade to `findDOMNode` to get the real dom element. */ export function getNodeHeight(node: HTMLElement) { - const element = findDOMNode(node); + const element = findDOMNode(node); return element ? element.offsetHeight : 0; }