import classnames from 'classnames'; import { Component } from 'react'; import Popover from '../../popover'; import Icon from '../../icon'; import Checkbox from '../../checkbox'; import { getNodeChildren, getNodeKey } from '../node-fns'; import { CascaderMenuClickHandler, ICascaderItem, CascaderValue, CascaderMenuHoverHandler, ICascaderBaseProps, CascaderItemSelectionState, } from '../types'; import InfiniteScroller from '../../infinite-scroller'; import { II18nLocaleCascader } from '../../i18n'; import InlineLoading from '../../loading/InlineLoading'; const withPopover = Popover.withPopover; export interface IMenuContentCommonProps { // injected by withPopover popover: Popover; options: ICascaderItem[]; expandTrigger?: 'click' | 'hover'; i18n: II18nLocaleCascader; scrollable: boolean; scrollLoad: (parent: ICascaderItem | null) => Promise; loadChildrenOnScroll: boolean; onOptionToggle: (node: ICascaderItem, checked: boolean) => void; onOptionHover: CascaderMenuHoverHandler; onOptionClick: CascaderMenuClickHandler; className?: string; loading: string[]; // 节点选中状态 selectionMap: Map; renderItemContent?: ICascaderBaseProps['renderItemContent']; getItemTooltip?: ICascaderBaseProps['getItemTooltip']; renderList?: ICascaderBaseProps['renderList']; multipleType?: 'normal' | 'checkbox'; } export interface IMenuContentMultipleProps extends IMenuContentCommonProps { multiple: true; value: Array; } export interface IMenuContentSingleProps extends IMenuContentCommonProps { multiple: false; value: CascaderValue[]; } export type IMenuContentProps = | IMenuContentMultipleProps | IMenuContentSingleProps; function defaultRenderItemContent(node: ICascaderItem): React.ReactNode { return ( {node.label} ); } function defaultGetItemTooltip(node: ICascaderItem): string { return node.label; } class MenuContent extends Component { static defaultProps = { renderItemContent: defaultRenderItemContent, getItemTooltip: defaultGetItemTooltip, }; render() { return (
{this.renderPanels()}
); } closePopup = () => this.props.popover?.close(); getMenuItemIcon(node: ICascaderItem, isActive: boolean, showCheck: boolean) { const { loading } = this.props; if (node.loadChildrenOnExpand) { const nodeKey = getNodeKey(node); if (loading.indexOf(nodeKey) !== -1 && isActive) { return ; } } // 有 children 或者需要加载 children 的时候说明非叶子节点 const hasChildren = node.children && node.children.length > 0; if (hasChildren || node.loadChildrenOnExpand) { return ; } if (!hasChildren && isActive && showCheck) { return ; } return null; } handleClickOption( node: ICascaderItem, closePop: () => void, checkState: CascaderItemSelectionState | undefined ) { const { onOptionClick, multiple, multipleType, onOptionToggle } = this.props; onOptionClick(node, closePop); if (multiple && multipleType === 'normal') { const isLeafOption = node.children && node.children.length === 0; const checked = checkState === 'on'; isLeafOption && onOptionToggle(node, !checked); } } renderCascaderItems( nodes: ICascaderItem[], level: number, parent: ICascaderItem | null ) { const { i18n } = this.props; if (!nodes || nodes?.length === 0) { return (
{i18n.empty}
); } const { value, onOptionHover, expandTrigger, scrollLoad, loadChildrenOnScroll, scrollable, multiple, multipleType, selectionMap, renderItemContent, getItemTooltip, renderList, } = this.props; // `style` can be used to position when used with a custom virtual list renderer const renderItem = (node: ICascaderItem, style?: React.CSSProperties) => { let checkState: CascaderItemSelectionState | undefined; if (multiple) { checkState = selectionMap.get(getNodeKey(node)); } const isActive = multiple ? checkState === 'on' || checkState === 'partial' : node.value === value[level - 1]; const cascaderItemCls = classnames('zent-cascader-v2__menu-item', { 'zent-cascader-v2__menu-item--active': isActive, 'zent-cascader-v2__menu-item--disabled': node.disabled, 'zent-cascader-v2__menu-item--multiple': multiple, 'zent-cascader-v2__menu-item--multiple--checkbox': multiple && multipleType === 'checkbox', 'zent-cascader-v2__menu-item--multiple--normal': multiple && multipleType === 'normal', 'zent-cascader-v2__menu-item--leaf': node.children.length === 0 && !node.loadChildrenOnExpand, }); const showCheck = multiple && multipleType !== 'checkbox'; return (
this.handleClickOption(node, this.closePopup, checkState) } onMouseEnter={ node.disabled || expandTrigger !== 'hover' ? undefined : () => onOptionHover(node) } style={style} > {multiple && multipleType === 'checkbox' && ( this.props.onOptionToggle(node, e.target.checked)} checked={checkState === 'on'} indeterminate={checkState === 'partial'} disabled={node.disabled} /> )} {renderItemContent(node)} {this.getMenuItemIcon(node, isActive, showCheck)}
); }; const key = `menu-${value.slice(0, level - 1).join('-')}`; // bail out if custom list renderer is provided if (typeof renderList === 'function') { return (
{renderList(nodes, renderItem)}
); } const cascaderItems = nodes.map(node => renderItem(node)); const hasMore = parent === null ? loadChildrenOnScroll : parent.loadChildrenOnScroll; return (
{scrollable && hasMore ? ( } loadMore={() => scrollLoad(parent)} skipLoadOnMount > {cascaderItems} ) : ( cascaderItems )}
); } renderPanels() { const PanelEls = []; const { value } = this.props; let { options } = this.props; let level = 1; PanelEls.push(this.renderCascaderItems(options, level, null)); if (value?.length > 0 && options?.length > 0) { for (let i = 0; i < value.length; i++) { level++; // 记录滚动加载的父元素 const parent = options.find(it => it.value === value[i]); options = getNodeChildren(options, value[i]); if (options?.length > 0) { PanelEls.push(this.renderCascaderItems(options, level, parent)); } } } return PanelEls; } } export default withPopover(MenuContent);