import React, { useEffect, useMemo, useRef, useState } from "react"; import { List } from "../../list"; import { EmptyTip } from "../../tips"; import { noop } from "../../_util/noop"; import { useIsomorphicLayoutEffect } from "../../_util/use-isomorphic-layout-effect"; import { getOptions } from "../CascaderBox"; import { CascaderLoading } from "../CascaderLoading"; import { CascaderBoxProps, CascaderData } from "../CascaderProps"; export function CascaderMenuLists({ data, curValue, onLoad, onClose, changeOnSelect, onSelect, expandTrigger, scheduleUpdate, tips, bottomTips, onScrollBottom = noop, }: CascaderBoxProps) { const enterTimerRef = useRef(null); // 面板可用值(动态加载时预先传输 value 场景,此时值无法和面板数据对应) const availableValue = useMemo(() => { const lists = [data]; const value: string[] = []; for (let i = 0; i < curValue?.length; ++i) { const option = lists[i]?.options?.find( option => option.value === curValue[i] ); if (option) { value.push(curValue[i]); } if (option && option.child) { lists.push(option.child as CascaderData); } else { break; } } return value; }, [curValue, data]); // 内部状态 const [internalValue, setInternalValue] = useState(availableValue || []); useIsomorphicLayoutEffect(() => { setInternalValue(availableValue || []); }, [curValue]); const lists = useMemo(() => { const lists = [data]; for (let i = 0; i < internalValue.length; ++i) { const option = lists[i]?.options?.find( option => option.value === internalValue[i] ); if (option && option.child) { lists.push(option.child as CascaderData); } else { break; } } return lists; }, [data, internalValue]); useEffect(() => { if (scheduleUpdate) { scheduleUpdate(); } }, [lists, scheduleUpdate]); function handleSelect(index, value, { event, hasChild }) { const newValue = [...internalValue.slice(0, index), value]; if (changeOnSelect) { onSelect(newValue, { event, options: getOptions(data, newValue) }); } else { setInternalValue(newValue); if (!hasChild) { onSelect(newValue, { event, options: getOptions(data, newValue) }); } } if (!hasChild) { onClose(); } } return ( <> {lists.map(({ options }, index) => ( onScrollBottom(internalValue.slice(0, index))} > {options ? ( <> {tips && ( {tips(internalValue.slice(0, index))} )} {options.map(({ value, label, child, disabled }) => { const selected = internalValue[index] === value; const triggerProps = expandTrigger === "hover" && child ? { onMouseEnter: (event: React.MouseEvent) => { enterTimerRef.current = setTimeout(() => { handleSelect(index, value, { event, hasChild: true, }); }, 150); }, onMouseLeave: () => { clearTimeout(enterTimerRef.current); }, } : { onClick: (event: React.MouseEvent) => { handleSelect(index, value, { event, hasChild: Boolean(child), }); }, }; const itemProps = { ...triggerProps, key: [...internalValue.slice(0, index), value].join("/"), disabled, selected, }; if (child) { return ; } return {label || value}; })} {options.length === 0 && ( )} {bottomTips && ( {bottomTips(internalValue.slice(0, index))} )} ) : ( onLoad(internalValue, getOptions(data, internalValue)) } /> )} ))} ); } CascaderMenuLists.displayName = "CascaderMenuLists";