@@ -5,6 +5,8 @@ import CascaderContext from '../context';
5
5
import { SEARCH_MARK } from '../hooks/useSearchOptions' ;
6
6
import { isLeaf , toPathKey } from '../utils/commonUtil' ;
7
7
import Checkbox from './Checkbox' ;
8
+ import List from 'rc-virtual-list' ;
9
+ import type { ListRef } from 'rc-virtual-list' ;
8
10
9
11
export const FIX_LABEL = '__cascader_fix_label__' ;
10
12
@@ -41,6 +43,7 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
41
43
isSelectable,
42
44
disabled : propsDisabled ,
43
45
} : ColumnProps < OptionType > ) {
46
+ const ref = React . useRef < ListRef > ( null ) ;
44
47
const menuPrefixCls = `${ prefixCls } -menu` ;
45
48
const menuItemPrefixCls = `${ prefixCls } -menu-item` ;
46
49
@@ -52,6 +55,9 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
52
55
loadingIcon,
53
56
dropdownMenuColumnStyle,
54
57
optionRender,
58
+ virtual,
59
+ listItemHeight,
60
+ listHeight,
55
61
} = React . useContext ( CascaderContext ) ;
56
62
57
63
const hoverOpen = expandTrigger === 'hover' ;
@@ -101,117 +107,140 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
101
107
) ;
102
108
103
109
// ============================ 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 : typeof optionInfoList [ 0 ] ) => {
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 ( ) ;
145
183
}
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
+ } ;
146
228
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 )
215
244
) }
216
245
</ ul >
217
246
) ;
0 commit comments