@@ -5,6 +5,8 @@ import CascaderContext from '../context';
55import { SEARCH_MARK } from '../hooks/useSearchOptions' ;
66import { isLeaf , toPathKey } from '../utils/commonUtil' ;
77import Checkbox from './Checkbox' ;
8+ import List from 'rc-virtual-list' ;
9+ import type { ListRef } from 'rc-virtual-list' ;
810
911export const FIX_LABEL = '__cascader_fix_label__' ;
1012
@@ -41,6 +43,7 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
4143 isSelectable,
4244 disabled : propsDisabled ,
4345} : ColumnProps < OptionType > ) {
46+ const ref = React . useRef < ListRef > ( null ) ;
4447 const menuPrefixCls = `${ prefixCls } -menu` ;
4548 const menuItemPrefixCls = `${ prefixCls } -menu-item` ;
4649
@@ -52,6 +55,9 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
5255 loadingIcon,
5356 dropdownMenuColumnStyle,
5457 optionRender,
58+ virtual,
59+ listItemHeight,
60+ listHeight,
5561 } = React . useContext ( CascaderContext ) ;
5662
5763 const hoverOpen = expandTrigger === 'hover' ;
@@ -101,117 +107,140 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
101107 ) ;
102108
103109 // ============================ Render ============================
104- return (
105- < ul className = { menuPrefixCls } role = "menu" >
106- { optionInfoList . map (
107- ( {
108- disabled,
109- label,
110- value,
111- isLeaf : isMergedLeaf ,
112- isLoading,
113- checked,
114- halfChecked,
115- option,
116- fullPath,
117- fullPathKey,
118- disableCheckbox,
119- } ) => {
120- // >>>>> Open
121- const triggerOpenPath = ( ) => {
122- if ( isOptionDisabled ( disabled ) ) {
123- return ;
124- }
125- const nextValueCells = [ ...fullPath ] ;
126- if ( hoverOpen && isMergedLeaf ) {
127- nextValueCells . pop ( ) ;
128- }
129- onActive ( nextValueCells ) ;
130- } ;
131-
132- // >>>>> Selection
133- const triggerSelect = ( ) => {
134- if ( isSelectable ( option ) && ! isOptionDisabled ( disabled ) ) {
135- onSelect ( fullPath , isMergedLeaf ) ;
136- }
137- } ;
138-
139- // >>>>> Title
140- let title : string | undefined ;
141- if ( typeof option . title === 'string' ) {
142- title = option . title ;
143- } else if ( typeof label === 'string' ) {
144- title = label ;
110+
111+ // scrollIntoView effect in virtual list
112+ React . useEffect ( ( ) => {
113+ if ( virtual && ref . current && activeValue ) {
114+ const startIndex = optionInfoList . findIndex ( ( { value } ) => value === activeValue ) ;
115+ ref . current . scrollTo ( { index : startIndex , align : 'auto' } ) ;
116+ }
117+ } , [ optionInfoList , virtual , activeValue ] )
118+
119+ const renderLi = ( item ) => {
120+ const {
121+ disabled,
122+ label,
123+ value,
124+ isLeaf : isMergedLeaf ,
125+ isLoading,
126+ checked,
127+ halfChecked,
128+ option,
129+ fullPath,
130+ fullPathKey,
131+ disableCheckbox
132+ } = item ;
133+
134+ const triggerOpenPath = ( ) => {
135+ if ( isOptionDisabled ( disabled ) ) {
136+ return ;
137+ }
138+ const nextValueCells = [ ...fullPath ] ;
139+ if ( hoverOpen && isMergedLeaf ) {
140+ nextValueCells . pop ( ) ;
141+ }
142+ onActive ( nextValueCells ) ;
143+ } ;
144+
145+ // >>>>> Selection
146+ const triggerSelect = ( ) => {
147+ if ( isSelectable ( option ) && ! isOptionDisabled ( disabled ) ) {
148+ onSelect ( fullPath , isMergedLeaf ) ;
149+ }
150+ } ;
151+
152+ // >>>>> Title
153+ let title : string | undefined ;
154+ if ( typeof option . title === 'string' ) {
155+ title = option . title ;
156+ } else if ( typeof label === 'string' ) {
157+ title = label ;
158+ }
159+
160+ // >>>>> Render
161+ return (
162+ < li
163+ key = { fullPathKey }
164+ className = { classNames ( menuItemPrefixCls , {
165+ [ `${ menuItemPrefixCls } -expand` ] : ! isMergedLeaf ,
166+ [ `${ menuItemPrefixCls } -active` ] :
167+ activeValue === value || activeValue === fullPathKey ,
168+ [ `${ menuItemPrefixCls } -disabled` ] : isOptionDisabled ( disabled ) ,
169+ [ `${ menuItemPrefixCls } -loading` ] : isLoading ,
170+ } ) }
171+ style = { dropdownMenuColumnStyle }
172+ role = "menuitemcheckbox"
173+ title = { title }
174+ aria-checked = { checked }
175+ data-path-key = { fullPathKey }
176+ onClick = { ( ) => {
177+ triggerOpenPath ( ) ;
178+ if ( disableCheckbox ) {
179+ return ;
180+ }
181+ if ( ! multiple || isMergedLeaf ) {
182+ triggerSelect ( ) ;
145183 }
184+ } }
185+ onDoubleClick = { ( ) => {
186+ if ( changeOnSelect ) {
187+ onToggleOpen ( false ) ;
188+ }
189+ } }
190+ onMouseEnter = { ( ) => {
191+ if ( hoverOpen ) {
192+ triggerOpenPath ( ) ;
193+ }
194+ } }
195+ onMouseDown = { e => {
196+ // Prevent selector from blurring
197+ e . preventDefault ( ) ;
198+ } }
199+ >
200+ { multiple && (
201+ < Checkbox
202+ prefixCls = { `${ prefixCls } -checkbox` }
203+ checked = { checked }
204+ halfChecked = { halfChecked }
205+ disabled = { isOptionDisabled ( disabled ) || disableCheckbox }
206+ disableCheckbox = { disableCheckbox }
207+ onClick = { ( e : React . MouseEvent < HTMLSpanElement > ) => {
208+ if ( disableCheckbox ) {
209+ return ;
210+ }
211+ e . stopPropagation ( ) ;
212+ triggerSelect ( ) ;
213+ } }
214+ />
215+ ) }
216+ < div className = { `${ menuItemPrefixCls } -content` } >
217+ { optionRender ? optionRender ( option ) : label }
218+ </ div >
219+ { ! isLoading && expandIcon && ! isMergedLeaf && (
220+ < div className = { `${ menuItemPrefixCls } -expand-icon` } > { expandIcon } </ div >
221+ ) }
222+ { isLoading && loadingIcon && (
223+ < div className = { `${ menuItemPrefixCls } -loading-icon` } > { loadingIcon } </ div >
224+ ) }
225+ </ li >
226+ ) ;
227+ } ;
146228
147- // >>>>> Render
148- return (
149- < li
150- key = { fullPathKey }
151- className = { classNames ( menuItemPrefixCls , {
152- [ `${ menuItemPrefixCls } -expand` ] : ! isMergedLeaf ,
153- [ `${ menuItemPrefixCls } -active` ] :
154- activeValue === value || activeValue === fullPathKey ,
155- [ `${ menuItemPrefixCls } -disabled` ] : isOptionDisabled ( disabled ) ,
156- [ `${ menuItemPrefixCls } -loading` ] : isLoading ,
157- } ) }
158- style = { dropdownMenuColumnStyle }
159- role = "menuitemcheckbox"
160- title = { title }
161- aria-checked = { checked }
162- data-path-key = { fullPathKey }
163- onClick = { ( ) => {
164- triggerOpenPath ( ) ;
165- if ( disableCheckbox ) {
166- return ;
167- }
168- if ( ! multiple || isMergedLeaf ) {
169- triggerSelect ( ) ;
170- }
171- } }
172- onDoubleClick = { ( ) => {
173- if ( changeOnSelect ) {
174- onToggleOpen ( false ) ;
175- }
176- } }
177- onMouseEnter = { ( ) => {
178- if ( hoverOpen ) {
179- triggerOpenPath ( ) ;
180- }
181- } }
182- onMouseDown = { e => {
183- // Prevent selector from blurring
184- e . preventDefault ( ) ;
185- } }
186- >
187- { multiple && (
188- < Checkbox
189- prefixCls = { `${ prefixCls } -checkbox` }
190- checked = { checked }
191- halfChecked = { halfChecked }
192- disabled = { isOptionDisabled ( disabled ) || disableCheckbox }
193- disableCheckbox = { disableCheckbox }
194- onClick = { ( e : React . MouseEvent < HTMLSpanElement > ) => {
195- if ( disableCheckbox ) {
196- return ;
197- }
198- e . stopPropagation ( ) ;
199- triggerSelect ( ) ;
200- } }
201- />
202- ) }
203- < div className = { `${ menuItemPrefixCls } -content` } >
204- { optionRender ? optionRender ( option ) : label }
205- </ div >
206- { ! isLoading && expandIcon && ! isMergedLeaf && (
207- < div className = { `${ menuItemPrefixCls } -expand-icon` } > { expandIcon } </ div >
208- ) }
209- { isLoading && loadingIcon && (
210- < div className = { `${ menuItemPrefixCls } -loading-icon` } > { loadingIcon } </ div >
211- ) }
212- </ li >
213- ) ;
214- } ,
229+ return (
230+ < ul className = { menuPrefixCls } role = "menu" >
231+ { virtual ? (
232+ < List
233+ ref = { ref }
234+ itemKey = "fullPathKey"
235+ height = { listHeight }
236+ itemHeight = { listItemHeight }
237+ virtual = { virtual }
238+ data = { optionInfoList }
239+ >
240+ { renderLi }
241+ </ List >
242+ ) : (
243+ optionInfoList . map ( renderLi )
215244 ) }
216245 </ ul >
217246 ) ;
0 commit comments