diff --git a/src/Menu.tsx b/src/Menu.tsx index aa50e63b..229f82e3 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -24,6 +24,7 @@ import type { ItemType, MenuClickEventHandler, MenuInfo, + MenuKey, MenuMode, MenuRef, RenderIconType, @@ -76,18 +77,18 @@ export interface MenuProps // Open control defaultOpenKeys?: string[]; - openKeys?: string[]; + openKeys?: MenuKey[]; // Active control - activeKey?: string; + activeKey?: MenuKey; defaultActiveFirst?: boolean; // Selection selectable?: boolean; multiple?: boolean; - defaultSelectedKeys?: string[]; - selectedKeys?: string[]; + defaultSelectedKeys?: MenuKey[]; + selectedKeys?: MenuKey[]; onSelect?: SelectEventHandler; onDeselect?: SelectEventHandler; @@ -120,7 +121,7 @@ export interface MenuProps // >>>>> Events onClick?: MenuClickEventHandler; - onOpenChange?: (openKeys: string[]) => void; + onOpenChange?: (openKeys: MenuKey[]) => void; // >>>>> Internal /*** @@ -260,7 +261,7 @@ const Menu = React.forwardRef((props, ref) => { // React 18 will merge mouse event which means we open key will not sync // ref: https://github.com/ant-design/ant-design/issues/38818 - const triggerOpenKeys = (keys: string[], forceFlush = false) => { + const triggerOpenKeys = (keys: MenuKey[], forceFlush = false) => { function doUpdate() { setMergedOpenKeys(keys); onOpenChange?.(keys); @@ -439,7 +440,7 @@ const Menu = React.forwardRef((props, ref) => { // Insert or Remove const { key: targetKey } = info; const exist = mergedSelectKeys.includes(targetKey); - let newSelectKeys: string[]; + let newSelectKeys: MenuKey[]; if (multiple) { if (exist) { @@ -481,7 +482,7 @@ const Menu = React.forwardRef((props, ref) => { triggerSelection(info); }); - const onInternalOpenChange = useMemoCallback((key: string, open: boolean) => { + const onInternalOpenChange = useMemoCallback((key: MenuKey, open: boolean) => { let newOpenKeys = mergedOpenKeys.filter(k => k !== key); if (open) { @@ -498,7 +499,7 @@ const Menu = React.forwardRef((props, ref) => { }); // ==================== Accessibility ===================== - const triggerAccessibilityOpen = (key: string, open?: boolean) => { + const triggerAccessibilityOpen = (key: MenuKey, open?: boolean) => { const nextOpen = open ?? !mergedOpenKeys.includes(key); onInternalOpenChange(key, nextOpen); diff --git a/src/MenuItem.tsx b/src/MenuItem.tsx index 1be29261..d4e0bef4 100644 --- a/src/MenuItem.tsx +++ b/src/MenuItem.tsx @@ -12,7 +12,7 @@ import PrivateContext from './context/PrivateContext'; import useActive from './hooks/useActive'; import useDirectionStyle from './hooks/useDirectionStyle'; import Icon from './Icon'; -import type { MenuInfo, MenuItemType } from './interface'; +import type { MenuInfo, MenuItemType, MenuKey } from './interface'; import { warnItemProp } from './utils/warnUtil'; export interface MenuItemProps @@ -24,7 +24,7 @@ export interface MenuItemProps children?: React.ReactNode; /** @private Internal filled key. Do not set it directly */ - eventKey?: string; + eventKey?: MenuKey; /** @private Do not use. Private warning empty usage */ warnKey?: boolean; diff --git a/src/MenuItemGroup.tsx b/src/MenuItemGroup.tsx index f812c0b6..e06de09d 100644 --- a/src/MenuItemGroup.tsx +++ b/src/MenuItemGroup.tsx @@ -3,7 +3,7 @@ import omit from 'rc-util/lib/omit'; import * as React from 'react'; import { MenuContext } from './context/MenuContext'; import { useFullPath, useMeasure } from './context/PathContext'; -import type { MenuItemGroupType } from './interface'; +import type { MenuItemGroupType, MenuKey } from './interface'; import { parseChildren } from './utils/commonUtil'; export interface MenuItemGroupProps @@ -13,7 +13,7 @@ export interface MenuItemGroupProps children?: React.ReactNode; /** @private Internal filled key. Do not set it directly */ - eventKey?: string; + eventKey?: MenuKey; /** @private Do not use. Private warning empty usage */ warnKey?: boolean; diff --git a/src/SubMenu/InlineSubMenuList.tsx b/src/SubMenu/InlineSubMenuList.tsx index b5f48b8d..853e7b73 100644 --- a/src/SubMenu/InlineSubMenuList.tsx +++ b/src/SubMenu/InlineSubMenuList.tsx @@ -3,12 +3,12 @@ import CSSMotion from 'rc-motion'; import { getMotion } from '../utils/motionUtil'; import MenuContextProvider, { MenuContext } from '../context/MenuContext'; import SubMenuList from './SubMenuList'; -import type { MenuMode } from '../interface'; +import type { MenuKey, MenuMode } from '../interface'; export interface InlineSubMenuListProps { id?: string; open: boolean; - keyPath: string[]; + keyPath: MenuKey[]; children: React.ReactNode; } diff --git a/src/SubMenu/index.tsx b/src/SubMenu/index.tsx index 06183bd2..b9b3ad11 100644 --- a/src/SubMenu/index.tsx +++ b/src/SubMenu/index.tsx @@ -4,7 +4,7 @@ import Overflow from 'rc-overflow'; import warning from 'rc-util/lib/warning'; import SubMenuList from './SubMenuList'; import { parseChildren } from '../utils/commonUtil'; -import type { MenuInfo, SubMenuType } from '../interface'; +import type { MenuInfo, MenuKey, SubMenuType } from '../interface'; import MenuContextProvider, { MenuContext } from '../context/MenuContext'; import useMemoCallback from '../hooks/useMemoCallback'; import PopupTrigger from './PopupTrigger'; @@ -32,7 +32,7 @@ export interface SubMenuProps internalPopupClose?: boolean; /** @private Internal filled key. Do not set it directly */ - eventKey?: string; + eventKey?: MenuKey; /** @private Do not use. Private warning empty usage */ warnKey?: boolean; diff --git a/src/context/IdContext.ts b/src/context/IdContext.ts index ec16a790..563a3187 100644 --- a/src/context/IdContext.ts +++ b/src/context/IdContext.ts @@ -1,8 +1,9 @@ +import { MenuKey } from '@/interface'; import * as React from 'react'; export const IdContext = React.createContext(null); -export function getMenuId(uuid: string, eventKey: string) { +export function getMenuId(uuid: string, eventKey: MenuKey) { if (uuid === undefined) { return null; } @@ -12,7 +13,7 @@ export function getMenuId(uuid: string, eventKey: string) { /** * Get `data-menu-id` */ -export function useMenuId(eventKey: string) { +export function useMenuId(eventKey: MenuKey) { const id = React.useContext(IdContext); return getMenuId(id, eventKey); } diff --git a/src/context/MenuContext.tsx b/src/context/MenuContext.tsx index c34c4bdf..cbcdf72c 100644 --- a/src/context/MenuContext.tsx +++ b/src/context/MenuContext.tsx @@ -5,6 +5,7 @@ import isEqual from 'rc-util/lib/isEqual'; import type { BuiltinPlacements, MenuClickEventHandler, + MenuKey, MenuMode, RenderIconType, TriggerSubMenuAction, @@ -13,7 +14,7 @@ import type { export interface MenuContextProps { prefixCls: string; rootClassName?: string; - openKeys: string[]; + openKeys: MenuKey[]; rtl?: boolean; // Mode @@ -25,12 +26,12 @@ export interface MenuContextProps { overflowDisabled?: boolean; // Active - activeKey: string; - onActive: (key: string) => void; - onInactive: (key: string) => void; + activeKey: MenuKey; + onActive: (key: MenuKey) => void; + onInactive: (key: MenuKey) => void; // Selection - selectedKeys: string[]; + selectedKeys: MenuKey[]; // Level inlineIndent: number; @@ -52,7 +53,7 @@ export interface MenuContextProps { // Function onItemClick: MenuClickEventHandler; - onOpenChange: (key: string, open: boolean) => void; + onOpenChange: (key: MenuKey, open: boolean) => void; getPopupContainer: (node: HTMLElement) => HTMLElement; } diff --git a/src/context/PathContext.tsx b/src/context/PathContext.tsx index 6ebadbeb..8ffafcc1 100644 --- a/src/context/PathContext.tsx +++ b/src/context/PathContext.tsx @@ -1,11 +1,12 @@ +import { MenuKey } from '@/interface'; import * as React from 'react'; const EmptyList: string[] = []; // ========================= Path Register ========================= export interface PathRegisterContextProps { - registerPath: (key: string, keyPath: string[]) => void; - unregisterPath: (key: string, keyPath: string[]) => void; + registerPath: (key: MenuKey, keyPath: MenuKey[]) => void; + unregisterPath: (key: MenuKey, keyPath: MenuKey[]) => void; } export const PathRegisterContext = React.createContext( @@ -17,9 +18,9 @@ export function useMeasure() { } // ========================= Path Tracker ========================== -export const PathTrackerContext = React.createContext(EmptyList); +export const PathTrackerContext = React.createContext(EmptyList); -export function useFullPath(eventKey?: string) { +export function useFullPath(eventKey?: MenuKey) { const parentKeyPath = React.useContext(PathTrackerContext); return React.useMemo( () => @@ -30,7 +31,7 @@ export function useFullPath(eventKey?: string) { // =========================== Path User =========================== export interface PathUserContextProps { - isSubPathKey: (pathKeys: string[], eventKey: string) => boolean; + isSubPathKey: (pathKeys: MenuKey[], eventKey: MenuKey) => boolean; } export const PathUserContext = React.createContext(null); diff --git a/src/hooks/useAccessibility.ts b/src/hooks/useAccessibility.ts index 5cd58e20..669e0f76 100644 --- a/src/hooks/useAccessibility.ts +++ b/src/hooks/useAccessibility.ts @@ -3,7 +3,7 @@ import KeyCode from 'rc-util/lib/KeyCode'; import raf from 'rc-util/lib/raf'; import * as React from 'react'; import { getMenuId } from '../context/IdContext'; -import type { MenuMode } from '../interface'; +import type { MenuKey, MenuMode } from '../interface'; // destruct to reduce minify size const { LEFT, RIGHT, UP, DOWN, ENTER, ESC, HOME, END } = KeyCode; @@ -181,10 +181,10 @@ function getNextFocusElement( return sameLevelFocusableMenuElementList[focusIndex]; } -export const refreshElements = (keys: string[], id: string) => { +export const refreshElements = (keys: MenuKey[], id: string) => { const elements = new Set(); - const key2element = new Map(); - const element2key = new Map(); + const key2element = new Map(); + const element2key = new Map(); keys.forEach(key => { const element = document.querySelector( @@ -203,22 +203,22 @@ export const refreshElements = (keys: string[], id: string) => { export function useAccessibility( mode: MenuMode, - activeKey: string, + activeKey: MenuKey, isRtl: boolean, id: string, containerRef: React.RefObject, - getKeys: () => string[], - getKeyPath: (key: string, includeOverflow?: boolean) => string[], + getKeys: () => MenuKey[], + getKeyPath: (key: MenuKey, includeOverflow?: boolean) => string[], - triggerActiveKey: (key: string) => void, - triggerAccessibilityOpen: (key: string, open?: boolean) => void, + triggerActiveKey: (key: MenuKey) => void, + triggerAccessibilityOpen: (key: MenuKey, open?: boolean) => void, originOnKeyDown?: React.KeyboardEventHandler, ): React.KeyboardEventHandler { const rafRef = React.useRef(); - const activeRef = React.useRef(); + const activeRef = React.useRef(); activeRef.current = activeKey; const cleanRaf = () => { diff --git a/src/hooks/useActive.ts b/src/hooks/useActive.ts index dd0d6005..9cc1089f 100644 --- a/src/hooks/useActive.ts +++ b/src/hooks/useActive.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { MenuContext } from '../context/MenuContext'; -import type { MenuHoverEventHandler } from '../interface'; +import type { MenuHoverEventHandler, MenuKey } from '../interface'; interface ActiveObj { active: boolean; @@ -9,7 +9,7 @@ interface ActiveObj { } export default function useActive( - eventKey: string, + eventKey: MenuKey, disabled: boolean, onMouseEnter?: MenuHoverEventHandler, onMouseLeave?: MenuHoverEventHandler, diff --git a/src/hooks/useKeyRecords.ts b/src/hooks/useKeyRecords.ts index db27d1ab..f2110b73 100644 --- a/src/hooks/useKeyRecords.ts +++ b/src/hooks/useKeyRecords.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { useRef, useCallback } from 'react'; import warning from 'rc-util/lib/warning'; import { nextSlice } from '../utils/timeUtil'; +import { MenuKey } from '@/interface'; const PATH_SPLIT = '__RC_UTIL_PATH_SPLIT__'; @@ -12,8 +13,8 @@ export const OVERFLOW_KEY = 'rc-menu-more'; export default function useKeyRecords() { const [, internalForceUpdate] = React.useState({}); - const key2pathRef = useRef(new Map()); - const path2keyRef = useRef(new Map()); + const key2pathRef = useRef(new Map()); + const path2keyRef = useRef(new Map()); const [overflowKeys, setOverflowKeys] = React.useState([]); const updateRef = useRef(0); const destroyRef = useRef(false); @@ -95,12 +96,12 @@ export default function useKeyRecords() { /** * Find current key related child path keys */ - const getSubPathKeys = useCallback((key: string): Set => { + const getSubPathKeys = useCallback((key: MenuKey): Set => { const connectedPath = `${key2pathRef.current.get(key)}${PATH_SPLIT}`; const pathKeys = new Set(); [...path2keyRef.current.keys()].forEach(pathKey => { - if (pathKey.startsWith(connectedPath)) { + if (`${pathKey}`.startsWith(connectedPath)) { pathKeys.add(path2keyRef.current.get(pathKey)); } }); diff --git a/src/interface.ts b/src/interface.ts index db433d01..eb4c21f6 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -6,6 +6,8 @@ interface ItemSharedProps { className?: string; } +export type MenuKey = React.Key; + export interface SubMenuType extends ItemSharedProps { label?: React.ReactNode; @@ -13,7 +15,7 @@ export interface SubMenuType extends ItemSharedProps { disabled?: boolean; - key: string; + key: MenuKey; rootClassName?: string; @@ -92,27 +94,27 @@ export type RenderIconType = | ((props: RenderIconInfo) => React.ReactNode); export interface MenuInfo { - key: string; - keyPath: string[]; + key: MenuKey; + keyPath: MenuKey[]; /** @deprecated This will not support in future. You should avoid to use this */ item: React.ReactInstance; domEvent: React.MouseEvent | React.KeyboardEvent; } export interface MenuTitleInfo { - key: string; + key: MenuKey; domEvent: React.MouseEvent | React.KeyboardEvent; } // ========================== Hover ========================== export type MenuHoverEventHandler = (info: { - key: string; + key: MenuKey; domEvent: React.MouseEvent; }) => void; // ======================== Selection ======================== export interface SelectInfo extends MenuInfo { - selectedKeys: string[]; + selectedKeys: MenuKey[]; } export type SelectEventHandler = (info: SelectInfo) => void; diff --git a/src/utils/commonUtil.ts b/src/utils/commonUtil.ts index 116ffc79..57e84bfb 100644 --- a/src/utils/commonUtil.ts +++ b/src/utils/commonUtil.ts @@ -1,33 +1,34 @@ +import { MenuKey } from "@/interface"; import toArray from "rc-util/lib/Children/toArray"; import * as React from 'react'; export function parseChildren( children: React.ReactNode | undefined, - keyPath: string[], + keyPath: MenuKey[], ) { return toArray(children).map((child, index) => { if (React.isValidElement(child)) { const { key } = child; let eventKey = (child.props as any)?.eventKey ?? key; - + const emptyKey = eventKey === null || eventKey === undefined; - + if (emptyKey) { eventKey = `tmp_key-${[...keyPath, index].join('-')}`; } - + const cloneProps = { key: eventKey, eventKey, } as any; - + if (process.env.NODE_ENV !== 'production' && emptyKey) { cloneProps.warnKey = true; } - + return React.cloneElement(child, cloneProps); } - + return child; }); - } \ No newline at end of file + }