diff --git a/.gitignore b/.gitignore index 97ca18b3..96235bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ coverage .dumi/tmp .dumi/tmp-production dist +.vscode \ No newline at end of file diff --git a/examples/adjust-overflow.tsx b/examples/adjust-overflow.tsx index 938ed3d7..1e0ede7d 100644 --- a/examples/adjust-overflow.tsx +++ b/examples/adjust-overflow.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import type { BuildInPlacements } from '@rc-component/trigger/lib/interface'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -60,7 +62,7 @@ const addressOptions = [ const MyCascader = ({ builtinPlacements }: { builtinPlacements?: BuildInPlacements }) => { const [inputValue, setInputValue] = useState(''); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; diff --git a/examples/animation.tsx b/examples/animation.tsx index 4909d666..32e055e3 100644 --- a/examples/animation.tsx +++ b/examples/animation.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -58,7 +60,7 @@ const addressOptions = [ const Demo = () => { const [inputValue, setInputValue] = useState(''); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; diff --git a/examples/change-on-select.tsx b/examples/change-on-select.tsx index e0825919..958fb6c0 100644 --- a/examples/change-on-select.tsx +++ b/examples/change-on-select.tsx @@ -1,6 +1,8 @@ import React from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const options = [ { @@ -61,7 +63,7 @@ const options = [ }, ]; -const onChange = (value, selectedOptions) => { +const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); }; diff --git a/examples/custom-arrow-icon.tsx b/examples/custom-arrow-icon.tsx index d693e4e0..03db88f3 100644 --- a/examples/custom-arrow-icon.tsx +++ b/examples/custom-arrow-icon.tsx @@ -1,8 +1,10 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; -const addressOptions = [ +const addressOptions: CascaderProps['options'] = [ { label: '福建', value: 'fj', @@ -96,12 +98,12 @@ const Demo = () => { }, ]); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; - const onChangeDynamic = (value, selectedOptions) => { + const onChangeDynamic: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setDynamicInputValue(selectedOptions.map(o => o.label).join(', ')); }; @@ -146,7 +148,7 @@ const Demo = () => { ); - const loadData = selectedOptions => { + const loadData: CascaderProps['loadData'] = selectedOptions => { const targetOption = selectedOptions[selectedOptions.length - 1]; targetOption.loading = true; // 动态加载下级数据 diff --git a/examples/custom-field-name.tsx b/examples/custom-field-name.tsx index dd4a24e2..5347d0a4 100644 --- a/examples/custom-field-name.tsx +++ b/examples/custom-field-name.tsx @@ -1,8 +1,10 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option } from './utils'; -const addressOptions = [ +const addressOptions: Option[] = [ { name: '福建', code: 'fj', @@ -59,7 +61,7 @@ const addressOptions = [ const Demo = () => { const [inputValue, setInputValue] = useState(''); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.name).join(', ')); }; diff --git a/examples/debug.tsx b/examples/debug.tsx index f655f608..e0f7f03f 100644 --- a/examples/debug.tsx +++ b/examples/debug.tsx @@ -1,8 +1,9 @@ import React from 'react'; import '../assets/index.less'; import Cascader from '../src'; +import type { Option2 } from './utils'; -const addressOptions = [ +const addressOptions: Option2[] = [ // ...new Array(20).fill(null).map((_, i) => ({ label: String(i), value: `99${i}` })), { label: 空孩子, @@ -88,7 +89,7 @@ const addressOptions = [ const Demo = () => { const [multiple, setMultiple] = React.useState(true); - const onChange = (value: any, selectedOptions: any) => { + const onChange = (value: string[], selectedOptions: Option2[]) => { console.log('[DEBUG] onChange - value:', value); console.log('[DEBUG] onChange - selectedOptions:', selectedOptions); }; @@ -105,27 +106,19 @@ const Demo = () => { /> Multiple - { - // console.log(props); - // return props.label as any; - // }} - // direction="rtl" - // searchValue="福a" - // changeOnSelect - /> + {multiple ? ( + + ) : ( + + )} ); }; diff --git a/examples/default-expand-single-option.tsx b/examples/default-expand-single-option.tsx index cd1cff59..06bb17c6 100644 --- a/examples/default-expand-single-option.tsx +++ b/examples/default-expand-single-option.tsx @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/no-shadow */ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const options = [ { @@ -41,12 +43,12 @@ const options = [ const App = () => { const [inputValue, setInputValue] = useState(''); - const [value, setValue] = useState([]); + const [value, setValue] = useState([]); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { const lastSelected = selectedOptions[selectedOptions.length - 1]; if (lastSelected.children && lastSelected.children.length === 1) { - value.push(lastSelected.children[0].value); + value.push(lastSelected.children[0].value as string); setInputValue(selectedOptions.map(o => o.label).join(', ')); setValue(value); return; diff --git a/examples/defaultValue.tsx b/examples/defaultValue.tsx index cfe30588..7a033c16 100644 --- a/examples/defaultValue.tsx +++ b/examples/defaultValue.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -93,7 +95,7 @@ const defaultOptions = [ const Demo = () => { const [inputValue, setInputValue] = useState(defaultOptions.map(o => o.label).join(', ')); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; diff --git a/examples/dropdown-render.tsx b/examples/dropdown-render.tsx index d18bc21d..4bb5df08 100644 --- a/examples/dropdown-render.tsx +++ b/examples/dropdown-render.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -59,7 +61,7 @@ const addressOptions = [ const Demo = () => { const [inputValue, setInputValue] = useState(''); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; diff --git a/examples/dynamic-options.tsx b/examples/dynamic-options.tsx index 27e0d903..aaf48ad7 100644 --- a/examples/dynamic-options.tsx +++ b/examples/dynamic-options.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -19,12 +21,12 @@ const Demo = () => { const [inputValue, setInputValue] = useState(''); const [options, setOptions] = useState(addressOptions); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log('OnChange:', value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; - const loadData = selectedOptions => { + const loadData: CascaderProps['loadData'] = selectedOptions => { console.log('onLoad:', selectedOptions); const targetOption = selectedOptions[selectedOptions.length - 1]; targetOption.loading = true; diff --git a/examples/hover.tsx b/examples/hover.tsx index 763cfb10..7d5de04c 100644 --- a/examples/hover.tsx +++ b/examples/hover.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -82,7 +84,7 @@ const addressOptions = [ const Demo = () => { const [inputValue, setInputValue] = useState(''); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; diff --git a/examples/multiple.tsx b/examples/multiple.tsx index 4f071e61..f88aaa47 100644 --- a/examples/multiple.tsx +++ b/examples/multiple.tsx @@ -1,6 +1,10 @@ -import React from 'react'; +/* eslint-disable @typescript-eslint/no-shadow */ +import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; + const { SHOW_CHILD } = Cascader; const optionLists = [ @@ -20,12 +24,14 @@ const optionLists = [ const Demo = () => { const [options, setOptions] = React.useState(optionLists); + const [value, setValue] = useState([]); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); + setValue(value); }; - const loadData = selectedOptions => { + const loadData: CascaderProps['loadData'] = selectedOptions => { const targetOption = selectedOptions[selectedOptions.length - 1]; targetOption.loading = true; @@ -55,6 +61,7 @@ const Demo = () => { options={options} showCheckedStrategy={SHOW_CHILD} loadData={loadData} + value={value} onChange={onChange} changeOnSelect /> diff --git a/examples/panel.tsx b/examples/panel.tsx index 17e6a2ad..1f985476 100644 --- a/examples/panel.tsx +++ b/examples/panel.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-shadow */ import React from 'react'; import '../assets/index.less'; import Cascader from '../src'; @@ -56,7 +57,9 @@ const addressOptions = [ ]; export default () => { - const [value, setValue] = React.useState([]); + const [value, setValue] = React.useState([]); + + const [value2, setValue2] = React.useState([]); return ( <> @@ -79,10 +82,11 @@ export default () => { { console.log('Change:', nextValue); + setValue2(nextValue); }} /> diff --git a/examples/rc-form.tsx b/examples/rc-form.tsx index bc78e340..24ce7209 100644 --- a/examples/rc-form.tsx +++ b/examples/rc-form.tsx @@ -1,8 +1,10 @@ import arrayTreeFilter from 'array-tree-filter'; import Form, { Field } from 'rc-field-form'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; import React from 'react'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -58,7 +60,7 @@ const addressOptions = [ ]; const CascaderInput = (props: any) => { - const onChange = value => { + const onChange: CascaderProps['onChange'] = value => { if (props.onChange) { props.onChange(value); } diff --git a/examples/simple.tsx b/examples/simple.tsx index bada51f3..ebad6c64 100644 --- a/examples/simple.tsx +++ b/examples/simple.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -59,7 +61,7 @@ const addressOptions = [ const Demo = () => { const [inputValue, setInputValue] = useState(''); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; diff --git a/examples/text-trigger.tsx b/examples/text-trigger.tsx index 03b92923..6314b8da 100644 --- a/examples/text-trigger.tsx +++ b/examples/text-trigger.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -58,7 +60,7 @@ const addressOptions = [ const Demo = () => { const [inputValue, setInputValue] = useState(''); - const onChange = (value, selectedOptions) => { + const onChange: CascaderProps['onChange'] = (value, selectedOptions) => { console.log(value, selectedOptions); setInputValue(selectedOptions.map(o => o.label).join(', ')); }; diff --git a/examples/utils.tsx b/examples/utils.tsx new file mode 100644 index 00000000..c7d64d66 --- /dev/null +++ b/examples/utils.tsx @@ -0,0 +1,17 @@ +export interface Option { + code?: string; + name?: string; + nodes?: Option[]; + disabled?: boolean; +} + +export interface Option2 { + value?: string; + label?: React.ReactNode; + title?: React.ReactNode; + disabled?: boolean; + disableCheckbox?: boolean; + isLeaf?: boolean; + loading?: boolean; + children?: Option2[]; +} diff --git a/examples/value.tsx b/examples/value.tsx index ec4c3e6c..f1d124eb 100644 --- a/examples/value.tsx +++ b/examples/value.tsx @@ -2,7 +2,9 @@ import arrayTreeFilter from 'array-tree-filter'; import React, { useState } from 'react'; import '../assets/index.less'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; +import type { Option2 } from './utils'; const addressOptions = [ { @@ -58,8 +60,8 @@ const addressOptions = [ ]; const Demo = () => { - const [value, setValue] = useState([]); - const onChange = value => { + const [value, setValue] = useState([]); + const onChange: CascaderProps['onChange'] = value => { console.log(value); setValue(value); }; diff --git a/examples/visible.tsx b/examples/visible.tsx index 23e52539..775433c8 100644 --- a/examples/visible.tsx +++ b/examples/visible.tsx @@ -58,17 +58,9 @@ const addressOptions = [ ]; const Demo = () => { - const [value, setValue] = useState([]); + const [value, setValue] = useState([]); const [popupVisible, setPopupVisible] = useState(false); - const onChange = value => { - setValue(value); - }; - - const onPopupVisibleChange = popupVisible => { - setPopupVisible(popupVisible); - }; - const getLabel = () => { return arrayTreeFilter(addressOptions, (o, level) => o.value === value[level]) .map(o => o.label) @@ -80,8 +72,8 @@ const Demo = () => { popupVisible={popupVisible} value={value} options={addressOptions} - onPopupVisibleChange={onPopupVisibleChange} - onChange={onChange} + onPopupVisibleChange={open => setPopupVisible(open)} + onChange={value => setValue(value)} > diff --git a/src/Cascader.tsx b/src/Cascader.tsx index 4c75b769..97adcd20 100644 --- a/src/Cascader.tsx +++ b/src/Cascader.tsx @@ -26,60 +26,58 @@ import { import { formatStrategyValues, toPathOptions } from './utils/treeUtil'; import warningProps, { warningNullOptions } from './utils/warningPropsUtil'; -export interface ShowSearchType { - filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames) => boolean; +export interface BaseOptionType { + disabled?: boolean; + disableCheckbox?: boolean; + label?: React.ReactNode; + value?: string | number | null; + children?: DefaultOptionType[]; +} + +export type DefaultOptionType = BaseOptionType & Record; + +export interface ShowSearchType< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +> { + filter?: ( + inputValue: string, + options: OptionType[], + fieldNames: FieldNames, + ) => boolean; render?: ( inputValue: string, path: OptionType[], prefixCls: string, - fieldNames: FieldNames, + fieldNames: FieldNames, ) => React.ReactNode; - sort?: (a: OptionType[], b: OptionType[], inputValue: string, fieldNames: FieldNames) => number; + sort?: ( + a: OptionType[], + b: OptionType[], + inputValue: string, + fieldNames: FieldNames, + ) => number; matchInputWidth?: boolean; limit?: number | false; } -export interface FieldNames { - label?: string; - value?: string; - children?: string; -} - -export interface InternalFieldNames extends Required { - key: string; -} - -export type SingleValueType = (string | number)[]; - -export type ValueType = SingleValueType | SingleValueType[]; export type ShowCheckedStrategy = typeof SHOW_PARENT | typeof SHOW_CHILD; -export interface BaseOptionType { - disabled?: boolean; - [name: string]: any; -} -export interface DefaultOptionType extends BaseOptionType { - label: React.ReactNode; - value?: string | number | null; - children?: DefaultOptionType[]; - disableCheckbox?: boolean; -} - -interface BaseCascaderProps - extends Omit< +interface BaseCascaderProps< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +> extends Omit< BaseSelectPropsWithoutPrivate, 'tokenSeparators' | 'labelInValue' | 'mode' | 'showSearch' > { // MISC id?: string; prefixCls?: string; - fieldNames?: FieldNames; + fieldNames?: FieldNames; optionRender?: (option: OptionType) => React.ReactNode; children?: React.ReactElement; // Value - value?: ValueType; - defaultValue?: ValueType; changeOnSelect?: boolean; displayRender?: (label: string[], selectedOptions?: OptionType[]) => React.ReactNode; checkable?: boolean | React.ReactNode; @@ -123,37 +121,59 @@ interface BaseCascaderProps = (value: SingleValueType, selectOptions: OptionType[]) => void; -type OnMultipleChange = ( - value: SingleValueType[], - selectOptions: OptionType[][], -) => void; - -export interface SingleCascaderProps - extends BaseCascaderProps { - checkable?: false; +export interface FieldNames< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +> { + label?: keyof OptionType; + value?: keyof OptionType | ValueField; + children?: keyof OptionType; +} - onChange?: OnSingleChange; +export type ValueType< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +> = keyof OptionType extends ValueField + ? unknown extends OptionType['value'] + ? OptionType[ValueField] + : OptionType['value'] + : OptionType[ValueField]; + +export type GetValueType< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, + Multiple extends boolean | React.ReactNode = false, +> = false extends Multiple + ? ValueType, ValueField>[] + : ValueType, ValueField>[][]; + +export interface CascaderProps< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, + Multiple extends boolean | React.ReactNode = false, +> extends BaseCascaderProps { + checkable?: Multiple; + value?: GetValueType; + defaultValue?: GetValueType; + onChange?: ( + value: GetValueType, + selectOptions: OptionType[], + ) => void; } -export interface MultipleCascaderProps - extends BaseCascaderProps { - checkable: true | React.ReactNode; +export type SingleValueType = (string | number)[]; +export type InternalValueType = SingleValueType | SingleValueType[]; - onChange?: OnMultipleChange; +export interface InternalFieldNames extends Required { + key: string; } -export type CascaderProps = - | SingleCascaderProps - | MultipleCascaderProps; - -export type InternalCascaderProps = Omit< - SingleCascaderProps | MultipleCascaderProps, - 'onChange' -> & { +export type InternalCascaderProps = Omit & { + value?: InternalValueType; + defaultValue?: InternalValueType; onChange?: ( - value: SingleValueType | SingleValueType[], - selectOptions: OptionType[] | OptionType[][], + value: InternalValueType, + selectOptions: BaseOptionType[] | BaseOptionType[][], ) => void; }; @@ -219,10 +239,10 @@ const Cascader = React.forwardRef((props, re const multiple = !!checkable; // =========================== Values =========================== - const [rawValues, setRawValues] = useMergedState(defaultValue, { - value, - postState: toRawValues, - }); + const [rawValues, setRawValues] = useMergedState< + InternalValueType | undefined, + SingleValueType[] + >(defaultValue, { value, postState: toRawValues }); // ========================= FieldNames ========================= const mergedFieldNames = React.useMemo( @@ -301,7 +321,7 @@ const Cascader = React.forwardRef((props, re ); // =========================== Change =========================== - const triggerChange = useEvent((nextValues: ValueType) => { + const triggerChange = useEvent((nextValues: InternalValueType) => { setRawValues(nextValues); // Save perf if no need trigger event @@ -453,13 +473,17 @@ const Cascader = React.forwardRef((props, re placement={mergedPlacement} onDropdownVisibleChange={onInternalDropdownVisibleChange} // Children - getRawInputElement={() => children} + getRawInputElement={() => children as React.ReactElement} /> ); -}) as unknown as (( - props: React.PropsWithChildren> & { - ref?: React.Ref; +}) as unknown as (< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, + Multiple extends boolean | React.ReactNode = false, +>( + props: React.PropsWithChildren> & { + ref?: React.Ref; }, ) => React.ReactElement) & { displayName?: string; diff --git a/src/OptionList/Checkbox.tsx b/src/OptionList/Checkbox.tsx index f01a639b..f06cfb23 100644 --- a/src/OptionList/Checkbox.tsx +++ b/src/OptionList/Checkbox.tsx @@ -8,7 +8,7 @@ export interface CheckboxProps { halfChecked?: boolean; disabled?: boolean; onClick?: React.MouseEventHandler; - disableCheckbox: boolean; + disableCheckbox?: boolean; } export default function Checkbox({ diff --git a/src/OptionList/Column.tsx b/src/OptionList/Column.tsx index d1bac9f5..82ec25cb 100644 --- a/src/OptionList/Column.tsx +++ b/src/OptionList/Column.tsx @@ -8,10 +8,10 @@ import Checkbox from './Checkbox'; export const FIX_LABEL = '__cascader_fix_label__'; -export interface ColumnProps { +export interface ColumnProps { prefixCls: string; multiple?: boolean; - options: DefaultOptionType[]; + options: OptionType[]; /** Current Column opened item key */ activeValue?: React.Key; /** The value path before current column */ @@ -26,7 +26,7 @@ export interface ColumnProps { searchValue?: string; } -export default function Column({ +export default function Column({ prefixCls, multiple, options, @@ -40,7 +40,7 @@ export default function Column({ loadingKeys, isSelectable, searchValue, -}: ColumnProps) { +}: ColumnProps) { const menuPrefixCls = `${prefixCls}-menu`; const menuItemPrefixCls = `${prefixCls}-menu-item`; @@ -61,7 +61,7 @@ export default function Column({ () => options.map(option => { const { disabled, disableCheckbox } = option; - const searchOptions = option[SEARCH_MARK]; + const searchOptions: Record[] = option[SEARCH_MARK]; const label = option[FIX_LABEL] ?? option[fieldNames.label]; const value = option[fieldNames.value]; @@ -135,7 +135,7 @@ export default function Column({ }; // >>>>> Title - let title: string; + let title: string | undefined; if (typeof option.title === 'string') { title = option.title; } else if (typeof label === 'string') { diff --git a/src/OptionList/List.tsx b/src/OptionList/List.tsx index 9b0ce3a4..4beedd05 100644 --- a/src/OptionList/List.tsx +++ b/src/OptionList/List.tsx @@ -27,7 +27,7 @@ export type RawOptionListProps = Pick< const RawOptionList = React.forwardRef((props, ref) => { const { prefixCls, multiple, searchValue, toggleOpen, notFoundContent, direction, open } = props; - const containerRef = React.useRef(); + const containerRef = React.useRef(null); const rtl = direction === 'rtl'; const { @@ -46,7 +46,7 @@ const RawOptionList = React.forwardRef(( const mergedPrefixCls = dropdownPrefixCls || prefixCls; // ========================= loadData ========================= - const [loadingKeys, setLoadingKeys] = React.useState([]); + const [loadingKeys, setLoadingKeys] = React.useState([]); const internalLoadData = (valueCells: React.Key[]) => { // Do not load when search @@ -71,7 +71,7 @@ const RawOptionList = React.forwardRef(( React.useEffect(() => { if (loadingKeys.length) { loadingKeys.forEach(loadingKey => { - const valueStrCells = toPathValueStr(loadingKey); + const valueStrCells = toPathValueStr(loadingKey as string); const optionList = toPathOptions(valueStrCells, options, fieldNames, true).map( ({ option }) => option, ); diff --git a/src/OptionList/useActive.ts b/src/OptionList/useActive.ts index 849f9bfb..ca0519ec 100644 --- a/src/OptionList/useActive.ts +++ b/src/OptionList/useActive.ts @@ -4,9 +4,9 @@ import CascaderContext from '../context'; /** * Control the active open options path. */ -export default ( - multiple: boolean, - open: boolean, +const useActive = ( + multiple?: boolean, + open?: boolean, ): [React.Key[], (activeValueCells: React.Key[]) => void] => { const { values } = React.useContext(CascaderContext); @@ -29,3 +29,5 @@ export default ( return [activeValueCells, setActiveValueCells]; }; + +export default useActive; diff --git a/src/OptionList/useKeyboard.ts b/src/OptionList/useKeyboard.ts index cc607995..b587368a 100644 --- a/src/OptionList/useKeyboard.ts +++ b/src/OptionList/useKeyboard.ts @@ -13,10 +13,10 @@ export default ( setActiveValueCells: (activeValueCells: React.Key[]) => void, onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void, contextProps: { - direction: 'ltr' | 'rtl'; + direction?: 'ltr' | 'rtl'; searchValue: string; toggleOpen: (open?: boolean) => void; - open: boolean; + open?: boolean; }, ) => { const { direction, searchValue, toggleOpen, open } = contextProps; diff --git a/src/Panel.tsx b/src/Panel.tsx index d1c7d0d3..9fa7e6dd 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -1,7 +1,14 @@ import classNames from 'classnames'; import { useEvent, useMergedState } from 'rc-util'; import * as React from 'react'; -import type { CascaderProps, InternalCascaderProps, SingleValueType, ValueType } from './Cascader'; +import type { + CascaderProps, + DefaultOptionType, + InternalCascaderProps, + InternalValueType, + SingleValueType, +} from './Cascader'; +import type { CascaderContextProps } from './context'; import CascaderContext from './context'; import useMissingValues from './hooks/useMissingValues'; import useOptions from './hooks/useOptions'; @@ -30,11 +37,19 @@ export type PickType = | 'direction' | 'notFoundContent'; -export type PanelProps = Pick; +export type PanelProps< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, + Multiple extends boolean | React.ReactNode = false, +> = Pick, PickType>; function noop() {} -export default function Panel(props: PanelProps) { +export default function Panel< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, + Multiple extends boolean | React.ReactNode = false, +>(props: PanelProps) { const { prefixCls = 'rc-cascader', style, @@ -59,10 +74,10 @@ export default function Panel(props: PanelProps) { const multiple = !!checkable; // ========================= Values ========================= - const [rawValues, setRawValues] = useMergedState(defaultValue, { - value, - postState: toRawValues, - }); + const [rawValues, setRawValues] = useMergedState< + InternalValueType | undefined, + SingleValueType[] + >(defaultValue, { value, postState: toRawValues }); // ========================= FieldNames ========================= const mergedFieldNames = React.useMemo( @@ -91,7 +106,7 @@ export default function Panel(props: PanelProps) { ); // =========================== Change =========================== - const triggerChange = useEvent((nextValues: ValueType) => { + const triggerChange = useEvent((nextValues: InternalValueType) => { setRawValues(nextValues); // Save perf if no need trigger event @@ -126,7 +141,7 @@ export default function Panel(props: PanelProps) { }); // ======================== Context ========================= - const cascaderContext = React.useMemo( + const cascaderContext = React.useMemo( () => ({ options: mergedOptions, fieldNames: mergedFieldNames, @@ -136,12 +151,12 @@ export default function Panel(props: PanelProps) { onSelect: onInternalSelect, checkable, searchOptions: [], - dropdownPrefixCls: null, + dropdownPrefixCls: undefined, loadData, expandTrigger, expandIcon, loadingIcon, - dropdownMenuColumnStyle: null, + dropdownMenuColumnStyle: undefined, }), [ mergedOptions, @@ -180,7 +195,7 @@ export default function Panel(props: PanelProps) { ) : ( ; fieldNames: InternalFieldNames; values: SingleValueType[]; halfValues: SingleValueType[]; @@ -24,6 +24,6 @@ export interface CascaderContextProps { optionRender?: CascaderProps['optionRender']; } -const CascaderContext = React.createContext(null); +const CascaderContext = React.createContext({} as CascaderContextProps); export default CascaderContext; diff --git a/src/hooks/useDisplayValues.ts b/src/hooks/useDisplayValues.ts index 0463bd2e..cd4aa5ec 100644 --- a/src/hooks/useDisplayValues.ts +++ b/src/hooks/useDisplayValues.ts @@ -20,7 +20,7 @@ export default ( displayRender || // Default displayRender (labels => { - const mergedLabels = multiple ? labels.slice(-1) : labels; + const mergedLabels: React.ReactNode[] = multiple ? labels.slice(-1) : labels; const SPLIT = ' / '; if (mergedLabels.every(label => ['string', 'number'].includes(typeof label))) { @@ -28,7 +28,7 @@ export default ( } // If exist non-string value, use ReactNode instead - return mergedLabels.reduce((list, label, index) => { + return mergedLabels.reduce((list: React.ReactNode[], label, index) => { const keyedLabel = React.isValidElement(label) ? React.cloneElement(label, { key: index }) : label; @@ -36,7 +36,6 @@ export default ( if (index === 0) { return [keyedLabel]; } - return [...list, SPLIT, keyedLabel]; }, []); }); diff --git a/src/hooks/useEntities.ts b/src/hooks/useEntities.ts index fdb37f9b..47a83207 100644 --- a/src/hooks/useEntities.ts +++ b/src/hooks/useEntities.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { convertDataToEntities } from 'rc-tree/lib/utils/treeUtil'; import type { DefaultOptionType, InternalFieldNames } from '../Cascader'; -import type { DataEntity } from 'rc-tree/lib/interface'; +import type { DataEntity, DataNode } from 'rc-tree/lib/interface'; import { VALUE_SPLIT } from '../utils/commonUtil'; export interface OptionsInfo { @@ -17,23 +17,25 @@ export default (options: DefaultOptionType[], fieldNames: InternalFieldNames) => options: DefaultOptionType[]; info: OptionsInfo; }>({ - options: null, - info: null, + options: [], + info: { keyEntities: {}, pathKeyEntities: {} }, }); const getEntities: GetEntities = React.useCallback(() => { if (cacheRef.current.options !== options) { cacheRef.current.options = options; - cacheRef.current.info = convertDataToEntities(options as any, { - fieldNames, + cacheRef.current.info = convertDataToEntities(options as DataNode[], { + fieldNames: fieldNames as any, initWrapper: wrapper => ({ ...wrapper, pathKeyEntities: {}, }), - processEntity: (entity, wrapper: any) => { - const pathKey = entity.nodes.map(node => node[fieldNames.value]).join(VALUE_SPLIT); + processEntity: (entity, wrapper) => { + const pathKey = (entity.nodes as DefaultOptionType[]) + .map(node => node[fieldNames.value]) + .join(VALUE_SPLIT); - wrapper.pathKeyEntities[pathKey] = entity; + (wrapper as unknown as OptionsInfo).pathKeyEntities[pathKey] = entity; // Overwrite origin key. // this is very hack but we need let conduct logic work with connect path diff --git a/src/hooks/useOptions.ts b/src/hooks/useOptions.ts index f0759d71..ea4c93f9 100644 --- a/src/hooks/useOptions.ts +++ b/src/hooks/useOptions.ts @@ -24,7 +24,7 @@ export default function useOptions( return pathKeys.map(pathKey => { const { nodes } = keyPathEntities[pathKey]; - return nodes.map(node => node[mergedFieldNames.value]); + return nodes.map(node => (node as Record)[mergedFieldNames.value]); }); }, [getPathKeyEntities, mergedFieldNames], diff --git a/src/hooks/useSearchOptions.ts b/src/hooks/useSearchOptions.ts index 289a9191..624af9d3 100644 --- a/src/hooks/useSearchOptions.ts +++ b/src/hooks/useSearchOptions.ts @@ -3,11 +3,11 @@ import type { DefaultOptionType, InternalFieldNames, ShowSearchType } from '../C export const SEARCH_MARK = '__rc_cascader_search_mark__'; -const defaultFilter: ShowSearchType['filter'] = (search, options, { label }) => +const defaultFilter: ShowSearchType['filter'] = (search, options, { label = '' }) => options.some(opt => String(opt[label]).toLowerCase().includes(search.toLowerCase())); const defaultRender: ShowSearchType['render'] = (inputValue, path, prefixCls, fieldNames) => - path.map(opt => opt[fieldNames.label]).join(' / '); + path.map(opt => opt[fieldNames.label as string]).join(' / '); export default ( search: string, @@ -15,7 +15,7 @@ export default ( fieldNames: InternalFieldNames, prefixCls: string, config: ShowSearchType, - changeOnSelect: boolean, + changeOnSelect?: boolean, ) => { const { filter = defaultFilter, render = defaultRender, limit = 50, sort } = config; diff --git a/src/hooks/useSelect.ts b/src/hooks/useSelect.ts index 60338cd4..9c6b6540 100644 --- a/src/hooks/useSelect.ts +++ b/src/hooks/useSelect.ts @@ -1,18 +1,18 @@ import { conductCheck } from 'rc-tree/lib/utils/conductUtil'; -import type { ShowCheckedStrategy, SingleValueType, ValueType } from '../Cascader'; +import type { InternalValueType, ShowCheckedStrategy, SingleValueType } from '../Cascader'; import { toPathKey, toPathKeys } from '../utils/commonUtil'; import { formatStrategyValues } from '../utils/treeUtil'; import type { GetEntities } from './useEntities'; export default function useSelect( multiple: boolean, - triggerChange: (nextValues: ValueType) => void, + triggerChange: (nextValues: InternalValueType) => void, checkedValues: SingleValueType[], halfCheckedValues: SingleValueType[], missingCheckedValues: SingleValueType[], getPathKeyEntities: GetEntities, getValueByKeyPath: (pathKeys: React.Key[]) => SingleValueType[], - showCheckedStrategy: ShowCheckedStrategy, + showCheckedStrategy?: ShowCheckedStrategy, ) { return (valuePath: SingleValueType) => { if (!multiple) { diff --git a/src/index.tsx b/src/index.tsx index 35ab7064..b73a5f68 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,12 +3,11 @@ import Panel from './Panel'; export type { BaseOptionType, - CascaderProps, DefaultOptionType, + CascaderProps, FieldNames, - MultipleCascaderProps, ShowSearchType, - SingleCascaderProps, + CascaderRef, } from './Cascader'; export { Panel }; diff --git a/src/utils/commonUtil.ts b/src/utils/commonUtil.ts index c54eb4c0..7f8c05a5 100644 --- a/src/utils/commonUtil.ts +++ b/src/utils/commonUtil.ts @@ -2,8 +2,8 @@ import type { DefaultOptionType, FieldNames, InternalFieldNames, + InternalValueType, SingleValueType, - ValueType, } from '../Cascader'; import { SEARCH_MARK } from '../hooks/useSearchOptions'; @@ -35,13 +35,13 @@ export function fillFieldNames(fieldNames?: FieldNames): InternalFieldNames { return { label: label || 'label', value: val, - key: val, + key: val as string, children: children || 'children', }; } export function isLeaf(option: DefaultOptionType, fieldNames: FieldNames) { - return option.isLeaf ?? !option[fieldNames.children]?.length; + return option.isLeaf ?? !option[fieldNames.children as string]?.length; } export function scrollIntoParentView(element: HTMLElement) { @@ -59,14 +59,16 @@ export function scrollIntoParentView(element: HTMLElement) { } export function getFullPathKeys(options: DefaultOptionType[], fieldNames: FieldNames) { - return options.map(item => item[SEARCH_MARK]?.map(opt => opt[fieldNames.value])); + return options.map(item => + item[SEARCH_MARK]?.map((opt: Record) => opt[fieldNames.value as string]), + ); } -function isMultipleValue(value: ValueType): value is SingleValueType[] { +function isMultipleValue(value: InternalValueType): value is SingleValueType[] { return Array.isArray(value) && Array.isArray(value[0]); } -export function toRawValues(value: ValueType): SingleValueType[] { +export function toRawValues(value?: InternalValueType): SingleValueType[] { if (!value) { return []; } @@ -76,4 +78,4 @@ export function toRawValues(value: ValueType): SingleValueType[] { } return (value.length === 0 ? [] : [value]).map(val => (Array.isArray(val) ? val : [val])); -} \ No newline at end of file +} diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts index 2ba2bfd8..b514a7d8 100644 --- a/src/utils/treeUtil.ts +++ b/src/utils/treeUtil.ts @@ -10,7 +10,7 @@ import { SHOW_CHILD } from './commonUtil'; export function formatStrategyValues( pathKeys: React.Key[], getKeyPathEntities: GetEntities, - showCheckedStrategy: ShowCheckedStrategy, + showCheckedStrategy?: ShowCheckedStrategy, ) { const valueSet = new Set(pathKeys); const keyPathEntities = getKeyPathEntities(); @@ -55,7 +55,7 @@ export function toPathOptions( valueOptions.push({ value: foundOption?.[fieldNames.value] ?? valueCell, index: foundIndex, - option: foundOption, + option: foundOption as DefaultOptionType, }); currentList = foundOption?.[fieldNames.children]; diff --git a/src/utils/warningPropsUtil.ts b/src/utils/warningPropsUtil.ts index 2b406996..56efed2e 100644 --- a/src/utils/warningPropsUtil.ts +++ b/src/utils/warningPropsUtil.ts @@ -26,14 +26,14 @@ export function warningNullOptions(options: DefaultOptionType[], fieldNames: Fie for (let i = 0; i < optionsList.length; i++) { const option = optionsList[i]; - if (option[fieldNames?.value] === null) { + if (option[fieldNames?.value as string] === null) { warning(false, '`value` in Cascader options should not be `null`.'); return true; } if ( - Array.isArray(option[fieldNames?.children]) && - recursiveOptions(option[fieldNames?.children]) + Array.isArray(option[fieldNames?.children as string]) && + recursiveOptions(option[fieldNames?.children as string]) ) { return true; } diff --git a/tests/Panel.spec.tsx b/tests/Panel.spec.tsx index 10ffb9f3..452f1be4 100644 --- a/tests/Panel.spec.tsx +++ b/tests/Panel.spec.tsx @@ -88,13 +88,13 @@ describe('Cascader.Panel', () => { it('notFoundContent', () => { const { container } = render(); - expect(container.querySelector('.rc-cascader-panel-empty').textContent).toEqual('Hello World'); + expect(container.querySelector('.rc-cascader-panel-empty')?.textContent).toEqual('Hello World'); }); it('control', () => { const { container } = render(); const checkedLi = container.querySelector('[aria-checked="true"]'); - expect(checkedLi.textContent).toEqual('Little'); + expect(checkedLi?.textContent).toEqual('Little'); }); }); diff --git a/tests/checkable.spec.tsx b/tests/checkable.spec.tsx index 5ccf02c2..032f889f 100644 --- a/tests/checkable.spec.tsx +++ b/tests/checkable.spec.tsx @@ -253,7 +253,9 @@ describe('Cascader.Checkable', () => { />, ); - fireEvent.click(document.querySelector('[data-path-key="China"] .rc-cascader-checkbox')); + fireEvent.click( + document.querySelector('[data-path-key="China"] .rc-cascader-checkbox') as HTMLElement, + ); expect(onChange).toHaveBeenCalledWith([['China', 'beijing'], ['China']], expect.anything()); }); diff --git a/tests/demoOptions.ts b/tests/demoOptions.ts index 283952aa..5692c229 100644 --- a/tests/demoOptions.ts +++ b/tests/demoOptions.ts @@ -1,6 +1,6 @@ -import type { DefaultOptionType } from '@/Cascader'; +import type { BaseOptionType } from '../src'; -export const optionsForActiveMenuItems: DefaultOptionType[] = [ +export const optionsForActiveMenuItems: BaseOptionType[] = [ { value: '1', label: '1', @@ -35,7 +35,7 @@ export const optionsForActiveMenuItems: DefaultOptionType[] = [ }, ]; -export const addressOptions: DefaultOptionType[] = [ +export const addressOptions: BaseOptionType[] = [ { label: '福建', value: 'fj', @@ -171,11 +171,11 @@ export const addressOptionsForUneven = [ { label: '高雄', value: 'gaoxiong', - } - ] + }, + ], }, { label: '香港', value: 'xg', }, -] \ No newline at end of file +]; diff --git a/tests/fieldNames.spec.tsx b/tests/fieldNames.spec.tsx index c90d3227..a485d1cd 100644 --- a/tests/fieldNames.spec.tsx +++ b/tests/fieldNames.spec.tsx @@ -24,13 +24,13 @@ describe('Cascader.FieldNames', () => { }, ], }, - ] as any; + ]; const fieldNames = { label: 'customTitle', value: 'customValue', children: 'customChildren', - }; + } as const; it('customize', () => { const onChange = jest.fn(); @@ -92,7 +92,7 @@ describe('Cascader.FieldNames', () => { fieldNames={fieldNames} defaultValue={['bamboo', 'little', 'toy']} displayRender={(labels, selectOptions) => - `${labels.join('->')} & ${selectOptions.map((opt: any) => opt.customValue).join('>>')}` + `${labels.join('->')} & ${selectOptions?.map(opt => opt.customValue).join('>>')}` } />, ); @@ -105,7 +105,7 @@ describe('Cascader.FieldNames', () => { it('same title & value should show correct title', () => { const wrapper = mount( { - let selectedValue; - const onChange = function onChange(value) { + let selectedValue: any; + const onChange: CascaderProps['onChange'] = function onChange(value) { + selectedValue = value; + }; + const onMultipleChange: CascaderProps['onChange'] = value => { selectedValue = value; }; @@ -91,7 +96,7 @@ describe('Cascader.Basic', () => { checkable changeOnSelect options={addressOptions} - onChange={onChange} + onChange={onMultipleChange} showCheckedStrategy={'SHOW_PARENT'} > @@ -114,7 +119,7 @@ describe('Cascader.Basic', () => { checkable changeOnSelect options={addressOptions} - onChange={onChange} + onChange={onMultipleChange} showCheckedStrategy={'SHOW_CHILD'} > @@ -677,7 +682,7 @@ describe('Cascader.Basic', () => { }); describe('focus test', () => { - let domSpy; + let domSpy: any; let focusTimes = 0; let blurTimes = 0; @@ -702,18 +707,18 @@ describe('Cascader.Basic', () => { }); it('focus', () => { - const cascaderRef = React.createRef() as any; + const cascaderRef = React.createRef(); mount(); - cascaderRef.current.focus(); + cascaderRef.current?.focus(); expect(focusTimes === 1).toBeTruthy(); }); it('blur', () => { - const cascaderRef = React.createRef() as any; + const cascaderRef = React.createRef(); mount(); - cascaderRef.current.blur(); + cascaderRef.current?.blur(); expect(blurTimes === 1).toBeTruthy(); }); }); @@ -1024,7 +1029,7 @@ describe('Cascader.Basic', () => { it('support custom cascader', () => { const wrapper = mount(); - expect(wrapper.find('.rc-cascader-dropdown').props().style.zIndex).toBe(999); + expect(wrapper.find('.rc-cascader-dropdown').props().style?.zIndex).toBe(999); }); it('`null` is a value in Cascader options should throw a warning', () => { @@ -1057,4 +1062,7 @@ describe('Cascader.Basic', () => { ); errorSpy.mockReset(); }); + it('toRawValues undefined', () => { + expect(toRawValues()).toEqual([]); + }); }); diff --git a/tests/keyboard.spec.tsx b/tests/keyboard.spec.tsx index 5bc83a59..e1f7a53d 100644 --- a/tests/keyboard.spec.tsx +++ b/tests/keyboard.spec.tsx @@ -1,15 +1,16 @@ import { mount } from 'enzyme'; import KeyCode from 'rc-util/lib/KeyCode'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; import { addressOptions } from './demoOptions'; import React from 'react'; describe('Cascader.Keyboard', () => { - let wrapper; - let selectedValue; - let selectedOptions; + let wrapper: any; + let selectedValue: any; + let selectedOptions: any; let menus; - const onChange = (value, options) => { + const onChange: CascaderProps['onChange'] = (value, options) => { selectedValue = value; selectedOptions = options; }; @@ -78,8 +79,8 @@ describe('Cascader.Keyboard', () => { expect(selectedValue).toEqual(['zj', 'hangzhou', 'yuhang']); expect(selectedOptions).toEqual([ addressOptions[1], - addressOptions[1].children[0], - addressOptions[1].children[0].children[0], + addressOptions[1]?.children?.[0], + addressOptions[1]?.children?.[0]?.children?.[0], ]); }); @@ -91,8 +92,8 @@ describe('Cascader.Keyboard', () => { expect(selectedValue).toEqual(['zj', 'hangzhou', 'yuhang']); expect(selectedOptions).toEqual([ addressOptions[1], - addressOptions[1].children[0], - addressOptions[1].children[0].children[0], + addressOptions[1]?.children?.[0], + addressOptions[1]?.children?.[0]?.children?.[0], ]); }); it('enter on search when has same sub key', () => { diff --git a/tests/loadData.spec.tsx b/tests/loadData.spec.tsx index 7b71ea52..a8ac9af4 100644 --- a/tests/loadData.spec.tsx +++ b/tests/loadData.spec.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { mount } from './enzyme'; +import type { CascaderProps } from '../src'; import Cascader from '../src'; describe('Cascader.LoadData', () => { @@ -144,7 +145,7 @@ describe('Cascader.LoadData', () => { const Demo = () => { const [options, setOptions] = React.useState([{ label: 'top', value: 'top', isLeaf: false }]); - const loadData = selectedOptions => { + const loadData: CascaderProps['loadData'] = selectedOptions => { Promise.resolve().then(() => { act(() => { selectedOptions[selectedOptions.length - 1].children = [ diff --git a/tests/search.spec.tsx b/tests/search.spec.tsx index 279c5d6a..2083f4cd 100644 --- a/tests/search.spec.tsx +++ b/tests/search.spec.tsx @@ -43,7 +43,7 @@ describe('Cascader.Search', () => { }, ], }, - ] as any; + ]; it('default search', () => { const onSearch = jest.fn(); @@ -106,7 +106,8 @@ describe('Cascader.Search', () => { const finalA = pathA[pathA.length - 1]; const finalB = pathB[pathB.length - 1]; - if (finalA.value < finalB.value) { + // this value is string + if ((finalA.value as any) < (finalB.value as any)) { return -1; } return 1; @@ -231,14 +232,14 @@ describe('Cascader.Search', () => { ], }, ]; - function customFilter(inputValue, path) { - return path.some(option => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1); - } const wrapper = mount( + path.some(option => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1), + }} />, ); wrapper.find('input').simulate('change', { target: { value: 'z' } }); @@ -250,16 +251,16 @@ describe('Cascader.Search', () => { const { container } = render(); // Search - fireEvent.change(container.querySelector('input'), { + fireEvent.change(container.querySelector('input') as HTMLElement, { target: { value: 'bamboo', }, }); // Click - fireEvent.click(document.querySelector('.rc-cascader-menu-item-content')); + fireEvent.click(document.querySelector('.rc-cascader-menu-item-content') as HTMLElement); expect(document.querySelector('.rc-cascader-dropdown-hidden')).toBeTruthy(); - expect(document.querySelector('.rc-cascader-menu-item-content').textContent).toBe( + expect(document.querySelector('.rc-cascader-menu-item-content')?.textContent).toBe( 'Label Bamboo / Label Little / Toy Fish', ); }); @@ -300,7 +301,7 @@ describe('Cascader.Search', () => { expect(container.querySelectorAll('.rc-cascader-menu-item')).toHaveLength(1); expect(container.querySelectorAll('.rc-cascader-menu-item-disabled')).toHaveLength(1); - expect(container.querySelector('.rc-cascader-menu-item-disabled').textContent).toEqual( + expect(container.querySelector('.rc-cascader-menu-item-disabled')?.textContent).toEqual( 'bamboo / little', ); }); @@ -312,7 +313,7 @@ describe('Cascader.Search', () => { optionRender={option => `${option.label} - test`} />, ); - expect(container.querySelector('.rc-cascader-menu-item-content').innerHTML).toEqual( + expect(container.querySelector('.rc-cascader-menu-item-content')?.innerHTML).toEqual( 'bamboo - test', ); rerender( @@ -322,7 +323,7 @@ describe('Cascader.Search', () => { optionRender={option => JSON.stringify(option)} />, ); - expect(container.querySelector('.rc-cascader-menu-item-content').innerHTML).toEqual( + expect(container.querySelector('.rc-cascader-menu-item-content')?.innerHTML).toEqual( '{"label":"bamboo","disabled":true,"value":"bamboo"}', ); }); diff --git a/tests/selector.spec.tsx b/tests/selector.spec.tsx index 840ca194..e7387714 100644 --- a/tests/selector.spec.tsx +++ b/tests/selector.spec.tsx @@ -10,7 +10,7 @@ jest.mock('../src/OptionList/useActive', () => (multiple: boolean, open: boolean const originHook = jest.requireActual('../src/OptionList/useActive').default; const [activeValueCells, setActiveValueCells] = originHook(multiple, open); - global.activeValueCells = activeValueCells; + (global as any).activeValueCells = activeValueCells; return [activeValueCells, setActiveValueCells]; }); @@ -23,7 +23,7 @@ describe('Cascader.Selector', () => { , ); - fireEvent.mouseDown(container.querySelector('.rc-cascader-clear-icon')); + fireEvent.mouseDown(container.querySelector('.rc-cascader-clear-icon') as HTMLElement); expect(onChange).toHaveBeenCalledWith(undefined, undefined); }); @@ -41,16 +41,16 @@ describe('Cascader.Selector', () => { ); // Open and select - fireEvent.mouseDown(container.querySelector('.rc-cascader-selector')); + fireEvent.mouseDown(container.querySelector('.rc-cascader-selector') as HTMLElement); expect(container.querySelector('.rc-cascader-open')).toBeTruthy(); - fireEvent.click(container.querySelector('.rc-cascader-menu-item-content')); + fireEvent.click(container.querySelector('.rc-cascader-menu-item-content') as HTMLElement); fireEvent.click(container.querySelectorAll('.rc-cascader-menu-item-content')[1]); expect(container.querySelector('.rc-cascader-open')).toBeFalsy(); // Clear - fireEvent.mouseDown(container.querySelector('.rc-cascader-clear-icon')); - expect(global.activeValueCells).toEqual([]); + fireEvent.mouseDown(container.querySelector('.rc-cascader-clear-icon') as HTMLElement); + expect((global as any).activeValueCells).toEqual([]); }); it('multiple', () => { diff --git a/tsconfig.json b/tsconfig.json index 94ced34b..f0d983d0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,17 +6,12 @@ "jsx": "react", "declaration": true, "skipLibCheck": true, + "strict": true, "esModuleInterop": true, "paths": { - "@/*": [ - "src/*" - ], - "@@/*": [ - "src/.umi/*" - ], - "rc-cascader": [ - "src/index.ts" - ] + "@/*": ["src/*"], + "@@/*": ["src/.umi/*"], + "rc-cascader": ["src/index.ts"] } } -} \ No newline at end of file +}