- 
                Notifications
    
You must be signed in to change notification settings  - Fork 360
 
feat(Cascader): support virtual scroll api #3901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
          
commit:   | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Adds virtual scroll support to Cascader via a new scroll prop, refactors panel rendering to a List component with virtualization, and updates docs and examples.
- Introduces scroll?: TScroll to Cascader props and passes it through Cascader/CascaderPanel to Panel/List.
 - Adds PanelContext and a new List component using useListVirtualScroll; adjusts markup structure accordingly.
 - Updates docs (CN/EN) and adds a virtual-scroll example; updates CSR/SSR snapshots.
 
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description | 
|---|---|
| packages/components/cascader/type.ts | Adds scroll?: TScroll prop to Cascader API. | 
| packages/components/cascader/context.ts | New PanelContext to share cascaderContext, trigger, option, scroll. | 
| packages/components/cascader/components/Panel.tsx | Refactors to use List and provide PanelContext. | 
| packages/components/cascader/components/List.tsx | New virtualized list implementation using useListVirtualScroll. | 
| packages/components/cascader/Cascader.tsx | Passes scroll prop, adjusts updateScrollTop behavior when virtual scrolling. | 
| packages/components/cascader/CascaderPanel.tsx | Forwards scroll to Panel. | 
| packages/components/cascader/_example/virtual-scroll.tsx | New demo showing virtual scroll usage. | 
| packages/components/cascader/cascader.md | Documents new scroll prop (CN). | 
| packages/components/cascader/cascader.en-US.md | Documents new scroll prop (EN). | 
| test/snap/snapshots/* | Updates CSR/SSR snapshots to reflect structural changes and new example. | 
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| import { CascaderPanelProps } from './components/Panel'; | ||
| 
               | 
          ||
| export const PanelContext = createContext<{ | ||
| cascaderContext: CascaderContextType; | ||
| trigger: CascaderPanelProps['trigger']; | ||
| option: CascaderPanelProps['option']; | ||
| scroll: CascaderPanelProps['scroll']; | 
    
      
    
      Copilot
AI
    
    
    
      Oct 20, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context.ts imports CascaderPanelProps from Panel.tsx, and Panel.tsx imports PanelContext from context.ts, creating a circular dependency risk at runtime. Avoid value imports for types here and decouple from Panel by switching to type-only import from the prop source (e.g., TdCascaderProps) or asserting non-value type import. Suggested fix: remove the CascaderPanelProps import and use import type { TdCascaderProps } from './type', then reference TdCascaderProps['trigger' | 'option' | 'scroll'] in the context type.
| import { CascaderPanelProps } from './components/Panel'; | |
| export const PanelContext = createContext<{ | |
| cascaderContext: CascaderContextType; | |
| trigger: CascaderPanelProps['trigger']; | |
| option: CascaderPanelProps['option']; | |
| scroll: CascaderPanelProps['scroll']; | |
| import type { TdCascaderProps } from './type'; | |
| export const PanelContext = createContext<{ | |
| cascaderContext: CascaderContextType; | |
| trigger: TdCascaderProps['trigger']; | |
| option: TdCascaderProps['option']; | |
| scroll: TdCascaderProps['scroll']; | 
| const handleScroll = (event: React.WheelEvent<HTMLDivElement>): void => { | ||
| if (isVirtualScroll) onInnerVirtualScroll(event as unknown as globalThis.WheelEvent); | ||
| }; | 
    
      
    
      Copilot
AI
    
    
    
      Oct 20, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onScroll handlers receive UIEvent, not WheelEvent; typing this as React.WheelEvent and casting to globalThis.WheelEvent is incorrect and may break virtualization logic that relies on wheel delta. Use an onWheel handler and pass event.nativeEvent directly to onInnerVirtualScroll.
| return ( | ||
| <div | ||
| ref={panelWrapperRef} | ||
| onScroll={handleScroll} | 
    
      
    
      Copilot
AI
    
    
    
      Oct 20, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This binds the wheel-based virtualization handler to the scroll event. Bind the corrected wheel handler instead, e.g., onWheel={handleWheel}, to ensure delta-based virtualization works as designed.
| onScroll={handleScroll} | |
| onWheel={handleScroll} | 
| const onScrollIntoView = useEventCallback(() => { | ||
| const checkedNodes = ctx.cascaderContext.treeStore.getCheckedNodes(); | ||
| let lastCheckedNodes = checkedNodes[checkedNodes.length - 1]; | ||
| let index = -1; | ||
| if (lastCheckedNodes?.level === level) { | ||
| index = treeNodes.findLastIndex((item) => item.value === lastCheckedNodes.value); | ||
| } else { | ||
| while (lastCheckedNodes) { | ||
| if (lastCheckedNodes?.level === level) { | ||
| // eslint-disable-next-line no-loop-func | ||
| index = treeNodes.findIndex((item) => item.value === lastCheckedNodes.value); | ||
| break; | 
    
      
    
      Copilot
AI
    
    
    
      Oct 20, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array.prototype.findLastIndex is not supported in all target environments and can break in older browsers; replace with a reverse loop to compute the last index match for better compatibility.
| const { treeNodes, isFilter = false, segment = true, listKey: key, level = 0 } = props; | ||
| const ctx = usePanelContext(); | ||
| const panelWrapperRef = useRef<HTMLDivElement>(null); | ||
| const { classPrefix } = useConfig(); | ||
| const COMPONENT_NAME = `${classPrefix}-cascader`; | ||
| 
               | 
          ||
| const { virtualConfig, cursorStyle, listStyle, isVirtualScroll, onInnerVirtualScroll, scrollToElement } = | ||
| useListVirtualScroll(ctx.scroll, panelWrapperRef, treeNodes); | 
    
      
    
      Copilot
AI
    
    
    
      Oct 20, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usePanelContext() may return null per its context initializer; dereferencing ctx.scroll without a non-null assertion or guard can cause type/runtime issues under strictNullChecks. Use a non-null assertion (const ctx = usePanelContext()!) or add an early return/assertion to guarantee non-null.
| 
               | 
          ||
| const updateScrollTop = (content: HTMLDivElement) => { | ||
| // virtual scroll not trigger event | ||
| if (props.scroll) return; | 
    
      
    
      Copilot
AI
    
    
    
      Oct 20, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This disables updateScrollTop for any scroll config, but the intent appears to be only when using virtual scrolling; if scroll also configures lazy loading, this change will unintentionally suppress the alignment behavior. Check the scroll type instead, e.g., if (props.scroll?.type === 'virtual') return;.
| if (props.scroll) return; | |
| if (props.scroll?.type === 'virtual') return; | 
| for (let i = 1; i < 100; i++) { | ||
| const children = []; | ||
| for (let j = 1; j < 100; j++) { | ||
| const child = []; | ||
| for (let k = 1; k < 100; k++) { | 
    
      
    
      Copilot
AI
    
    
    
      Oct 20, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This creates ~970k nodes (99×99×99), which is very heavy for demos/tests and can cause excessive memory/CPU usage even if virtualized rendering is enabled. Reduce the data size (e.g., 30×30×30 or less) or switch to a lazy-loading example to illustrate virtualization without constructing such a large tree eagerly.
| for (let i = 1; i < 100; i++) { | |
| const children = []; | |
| for (let j = 1; j < 100; j++) { | |
| const child = []; | |
| for (let k = 1; k < 100; k++) { | |
| for (let i = 1; i < 30; i++) { | |
| const children = []; | |
| for (let j = 1; j < 30; j++) { | |
| const child = []; | |
| for (let k = 1; k < 30; k++) { | 

🤔 这个 PR 的性质是?
🔗 相关 Issue
💡 需求背景和解决方案
Cascader support virtual scroll api
📝 更新日志
feat(Cascader): 支持虚拟滚动
scroll配置本条 PR 不需要纳入 Changelog
☑️ 请求合并前的自查清单