From 5c6258ec51d9e8c9826e528b3a98e6fdad4114e5 Mon Sep 17 00:00:00 2001 From: n0ruSh Date: Thu, 22 Oct 2020 17:13:36 +0800 Subject: [PATCH 01/10] feat: add sorting functionality --- README.md | 2 + examples/sorter.tsx | 97 ++++++++++++++++++++++++++++++++++++++++++ src/generate.tsx | 31 +++++++++++--- src/interface/index.ts | 2 + tests/Select.test.tsx | 17 ++++++++ 5 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 examples/sorter.tsx diff --git a/README.md b/README.md index c368d11ce..b2efbfee5 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ export default () => ( | loading | show loading icon in arrow | Boolean | false | | virtual | Disable virtual scroll | Boolean | true | | direction | direction of dropdown | 'ltr' \| 'rtl' | 'ltr' | +| sorter | Sort function for local sort, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. | Function(optionA:Option, optionB: Option) | - | +| sortOrder | Order of sorted values: 'ascend' 'descend' | string | 'ascend' | ### Methods diff --git a/examples/sorter.tsx b/examples/sorter.tsx new file mode 100644 index 000000000..92967a382 --- /dev/null +++ b/examples/sorter.tsx @@ -0,0 +1,97 @@ +import React, { useState } from 'react'; +import Select, { Option } from '../src'; +import '../assets/index.less'; +import { SortOrder, OptionCoreData } from '../src/interface'; + +const options = [ + { + label: 'jack', + value: 'jack', + }, + { + label: 'lucy', + value: 'lucy', + }, + { + label: 'alpha', + value: 'alpha', + }, + { + label: 'zoo', + value: 'zoo', + }, +]; + +const incidencesStateResource = [ + { value: 4, label: 'Not Identified' }, + { value: 3, label: 'Closed' }, + { value: 2, label: 'Communicated' }, + { value: 6, label: 'Identified' }, + { value: 1, label: 'Resolved' }, + { value: 5, label: 'Cancelled' }, +]; + +const sorterByLabel = (optionA, optionB) => optionA.label.localeCompare(optionB.label); + +const sorterByChildren = (optionA, optionB) => optionA.children.localeCompare(optionB.children); + +const sortOptions = [ + { + label: '升序', + value: 'ascend', + }, + { + label: '降序', + value: 'descend', + }, +]; + +const compareFn = (input, option) => + (option.label as string).toLowerCase().indexOf(input.toLowerCase()) >= 0; + +const Test = () => { + const [sortOrder, setSortOrder] = useState('ascend'); + return ( +
+ +

Select with options children

+ +

Select with search

+ +
+ + ); +}; + +export default Test; diff --git a/src/generate.tsx b/src/generate.tsx index 5b053a630..0165329ef 100644 --- a/src/generate.tsx +++ b/src/generate.tsx @@ -14,7 +14,7 @@ import classNames from 'classnames'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; import Selector, { RefSelectorProps } from './Selector'; import SelectTrigger, { RefTriggerProps } from './SelectTrigger'; -import { RenderNode, Mode, RenderDOMFunc, OnActiveValue } from './interface'; +import { RenderNode, Mode, RenderDOMFunc, OnActiveValue, SortOrder } from './interface'; import { GetLabeledValue, FilterOptions, @@ -148,6 +148,10 @@ export interface SelectProps extends Re onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; + // Sort + sorter?: (optionA: OptionsType[number], optionB: OptionsType[number]) => number; + sortOrder?: SortOrder; + // Motion choiceTransitionName?: string; @@ -187,7 +191,7 @@ export interface GenerateConfig { /** Convert single raw value into { label, value } format. Will be called by each value */ getLabeledValue: GetLabeledValue>; filterOptions: FilterOptions; - findValueOption: // Need still support legacy ts api + findValueOption: // Need still support legacy ts api | ((values: RawValueType[], options: FlattenOptionsType) => OptionsType) // New API add prevValueOptions support | (( @@ -310,6 +314,8 @@ export default function generateSelector< onSelect, onDeselect, onClear, + sorter, + sortOrder = 'ascend', internalProps = {}, @@ -424,10 +430,25 @@ export default function generateSelector< const getValueOption = useCacheOptions(mergedRawValue, mergedFlattenOptions); + const sortOptions = originOptions => { + if (sorter == null) { + return originOptions; + } + const sort = sourceOptions => { + const compareFn = (optionA, optionB) => { + const compareValue = sorter(optionA, optionB); + return sortOrder === 'ascend' ? compareValue : compareValue * -1; + }; + return sourceOptions.sort(compareFn); + }; + + return sort(originOptions || []); + }; + // Display options for OptionList const displayOptions = useMemo(() => { if (!mergedSearchValue || !mergedShowSearch) { - return [...mergedOptions] as OptionsType; + return [...sortOptions(mergedOptions)] as OptionsType; } const filteredOptions: OptionsType = filterOptions(mergedSearchValue, mergedOptions, { optionFilterProp, @@ -444,8 +465,8 @@ export default function generateSelector< }); } - return filteredOptions; - }, [mergedOptions, mergedSearchValue, mode, mergedShowSearch]); + return sortOptions(filteredOptions); + }, [mergedOptions, mergedSearchValue, mode, mergedShowSearch, sortOrder]); const displayFlattenOptions: FlattenOptionsType = useMemo( () => flattenOptions(displayOptions, props), diff --git a/src/interface/index.ts b/src/interface/index.ts index 42456cae4..7f3c99308 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -50,3 +50,5 @@ export interface FlattenOptionData { key: string | number; data: OptionData | OptionGroupData; } + +export type SortOrder = 'descend' | 'ascend'; diff --git a/tests/Select.test.tsx b/tests/Select.test.tsx index 821951051..4291dbc25 100644 --- a/tests/Select.test.tsx +++ b/tests/Select.test.tsx @@ -1624,4 +1624,21 @@ describe('Select.Basic', () => { .first() .simulate('mouseenter'); }); + + it('sorter should work', () => { + const wrapper = mount( + , + ); + + toggleOpen(wrapper); + expect( + wrapper + .find('.rc-select-item-option-content') + .first() + .text(), + ).toBe('1'); + }); }); From f043d53b0dcf641429b95633f8e4fdf7106591d7 Mon Sep 17 00:00:00 2001 From: n0ruSh Date: Tue, 3 Nov 2020 10:55:51 +0800 Subject: [PATCH 02/10] feat: replace sort and sortOrder to filterSort and sorting only works for search filter --- README.md | 3 +- examples/filterSort.tsx | 41 +++++++++++++++++ examples/sorter.tsx | 97 ----------------------------------------- src/generate.tsx | 34 ++++----------- src/interface/index.ts | 2 - 5 files changed, 51 insertions(+), 126 deletions(-) create mode 100644 examples/filterSort.tsx delete mode 100644 examples/sorter.tsx diff --git a/README.md b/README.md index b2efbfee5..55917d53d 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ export default () => ( | disabled | whether disabled select | bool | false | | filterOption | whether filter options by input value. default filter by option's optionFilterProp prop's value | bool | true/Function(inputValue:string, option:Option) | | optionFilterProp | which prop value of option will be used for filter if filterOption is true | String | 'value' | +| filterSort | Sort function for search options sorting, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. | Function(optionA:Option, optionB: Option) | - | | optionLabelProp | render option value or option children as content of select | String: 'value'/'children' | 'value' | | defaultValue | initial selected option(s) | String/Array | - | | value | current selected option(s) | String/Array/{key:String, label:React.Node}/Array<{key, label}> | - | @@ -127,8 +128,6 @@ export default () => ( | loading | show loading icon in arrow | Boolean | false | | virtual | Disable virtual scroll | Boolean | true | | direction | direction of dropdown | 'ltr' \| 'rtl' | 'ltr' | -| sorter | Sort function for local sort, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. | Function(optionA:Option, optionB: Option) | - | -| sortOrder | Order of sorted values: 'ascend' 'descend' | string | 'ascend' | ### Methods diff --git a/examples/filterSort.tsx b/examples/filterSort.tsx new file mode 100644 index 000000000..8c0d4263a --- /dev/null +++ b/examples/filterSort.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import Select from '../src'; +import '../assets/index.less'; + +const incidencesStateResource = [ + { value: 4, label: 'Not Identified' }, + { value: 3, label: 'Closed' }, + { value: 2, label: 'Communicated' }, + { value: 6, label: 'Identified' }, + { value: 1, label: 'Resolved' }, + { value: 5, label: 'Cancelled' }, +]; + +const sorterByLabel = (optionA, optionB) => optionA.label.localeCompare(optionB.label); + +const compareFn = (input, option) => + (option.label as string).toLowerCase().indexOf(input.toLowerCase()) >= 0; + +const Test = () => ( +
+

with filter sort

+ +

without filter sort

+ -
-

Select with options prop

- -

Select with options children

- -

Select with search

- -
-
- ); -}; - -export default Test; diff --git a/src/generate.tsx b/src/generate.tsx index 0165329ef..5a9d11788 100644 --- a/src/generate.tsx +++ b/src/generate.tsx @@ -14,7 +14,7 @@ import classNames from 'classnames'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; import Selector, { RefSelectorProps } from './Selector'; import SelectTrigger, { RefTriggerProps } from './SelectTrigger'; -import { RenderNode, Mode, RenderDOMFunc, OnActiveValue, SortOrder } from './interface'; +import { RenderNode, Mode, RenderDOMFunc, OnActiveValue } from './interface'; import { GetLabeledValue, FilterOptions, @@ -85,6 +85,7 @@ export interface SelectProps extends Re * It's by design. */ filterOption?: boolean | FilterFunc; + filterSort?: (optionA: OptionsType[number], optionB: OptionsType[number]) => number; showSearch?: boolean; autoClearSearchValue?: boolean; onSearch?: (value: string) => void; @@ -148,10 +149,6 @@ export interface SelectProps extends Re onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; - // Sort - sorter?: (optionA: OptionsType[number], optionB: OptionsType[number]) => number; - sortOrder?: SortOrder; - // Motion choiceTransitionName?: string; @@ -262,6 +259,7 @@ export default function generateSelector< inputValue, searchValue, filterOption, + filterSort, optionFilterProp = 'value', autoClearSearchValue = true, onSearch, @@ -314,8 +312,6 @@ export default function generateSelector< onSelect, onDeselect, onClear, - sorter, - sortOrder = 'ascend', internalProps = {}, @@ -430,25 +426,10 @@ export default function generateSelector< const getValueOption = useCacheOptions(mergedRawValue, mergedFlattenOptions); - const sortOptions = originOptions => { - if (sorter == null) { - return originOptions; - } - const sort = sourceOptions => { - const compareFn = (optionA, optionB) => { - const compareValue = sorter(optionA, optionB); - return sortOrder === 'ascend' ? compareValue : compareValue * -1; - }; - return sourceOptions.sort(compareFn); - }; - - return sort(originOptions || []); - }; - // Display options for OptionList const displayOptions = useMemo(() => { if (!mergedSearchValue || !mergedShowSearch) { - return [...sortOptions(mergedOptions)] as OptionsType; + return [...mergedOptions] as OptionsType; } const filteredOptions: OptionsType = filterOptions(mergedSearchValue, mergedOptions, { optionFilterProp, @@ -464,9 +445,12 @@ export default function generateSelector< key: '__RC_SELECT_TAG_PLACEHOLDER__', }); } + if (filterSort && Array.isArray(filteredOptions)) { + filteredOptions.sort(filterSort); + } - return sortOptions(filteredOptions); - }, [mergedOptions, mergedSearchValue, mode, mergedShowSearch, sortOrder]); + return filteredOptions; + }, [mergedOptions, mergedSearchValue, mode, mergedShowSearch, filterSort]); const displayFlattenOptions: FlattenOptionsType = useMemo( () => flattenOptions(displayOptions, props), diff --git a/src/interface/index.ts b/src/interface/index.ts index 7f3c99308..42456cae4 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -50,5 +50,3 @@ export interface FlattenOptionData { key: string | number; data: OptionData | OptionGroupData; } - -export type SortOrder = 'descend' | 'ascend'; From fa0aca09aac7e316044d47c932ae811cee75fb86 Mon Sep 17 00:00:00 2001 From: n0ruSh Date: Tue, 3 Nov 2020 11:21:13 +0800 Subject: [PATCH 03/10] fix: test case and demo --- examples/filterSort.tsx | 5 ----- src/generate.tsx | 2 +- tests/Select.test.tsx | 23 ++++++++++++++++------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/examples/filterSort.tsx b/examples/filterSort.tsx index 8c0d4263a..299f70349 100644 --- a/examples/filterSort.tsx +++ b/examples/filterSort.tsx @@ -13,9 +13,6 @@ const incidencesStateResource = [ const sorterByLabel = (optionA, optionB) => optionA.label.localeCompare(optionB.label); -const compareFn = (input, option) => - (option.label as string).toLowerCase().indexOf(input.toLowerCase()) >= 0; - const Test = () => (

with filter sort

@@ -24,7 +21,6 @@ const Test = () => ( style={{ width: 500 }} filterSort={sorterByLabel} optionFilterProp="label" - filterOption={compareFn} options={incidencesStateResource} >

without filter sort

@@ -32,7 +28,6 @@ const Test = () => ( showSearch style={{ width: 500 }} optionFilterProp="label" - filterOption={compareFn} options={incidencesStateResource} />
diff --git a/src/generate.tsx b/src/generate.tsx index 5a9d11788..d5f2efd77 100644 --- a/src/generate.tsx +++ b/src/generate.tsx @@ -188,7 +188,7 @@ export interface GenerateConfig { /** Convert single raw value into { label, value } format. Will be called by each value */ getLabeledValue: GetLabeledValue>; filterOptions: FilterOptions; - findValueOption: // Need still support legacy ts api + findValueOption:// Need still support legacy ts api | ((values: RawValueType[], options: FlattenOptionsType) => OptionsType) // New API add prevValueOptions support | (( diff --git a/tests/Select.test.tsx b/tests/Select.test.tsx index 4291dbc25..e8d761627 100644 --- a/tests/Select.test.tsx +++ b/tests/Select.test.tsx @@ -1625,20 +1625,29 @@ describe('Select.Basic', () => { .simulate('mouseenter'); }); - it('sorter should work', () => { + it('filterSort should work', () => { const wrapper = mount( - , +