2023-08-27 23:57:23 +08:00
import * as React from 'react' ;
2022-06-21 10:24:52 +08:00
import LeftOutlined from '@ant-design/icons/LeftOutlined' ;
import LoadingOutlined from '@ant-design/icons/LoadingOutlined' ;
import RightOutlined from '@ant-design/icons/RightOutlined' ;
2015-12-29 18:31:48 +08:00
import classNames from 'classnames' ;
2021-12-21 21:38:11 +08:00
import type {
BaseOptionType ,
DefaultOptionType ,
2022-06-21 10:24:52 +08:00
FieldNames ,
MultipleCascaderProps as RcMultipleCascaderProps ,
SingleCascaderProps as RcSingleCascaderProps ,
2023-03-30 14:55:34 +08:00
ShowSearchType ,
2021-12-21 21:38:11 +08:00
} from 'rc-cascader' ;
2022-06-21 10:24:52 +08:00
import RcCascader from 'rc-cascader' ;
2023-03-30 14:55:34 +08:00
import type { Placement } from 'rc-select/lib/BaseSelect' ;
2021-01-13 21:00:30 +08:00
import omit from 'rc-util/lib/omit' ;
2023-08-27 23:57:23 +08:00
2022-05-07 14:31:54 +08:00
import type { SelectCommonPlacement } from '../_util/motion' ;
2023-07-25 10:54:58 +08:00
import { getTransitionName } from '../_util/motion' ;
2023-08-27 23:57:23 +08:00
import genPurePanel from '../_util/PurePanel' ;
2022-05-07 14:31:54 +08:00
import type { InputStatus } from '../_util/statusUtils' ;
import { getMergedStatus , getStatusClassNames } from '../_util/statusUtils' ;
2023-09-11 17:28:04 +08:00
import { devUseWarning } from '../_util/warning' ;
2023-05-12 14:53:47 +08:00
import { ConfigContext } from '../config-provider' ;
import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty' ;
2023-08-27 23:57:23 +08:00
import DisabledContext from '../config-provider/DisabledContext' ;
2023-05-12 14:53:47 +08:00
import useSize from '../config-provider/hooks/useSize' ;
2023-08-27 23:57:23 +08:00
import type { SizeType } from '../config-provider/SizeContext' ;
2023-03-30 14:55:34 +08:00
import { FormItemInputContext } from '../form/context' ;
2022-06-21 19:40:22 +08:00
import useSelectStyle from '../select/style' ;
2023-04-04 13:43:53 +08:00
import useBuiltinPlacements from '../select/useBuiltinPlacements' ;
2023-03-05 23:29:03 +08:00
import useShowArrow from '../select/useShowArrow' ;
2023-09-11 17:28:04 +08:00
import useIcons from '../select/useIcons' ;
2023-05-12 14:53:47 +08:00
import { useCompactItemContext } from '../space/Compact' ;
2023-03-05 23:29:03 +08:00
import useStyle from './style' ;
2015-12-29 11:46:13 +08:00
2021-08-31 15:51:02 +08:00
// Align the design since we use `rc-select` in root. This help:
// - List search content will show all content
// - Hover opacity style
// - Search filter match case
2023-08-27 23:57:23 +08:00
export type { BaseOptionType , DefaultOptionType } ;
2021-11-24 17:45:13 +08:00
2021-08-31 15:51:02 +08:00
export type FieldNamesType = FieldNames ;
export type FilledFieldNamesType = Required < FieldNamesType > ;
2022-03-26 00:17:34 +08:00
const { SHOW_CHILD , SHOW_PARENT } = RcCascader ;
2023-01-16 09:55:52 +08:00
function highlightKeyword ( str : string , lowerKeyword : string , prefixCls? : string ) {
2021-08-31 15:51:02 +08:00
const cells = str
. toLowerCase ( )
. split ( lowerKeyword )
. reduce ( ( list , cur , index ) = > ( index === 0 ? [ cur ] : [ . . . list , lowerKeyword , cur ] ) , [ ] ) ;
const fillCells : React.ReactNode [ ] = [ ] ;
let start = 0 ;
cells . forEach ( ( cell , index ) = > {
const end = start + cell . length ;
let originWorld : React.ReactNode = str . slice ( start , end ) ;
start = end ;
if ( index % 2 === 1 ) {
originWorld = (
2022-01-11 13:18:17 +08:00
// eslint-disable-next-line react/no-array-index-key
2023-02-23 21:56:43 +08:00
< span className = { ` ${ prefixCls } -menu-item-keyword ` } key = { ` separator- ${ index } ` } >
2021-08-31 15:51:02 +08:00
{ originWorld }
< / span >
) ;
}
2016-09-01 11:08:41 +08:00
2021-08-31 15:51:02 +08:00
fillCells . push ( originWorld ) ;
2016-09-01 11:08:41 +08:00
} ) ;
2021-08-31 15:51:02 +08:00
return fillCells ;
2016-09-01 11:08:41 +08:00
}
2021-08-31 15:51:02 +08:00
const defaultSearchRender : ShowSearchType [ 'render' ] = ( inputValue , path , prefixCls , fieldNames ) = > {
const optionList : React.ReactNode [ ] = [ ] ;
2018-11-06 12:36:03 +08:00
2021-08-31 15:51:02 +08:00
// We do lower here to save perf
const lower = inputValue . toLowerCase ( ) ;
2018-05-04 17:27:34 +08:00
2021-08-31 15:51:02 +08:00
path . forEach ( ( node , index ) = > {
if ( index !== 0 ) {
optionList . push ( ' / ' ) ;
2018-12-14 11:40:47 +08:00
}
2023-03-30 14:55:34 +08:00
let label = node [ fieldNames . label ! ] ;
2021-08-31 15:51:02 +08:00
const type = typeof label ;
if ( type === 'string' || type === 'number' ) {
label = highlightKeyword ( String ( label ) , lower , prefixCls ) ;
}
2016-10-24 12:04:26 +08:00
2021-08-31 15:51:02 +08:00
optionList . push ( label ) ;
2019-07-08 10:57:34 +08:00
} ) ;
2021-08-31 15:51:02 +08:00
return optionList ;
} ;
2019-07-08 10:57:34 +08:00
2023-06-27 23:37:24 +08:00
type SingleCascaderProps < OptionType extends BaseOptionType > = Omit <
RcSingleCascaderProps < OptionType > ,
'checkable' | 'options'
> & {
2022-02-12 21:56:55 +08:00
multiple? : false ;
} ;
2023-06-27 23:37:24 +08:00
type MultipleCascaderProps < OptionType extends BaseOptionType > = Omit <
RcMultipleCascaderProps < OptionType > ,
'checkable' | 'options'
> & {
2022-02-12 21:56:55 +08:00
multiple : true ;
} ;
2023-06-27 23:37:24 +08:00
type UnionCascaderProps < OptionType extends BaseOptionType > =
| SingleCascaderProps < OptionType >
| MultipleCascaderProps < OptionType > ;
export type CascaderProps < DataNodeType extends BaseOptionType = any > =
UnionCascaderProps < DataNodeType > & {
multiple? : boolean ;
size? : SizeType ;
2023-07-19 20:27:09 +08:00
/ * *
* @deprecated ` showArrow ` is deprecated which will be removed in next major version . It will be a
* default behavior , you can hide it by setting ` suffixIcon ` to null .
* /
showArrow? : boolean ;
2023-06-27 23:37:24 +08:00
disabled? : boolean ;
bordered? : boolean ;
placement? : SelectCommonPlacement ;
suffixIcon? : React.ReactNode ;
options? : DataNodeType [ ] ;
status? : InputStatus ;
2023-08-09 11:57:01 +08:00
autoClearSearchValue? : boolean ;
2023-06-27 23:37:24 +08:00
rootClassName? : string ;
popupClassName? : string ;
/** @deprecated Please use `popupClassName` instead */
dropdownClassName? : string ;
} ;
2020-11-30 11:31:22 +08:00
2021-11-24 17:45:13 +08:00
export interface CascaderRef {
2021-08-31 15:51:02 +08:00
focus : ( ) = > void ;
blur : ( ) = > void ;
}
2023-07-18 12:18:51 +08:00
const Cascader = React . forwardRef < CascaderRef , CascaderProps < any > > ( ( props , ref ) = > {
2021-08-31 15:51:02 +08:00
const {
prefixCls : customizePrefixCls ,
size : customizeSize ,
2022-04-29 20:48:10 +08:00
disabled : customDisabled ,
2021-08-31 15:51:02 +08:00
className ,
2023-01-20 11:03:50 +08:00
rootClassName ,
2021-08-31 15:51:02 +08:00
multiple ,
bordered = true ,
transitionName ,
choiceTransitionName = '' ,
2021-09-13 15:59:57 +08:00
popupClassName ,
2022-09-08 14:33:11 +08:00
dropdownClassName ,
2021-08-31 15:51:02 +08:00
expandIcon ,
2022-01-20 16:54:47 +08:00
placement ,
2021-08-31 15:51:02 +08:00
showSearch ,
allowClear = true ,
notFoundContent ,
direction ,
2021-09-13 15:59:57 +08:00
getPopupContainer ,
2022-02-17 15:08:13 +08:00
status : customStatus ,
showArrow ,
2023-04-04 13:43:53 +08:00
builtinPlacements ,
2023-06-27 17:13:27 +08:00
style ,
2021-08-31 15:51:02 +08:00
. . . rest
} = props ;
2023-01-02 22:18:35 +08:00
const restProps = omit ( rest , [ 'suffixIcon' ] ) ;
2021-08-31 15:51:02 +08:00
const {
2021-09-13 15:59:57 +08:00
getPopupContainer : getContextPopupContainer ,
2021-08-31 15:51:02 +08:00
getPrefixCls ,
renderEmpty ,
direction : rootDirection ,
2023-04-07 10:17:00 +08:00
popupOverflow ,
2023-06-27 17:13:27 +08:00
cascader ,
2022-12-06 21:09:59 +08:00
} = React . useContext ( ConfigContext ) ;
2021-08-31 15:51:02 +08:00
const mergedDirection = direction || rootDirection ;
const isRtl = mergedDirection === 'rtl' ;
2022-03-24 21:54:20 +08:00
// =================== Form =====================
2022-03-25 17:48:12 +08:00
const {
status : contextStatus ,
hasFeedback ,
isFormItemInput ,
feedbackIcon ,
2022-12-06 21:09:59 +08:00
} = React . useContext ( FormItemInputContext ) ;
2022-02-17 15:08:13 +08:00
const mergedStatus = getMergedStatus ( contextStatus , customStatus ) ;
2021-09-13 15:59:57 +08:00
// =================== Warning =====================
2022-09-08 14:33:11 +08:00
if ( process . env . NODE_ENV !== 'production' ) {
2023-09-13 22:07:33 +08:00
const warning = devUseWarning ( 'Cascader' ) ;
2023-09-11 17:28:04 +08:00
2023-09-13 22:07:33 +08:00
warning . deprecated ( ! dropdownClassName , 'dropdownClassName' , 'popupClassName' ) ;
2023-07-19 20:27:09 +08:00
warning (
! ( 'showArrow' in props ) ,
2023-09-11 17:28:04 +08:00
'deprecated' ,
2023-07-19 20:27:09 +08:00
'`showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.' ,
) ;
2022-09-08 14:33:11 +08:00
}
2021-09-13 15:59:57 +08:00
2021-08-31 15:51:02 +08:00
// =================== No Found ====================
2023-01-09 10:04:35 +08:00
const mergedNotFoundContent = notFoundContent || renderEmpty ? . ( 'Cascader' ) || (
< DefaultRenderEmpty componentName = "Cascader" / >
) ;
2021-08-31 15:51:02 +08:00
// ==================== Prefix =====================
const rootPrefixCls = getPrefixCls ( ) ;
const prefixCls = getPrefixCls ( 'select' , customizePrefixCls ) ;
const cascaderPrefixCls = getPrefixCls ( 'cascader' , customizePrefixCls ) ;
2022-04-07 12:20:34 +08:00
const [ wrapSelectSSR , hashId ] = useSelectStyle ( prefixCls ) ;
2022-03-09 00:29:00 +08:00
const [ wrapCascaderSSR ] = useStyle ( cascaderPrefixCls ) ;
2022-10-18 16:23:10 +08:00
const { compactSize , compactItemClassnames } = useCompactItemContext ( prefixCls , direction ) ;
2021-08-31 15:51:02 +08:00
// =================== Dropdown ====================
2021-09-13 15:59:57 +08:00
const mergedDropdownClassName = classNames (
2022-09-08 14:33:11 +08:00
popupClassName || dropdownClassName ,
2021-09-13 15:59:57 +08:00
` ${ cascaderPrefixCls } -dropdown ` ,
{
[ ` ${ cascaderPrefixCls } -dropdown-rtl ` ] : mergedDirection === 'rtl' ,
} ,
2023-01-20 11:03:50 +08:00
rootClassName ,
2022-03-09 00:29:00 +08:00
hashId ,
2021-09-13 15:59:57 +08:00
) ;
2018-12-14 11:40:47 +08:00
2021-08-31 15:51:02 +08:00
// ==================== Search =====================
const mergedShowSearch = React . useMemo ( ( ) = > {
if ( ! showSearch ) {
return showSearch ;
2019-07-08 10:57:34 +08:00
}
2021-08-31 15:51:02 +08:00
let searchConfig : ShowSearchType = {
render : defaultSearchRender ,
2015-12-29 11:46:13 +08:00
} ;
2016-03-29 14:01:10 +08:00
2021-08-31 15:51:02 +08:00
if ( typeof showSearch === 'object' ) {
searchConfig = {
. . . searchConfig ,
. . . showSearch ,
} ;
2020-08-14 17:53:34 +08:00
}
2021-08-31 15:51:02 +08:00
return searchConfig ;
} , [ showSearch ] ) ;
2019-08-05 18:38:10 +08:00
2021-08-31 15:51:02 +08:00
// ===================== Size ======================
2023-06-19 14:26:48 +08:00
const mergedSize = useSize ( ( ctx ) = > customizeSize ? ? compactSize ? ? ctx ) ;
2016-09-01 11:08:41 +08:00
2022-04-29 20:48:10 +08:00
// ===================== Disabled =====================
const disabled = React . useContext ( DisabledContext ) ;
2022-09-20 16:48:59 +08:00
const mergedDisabled = customDisabled ? ? disabled ;
2022-04-29 20:48:10 +08:00
2021-08-31 15:51:02 +08:00
// ===================== Icon ======================
let mergedExpandIcon = expandIcon ;
if ( ! expandIcon ) {
mergedExpandIcon = isRtl ? < LeftOutlined / > : < RightOutlined / > ;
2015-12-29 21:18:27 +08:00
}
2016-03-29 14:01:10 +08:00
2021-08-31 15:51:02 +08:00
const loadingIcon = (
< span className = { ` ${ prefixCls } -menu-item-loading-icon ` } >
2022-01-21 10:40:34 +08:00
< LoadingOutlined spin / >
2021-08-31 15:51:02 +08:00
< / span >
) ;
2017-11-19 01:41:40 +08:00
2021-08-31 15:51:02 +08:00
// =================== Multiple ====================
const checkable = React . useMemo (
( ) = > ( multiple ? < span className = { ` ${ cascaderPrefixCls } -checkbox-inner ` } / > : false ) ,
[ multiple ] ,
) ;
2017-11-19 01:41:40 +08:00
2021-08-31 15:51:02 +08:00
// ===================== Icons =====================
2023-07-19 20:27:09 +08:00
const showSuffixIcon = useShowArrow ( props . suffixIcon , showArrow ) ;
2023-09-11 17:28:04 +08:00
const { suffixIcon , removeIcon , clearIcon } = useIcons ( {
2021-08-31 15:51:02 +08:00
. . . props ,
2022-02-17 15:08:13 +08:00
hasFeedback ,
2022-03-25 17:48:12 +08:00
feedbackIcon ,
2023-07-19 20:27:09 +08:00
showSuffixIcon ,
2021-08-31 15:51:02 +08:00
multiple ,
prefixCls ,
2023-08-02 14:21:11 +08:00
componentName : 'Cascader' ,
2021-08-31 15:51:02 +08:00
} ) ;
2020-01-02 19:10:16 +08:00
2022-01-20 16:54:47 +08:00
// ===================== Placement =====================
2023-03-30 14:55:34 +08:00
const memoPlacement = React . useMemo < Placement > ( ( ) = > {
2022-01-20 16:54:47 +08:00
if ( placement !== undefined ) {
return placement ;
}
2023-01-09 19:35:14 +08:00
return isRtl ? 'bottomRight' : 'bottomLeft' ;
2023-03-30 14:55:34 +08:00
} , [ placement , isRtl ] ) ;
2022-01-20 16:54:47 +08:00
2023-04-07 10:17:00 +08:00
const mergedBuiltinPlacements = useBuiltinPlacements ( builtinPlacements , popupOverflow ) ;
2023-04-04 13:43:53 +08:00
2023-08-02 14:21:11 +08:00
const mergedAllowClear = allowClear === true ? { clearIcon } : allowClear ;
2021-08-31 15:51:02 +08:00
// ==================== Render =====================
2022-03-09 00:29:00 +08:00
const renderNode = (
2021-08-31 15:51:02 +08:00
< RcCascader
prefixCls = { prefixCls }
className = { classNames (
! customizePrefixCls && cascaderPrefixCls ,
{
[ ` ${ prefixCls } -lg ` ] : mergedSize === 'large' ,
[ ` ${ prefixCls } -sm ` ] : mergedSize === 'small' ,
[ ` ${ prefixCls } -rtl ` ] : isRtl ,
[ ` ${ prefixCls } -borderless ` ] : ! bordered ,
2022-03-24 21:54:20 +08:00
[ ` ${ prefixCls } -in-form-item ` ] : isFormItemInput ,
2021-08-31 15:51:02 +08:00
} ,
2022-02-17 15:08:13 +08:00
getStatusClassNames ( prefixCls , mergedStatus , hasFeedback ) ,
2022-10-18 16:23:10 +08:00
compactItemClassnames ,
2023-06-27 17:13:27 +08:00
cascader ? . className ,
2021-08-31 15:51:02 +08:00
className ,
2023-01-20 11:03:50 +08:00
rootClassName ,
2022-03-09 00:29:00 +08:00
hashId ,
2021-08-31 15:51:02 +08:00
) }
2022-04-29 20:48:10 +08:00
disabled = { mergedDisabled }
2023-06-27 17:13:27 +08:00
style = { { . . . cascader ? . style , . . . style } }
2021-08-31 15:51:02 +08:00
{ . . . ( restProps as any ) }
2023-04-04 13:43:53 +08:00
builtinPlacements = { mergedBuiltinPlacements }
2021-08-31 15:51:02 +08:00
direction = { mergedDirection }
2023-03-30 14:55:34 +08:00
placement = { memoPlacement }
2021-08-31 15:51:02 +08:00
notFoundContent = { mergedNotFoundContent }
2023-08-02 14:21:11 +08:00
allowClear = { mergedAllowClear }
2021-08-31 15:51:02 +08:00
showSearch = { mergedShowSearch }
expandIcon = { mergedExpandIcon }
2023-07-19 20:27:09 +08:00
suffixIcon = { suffixIcon }
2021-08-31 15:51:02 +08:00
removeIcon = { removeIcon }
loadingIcon = { loadingIcon }
checkable = { checkable }
dropdownClassName = { mergedDropdownClassName }
dropdownPrefixCls = { customizePrefixCls || cascaderPrefixCls }
choiceTransitionName = { getTransitionName ( rootPrefixCls , '' , choiceTransitionName ) }
2023-07-25 10:54:58 +08:00
transitionName = { getTransitionName ( rootPrefixCls , 'slide-up' , transitionName ) }
2021-09-13 15:59:57 +08:00
getPopupContainer = { getPopupContainer || getContextPopupContainer }
2021-08-31 15:51:02 +08:00
ref = { ref }
/ >
2020-01-03 13:38:16 +08:00
) ;
2022-03-09 00:29:00 +08:00
return wrapCascaderSSR ( wrapSelectSSR ( renderNode ) ) ;
2022-03-26 00:17:34 +08:00
} ) as unknown as ( < OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType > (
2021-12-21 21:38:11 +08:00
props : React.PropsWithChildren < CascaderProps < OptionType > > & { ref? : React.Ref < CascaderRef > } ,
2021-11-24 17:45:13 +08:00
) = > React . ReactElement ) & {
displayName : string ;
2022-03-26 00:17:34 +08:00
SHOW_PARENT : typeof SHOW_PARENT ;
SHOW_CHILD : typeof SHOW_CHILD ;
2022-07-07 20:05:19 +08:00
_InternalPanelDoNotUseOrYouWillBeFired : typeof PurePanel ;
2021-11-24 17:45:13 +08:00
} ;
2022-06-21 10:24:52 +08:00
if ( process . env . NODE_ENV !== 'production' ) {
Cascader . displayName = 'Cascader' ;
}
2022-07-07 20:05:19 +08:00
// We don't care debug panel
2023-06-07 21:59:21 +08:00
/* istanbul ignore next */
2022-07-07 20:05:19 +08:00
const PurePanel = genPurePanel ( Cascader ) ;
2022-03-26 00:17:34 +08:00
Cascader . SHOW_PARENT = SHOW_PARENT ;
Cascader . SHOW_CHILD = SHOW_CHILD ;
2022-07-07 20:05:19 +08:00
Cascader . _InternalPanelDoNotUseOrYouWillBeFired = PurePanel ;
2018-12-14 11:40:47 +08:00
export default Cascader ;