From 2d99ce4479fc50f142d53e1c66306b5701afaacc Mon Sep 17 00:00:00 2001 From: Wanpan Date: Thu, 25 Apr 2024 17:27:01 +0800 Subject: [PATCH] feat: Supports onPopupScroll props (#507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Supports onPopupScroll props * fix: eslint fix * feat: add test case * fix: update test * test: update case * test: fix test case * test: fix onPopupScroll evt is null * test: fix outdated cases * test: fix test case * docs: clean up --------- Co-authored-by: 二货机器人 --- README.md | 2 +- examples/basic.tsx | 5 +- examples/filter.tsx | 8 +- package.json | 2 +- src/OptionList.tsx | 5 + src/TreeSelect.tsx | 4 + src/TreeSelectContext.ts | 1 + tests/Select.multiple.spec.js | 2 +- tests/Select.props.spec.js | 29 + tests/Select.spec.tsx | 17 +- .../Select.checkable.spec.tsx.snap | 644 +++++++------- tests/__snapshots__/Select.spec.tsx.snap | 787 +++++++++--------- tests/shared/focusTest.js | 13 +- 13 files changed, 810 insertions(+), 709 deletions(-) diff --git a/README.md b/README.md index f1d5fef0..4916ff45 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ online example: https://tree-select-react-component.vercel.app/ |dropdownMatchSelectWidth | whether dropdown's with is same with select. Default set `min-width` same as input | bool | true | |dropdownClassName | additional className applied to dropdown | String | - | |dropdownStyle | additional style applied to dropdown | Object | {} | -|dropdownPopupAlign | specify alignment for dropdown (alignConfig of [dom-align](https://github.com/yiminghe/dom-align)) | Object | - | |onDropdownVisibleChange | control dropdown visible | function | `() => { return true; }` | |notFoundContent | specify content to show when no result matches. | String | 'Not Found' | |showSearch | whether show search input in single mode | bool | true | @@ -75,6 +74,7 @@ online example: https://tree-select-react-component.vercel.app/ |onSelect | called when select treeNode | function(value, node, extra) | - | |onSearch | called when input changed | function | - | |onTreeExpand | called when tree node expand | function(expandedKeys) | - | +|onPopupScroll | called when popup scroll | function(event) | - | |showCheckedStrategy | `TreeSelect.SHOW_ALL`: show all checked treeNodes (Include parent treeNode). `TreeSelect.SHOW_PARENT`: show checked treeNodes (Just show parent treeNode). Default just show child. | enum{TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD | |treeIcon | show tree icon | bool | false | |treeLine | show tree line | bool | false | diff --git a/examples/basic.tsx b/examples/basic.tsx index a0d97993..4b6b8cb4 100644 --- a/examples/basic.tsx +++ b/examples/basic.tsx @@ -212,6 +212,9 @@ class Demo extends React.Component { }); }} onSelect={this.onSelect} + onPopupScroll={evt => { + console.log('onPopupScroll:', evt.target); + }} />

single select (just select children)

@@ -256,7 +259,7 @@ class Demo extends React.Component { choiceTransitionName="rc-tree-select-selection__choice-zoom" style={{ width: 300 }} // dropdownStyle={{ height: 200, overflow: 'auto' }} - dropdownPopupAlign={{ + dropdownAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2], }} diff --git a/examples/filter.tsx b/examples/filter.tsx index d473bf99..8a7881a0 100644 --- a/examples/filter.tsx +++ b/examples/filter.tsx @@ -64,10 +64,10 @@ class Demo extends React.Component { transitionName="rc-tree-select-dropdown-slide-up" choiceTransitionName="rc-tree-select-selection__choice-zoom" // dropdownStyle={{ height: 200, overflow: 'auto' }} - dropdownPopupAlign={{ - overflow: { adjustY: 0, adjustX: 0 }, - offset: [0, 2], - }} + // dropdownPopupAlign={{ + // overflow: { adjustY: 0, adjustX: 0 }, + // offset: [0, 2], + // }} placeholder={请下拉选择} treeLine maxTagTextLength={10} diff --git a/package.json b/package.json index 7ac01668..cdf56a58 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "rc-dialog": "^7.5.7", "rc-field-form": "^2.0.0", "rc-test": "^7.0.4", - "rc-virtual-list": "^1.1.0", + "rc-virtual-list": "^3.0.0", "react": "^16.8.0", "react-dom": "^16.8.0", "typescript": "^5.0.0" diff --git a/src/OptionList.tsx b/src/OptionList.tsx index b3b3c1d8..2d627c1d 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -44,6 +44,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, dropdownMatchSelectWidth, treeExpandAction, treeTitleRender, + onPopupScroll, } = React.useContext(TreeSelectContext); const { @@ -70,6 +71,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, const memoTreeData = useMemo( () => treeData, + // eslint-disable-next-line react-hooks/exhaustive-deps [open, treeData], (prev, next) => next[0] && prev[1] !== next[1], ); @@ -97,6 +99,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, treeRef.current?.scrollTo({ key: checkedKeys[0] }); setActiveKey(checkedKeys[0]); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); // ========================== Search ========================== @@ -123,6 +126,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, if (searchValue) { setSearchExpandedKeys(getAllKeys(treeData, fieldNames)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchValue]); const onInternalExpand = (keys: Key[]) => { @@ -252,6 +256,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, onLoad={onTreeLoad} filterTreeNode={filterTreeNode} expandAction={treeExpandAction} + onScroll={onPopupScroll} /> ); diff --git a/src/TreeSelect.tsx b/src/TreeSelect.tsx index 051cdddf..c949f120 100644 --- a/src/TreeSelect.tsx +++ b/src/TreeSelect.tsx @@ -240,6 +240,7 @@ const TreeSelect = React.forwardRef((props, ref) treeMotion, treeTitleRender, + onPopupScroll, ...restProps } = props; @@ -465,6 +466,7 @@ const TreeSelect = React.forwardRef((props, ref) ...item, label: item.label ?? item.value, })); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ mergedFieldNames, mergedMultiple, @@ -681,6 +683,7 @@ const TreeSelect = React.forwardRef((props, ref) onSelect: onOptionSelect, treeExpandAction, treeTitleRender, + onPopupScroll, }), [ virtual, @@ -693,6 +696,7 @@ const TreeSelect = React.forwardRef((props, ref) onOptionSelect, treeExpandAction, treeTitleRender, + onPopupScroll, ], ); diff --git a/src/TreeSelectContext.ts b/src/TreeSelectContext.ts index 344f71de..628d3742 100644 --- a/src/TreeSelectContext.ts +++ b/src/TreeSelectContext.ts @@ -13,6 +13,7 @@ export interface TreeSelectContextProps { onSelect: OnInternalSelect; treeExpandAction?: ExpandAction; treeTitleRender?: (node: any) => React.ReactNode; + onPopupScroll?: React.UIEventHandler; } const TreeSelectContext = React.createContext(null as any); diff --git a/tests/Select.multiple.spec.js b/tests/Select.multiple.spec.js index ec95f5e5..b7e68f4e 100644 --- a/tests/Select.multiple.spec.js +++ b/tests/Select.multiple.spec.js @@ -7,7 +7,7 @@ import TreeSelect, { TreeNode } from '../src'; import focusTest from './shared/focusTest'; describe('TreeSelect.multiple', () => { - focusTest('multiple'); + focusTest(true); const treeData = [ { key: '0', value: '0', title: 'label0' }, diff --git a/tests/Select.props.spec.js b/tests/Select.props.spec.js index 81ef4fe7..d9b40225 100644 --- a/tests/Select.props.spec.js +++ b/tests/Select.props.spec.js @@ -2,6 +2,8 @@ import { mount } from 'enzyme'; import Tree, { TreeNode } from 'rc-tree'; import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; + import TreeSelect, { SHOW_ALL, SHOW_CHILD, SHOW_PARENT, TreeNode as SelectNode } from '../src'; // Promisify timeout to let jest catch works @@ -241,6 +243,33 @@ describe('TreeSelect.props', () => { expect(handleSearch).toHaveBeenCalledWith('Search changed'); }); + it('onPopupScroll', async () => { + const onPopupScroll = jest.fn(e => { + // Prevents React from resetting its properties: + e.persist(); + }); + render( + ({ + title: `Title ${index}`, + value: index, + }))} + />, + ); + + fireEvent.scroll(document.querySelector('.rc-tree-select-tree-list-holder'), { + scrollY: 100, + }); + + expect(onPopupScroll).toHaveBeenCalled(); + expect(onPopupScroll.mock.calls[0][0].target).toBe( + document.querySelector('.rc-tree-select-tree-list-holder'), + ); + }); + it('showArrow', () => { const wrapper = mount(createOpenSelect({ suffixIcon: null })); expect(wrapper.find('.rc-tree-select-arrow').length).toBeFalsy(); diff --git a/tests/Select.spec.tsx b/tests/Select.spec.tsx index 75fd2429..698dc89e 100644 --- a/tests/Select.spec.tsx +++ b/tests/Select.spec.tsx @@ -6,9 +6,17 @@ import TreeSelect, { TreeNode } from '../src'; import focusTest from './shared/focusTest'; import { selectNode } from './util'; +const mockScrollTo = jest.fn(); + +// Mock `useScrollTo` from `rc-virtual-list/lib/hooks/useScrollTo` +jest.mock('rc-virtual-list/lib/hooks/useScrollTo', () => { + return () => mockScrollTo; +}); + describe('TreeSelect.basic', () => { beforeEach(() => { jest.useFakeTimers(); + mockScrollTo.mockReset(); }); beforeAll(() => { @@ -19,7 +27,7 @@ describe('TreeSelect.basic', () => { jest.useRealTimers(); }); - focusTest('single'); + focusTest(); describe('render', () => { const treeData = [ @@ -391,11 +399,8 @@ describe('TreeSelect.basic', () => { wrapper.openSelect(); expect(wrapper.isOpen()).toBeFalsy(); - const scrollTo = jest.fn(); - wrapper.find('List').instance().scrollTo = scrollTo; - wrapper.openSelect(); - expect(scrollTo).toHaveBeenCalled(); + expect(mockScrollTo).toHaveBeenCalled(); }); }); @@ -525,7 +530,7 @@ describe('TreeSelect.basic', () => { keyDown(KeyCode.DOWN); expect(wrapper.find('.rc-tree-select-tree-treenode-active').text()).toBe('0 label'); - + keyDown(KeyCode.UP); expect(wrapper.find('.rc-tree-select-tree-treenode-active').text()).toBe('11 label'); }); diff --git a/tests/__snapshots__/Select.checkable.spec.tsx.snap b/tests/__snapshots__/Select.checkable.spec.tsx.snap index fb5d0b12..f0ede9b7 100644 --- a/tests/__snapshots__/Select.checkable.spec.tsx.snap +++ b/tests/__snapshots__/Select.checkable.spec.tsx.snap @@ -157,110 +157,115 @@ exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 1
-
-
+
+
-
-
-
@@ -352,110 +357,115 @@ exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 2
-
-
+
+
-
-
-
@@ -625,110 +635,115 @@ exports[`TreeSelect.checkable uncheck remove by tree check 1`] = `
-
-
+
+
-
-
-
@@ -817,110 +832,115 @@ exports[`TreeSelect.checkable uncheck remove by tree check 2`] = `
-
-
+
+
-
-
-
diff --git a/tests/__snapshots__/Select.spec.tsx.snap b/tests/__snapshots__/Select.spec.tsx.snap index 1e449191..8b0c118a 100644 --- a/tests/__snapshots__/Select.spec.tsx.snap +++ b/tests/__snapshots__/Select.spec.tsx.snap @@ -64,124 +64,129 @@ exports[`TreeSelect.basic render renders TreeNode correctly 1`] = `
-
-
+
+
-
-
-
@@ -257,124 +262,129 @@ exports[`TreeSelect.basic render renders TreeNode correctly with falsy child 1`]
-
-
+
+
-
-
-
@@ -587,68 +597,73 @@ exports[`TreeSelect.basic render renders treeDataSimpleMode correctly 1`] = `
-
-
+
+
-
-
-
@@ -724,38 +739,43 @@ exports[`TreeSelect.basic search nodes check tree changed by filter 1`] = `
-
-
+
+
-
@@ -831,64 +851,69 @@ exports[`TreeSelect.basic search nodes check tree changed by filter 2`] = `
-
-
+
+
-
-
-
@@ -964,64 +989,69 @@ exports[`TreeSelect.basic search nodes filter node but not remove then 1`] = `
-
-
+
+
-
-
-
@@ -1095,64 +1125,69 @@ exports[`TreeSelect.basic search nodes renders search input 1`] = `
-
-
+
+
-
-
-
diff --git a/tests/shared/focusTest.js b/tests/shared/focusTest.js index 7ca1f985..56b62d21 100644 --- a/tests/shared/focusTest.js +++ b/tests/shared/focusTest.js @@ -3,7 +3,7 @@ import React from 'react'; import { mount } from 'enzyme'; import TreeSelect from '../../src'; -export default function focusTest(mode) { +export default function focusTest(multiple = false) { let container; beforeEach(() => { @@ -21,7 +21,7 @@ export default function focusTest(mode) { const treeRef = React.createRef(); mount( - , + , { attachTo: container }, ); @@ -35,7 +35,7 @@ export default function focusTest(mode) { const treeRef = React.createRef(); mount( - , + , { attachTo: container }, ); treeRef.current.focus(); @@ -46,10 +46,9 @@ export default function focusTest(mode) { it('autoFocus', () => { const handleFocus = jest.fn(); const treeData = [{ key: '0', value: '0', title: '0 label' }]; - mount( - , - { attachTo: container }, - ); + mount(, { + attachTo: container, + }); expect(handleFocus).toHaveBeenCalled(); }); }