1
This commit is contained in:
+4
@@ -0,0 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuDividerType } from './interface';
|
||||
export type DividerProps = Omit<MenuDividerType, 'type'>;
|
||||
export default function Divider({ className, style }: DividerProps): React.JSX.Element;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { MenuContext } from "./context/MenuContext";
|
||||
import { useMeasure } from "./context/PathContext";
|
||||
export default function Divider({
|
||||
className,
|
||||
style
|
||||
}) {
|
||||
const {
|
||||
prefixCls
|
||||
} = React.useContext(MenuContext);
|
||||
const measure = useMeasure();
|
||||
if (measure) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement("li", {
|
||||
role: "separator",
|
||||
className: clsx(`${prefixCls}-item-divider`, className),
|
||||
style: style
|
||||
});
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import type { RenderIconInfo, RenderIconType } from './interface';
|
||||
export interface IconProps {
|
||||
icon?: RenderIconType;
|
||||
props: RenderIconInfo;
|
||||
/** Fallback of icon if provided */
|
||||
children?: React.ReactElement;
|
||||
}
|
||||
export default function Icon({ icon, props, children }: IconProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
export default function Icon({
|
||||
icon,
|
||||
props,
|
||||
children
|
||||
}) {
|
||||
let iconNode;
|
||||
if (icon === null || icon === false) {
|
||||
return null;
|
||||
}
|
||||
if (typeof icon === 'function') {
|
||||
iconNode = /*#__PURE__*/React.createElement(icon, {
|
||||
...props
|
||||
});
|
||||
} else if (typeof icon !== "boolean") {
|
||||
// Compatible for origin definition
|
||||
iconNode = icon;
|
||||
}
|
||||
return iconNode || children || null;
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import * as React from 'react';
|
||||
import type { BuiltinPlacements, Components, ItemType, MenuClickEventHandler, MenuMode, MenuRef, RenderIconType, SelectEventHandler, TriggerSubMenuAction, PopupRender } from './interface';
|
||||
import { SemanticName } from './SubMenu';
|
||||
export interface MenuProps extends Omit<React.HTMLAttributes<HTMLUListElement>, 'onClick' | 'onSelect' | 'dir'> {
|
||||
prefixCls?: string;
|
||||
rootClassName?: string;
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
items?: ItemType[];
|
||||
/** @deprecated Please use `items` instead */
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
/** @private Disable auto overflow. Pls note the prop name may refactor since we do not final decided. */
|
||||
disabledOverflow?: boolean;
|
||||
/** direction of menu */
|
||||
direction?: 'ltr' | 'rtl';
|
||||
mode?: MenuMode;
|
||||
inlineCollapsed?: boolean;
|
||||
defaultOpenKeys?: string[];
|
||||
openKeys?: string[];
|
||||
activeKey?: string;
|
||||
defaultActiveFirst?: boolean;
|
||||
selectable?: boolean;
|
||||
multiple?: boolean;
|
||||
defaultSelectedKeys?: string[];
|
||||
selectedKeys?: string[];
|
||||
onSelect?: SelectEventHandler;
|
||||
onDeselect?: SelectEventHandler;
|
||||
inlineIndent?: number;
|
||||
/** Menu motion define. Use `defaultMotions` if you need config motion of each mode */
|
||||
motion?: CSSMotionProps;
|
||||
/** Default menu motion of each mode */
|
||||
defaultMotions?: Partial<{
|
||||
[key in MenuMode | 'other']: CSSMotionProps;
|
||||
}>;
|
||||
subMenuOpenDelay?: number;
|
||||
subMenuCloseDelay?: number;
|
||||
forceSubMenuRender?: boolean;
|
||||
triggerSubMenuAction?: TriggerSubMenuAction;
|
||||
builtinPlacements?: BuiltinPlacements;
|
||||
itemIcon?: RenderIconType;
|
||||
expandIcon?: RenderIconType;
|
||||
overflowedIndicator?: React.ReactNode;
|
||||
/** @private Internal usage. Do not use in your production. */
|
||||
overflowedIndicatorPopupClassName?: string;
|
||||
getPopupContainer?: (node: HTMLElement) => HTMLElement;
|
||||
onClick?: MenuClickEventHandler;
|
||||
onOpenChange?: (openKeys: string[]) => void;
|
||||
/***
|
||||
* @private Only used for `pro-layout`. Do not use in your prod directly
|
||||
* and we do not promise any compatibility for this.
|
||||
*/
|
||||
_internalRenderMenuItem?: (originNode: React.ReactElement, menuItemProps: any, stateProps: {
|
||||
selected: boolean;
|
||||
}) => React.ReactElement;
|
||||
/***
|
||||
* @private Only used for `pro-layout`. Do not use in your prod directly
|
||||
* and we do not promise any compatibility for this.
|
||||
*/
|
||||
_internalRenderSubMenuItem?: (originNode: React.ReactElement, subMenuItemProps: any, stateProps: {
|
||||
selected: boolean;
|
||||
open: boolean;
|
||||
active: boolean;
|
||||
disabled: boolean;
|
||||
}) => React.ReactElement;
|
||||
/**
|
||||
* @private NEVER! EVER! USE IN PRODUCTION!!!
|
||||
* This is a hack API for `antd` to fix `findDOMNode` issue.
|
||||
* Not use it! Not accept any PR try to make it as normal API.
|
||||
* By zombieJ
|
||||
*/
|
||||
_internalComponents?: Components;
|
||||
popupRender?: PopupRender;
|
||||
}
|
||||
declare const Menu: React.ForwardRefExoticComponent<MenuProps & React.RefAttributes<MenuRef>>;
|
||||
export default Menu;
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import { clsx } from 'clsx';
|
||||
import Overflow from '@rc-component/overflow';
|
||||
import useControlledState from "@rc-component/util/es/hooks/useControlledState";
|
||||
import useId from "@rc-component/util/es/hooks/useId";
|
||||
import isEqual from "@rc-component/util/es/isEqual";
|
||||
import warning from "@rc-component/util/es/warning";
|
||||
import * as React from 'react';
|
||||
import { useImperativeHandle } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { IdContext } from "./context/IdContext";
|
||||
import MenuContextProvider from "./context/MenuContext";
|
||||
import { PathRegisterContext, PathUserContext } from "./context/PathContext";
|
||||
import PrivateContext from "./context/PrivateContext";
|
||||
import { getFocusableElements, refreshElements, useAccessibility } from "./hooks/useAccessibility";
|
||||
import useKeyRecords, { OVERFLOW_KEY } from "./hooks/useKeyRecords";
|
||||
import useMemoCallback from "./hooks/useMemoCallback";
|
||||
import MenuItem from "./MenuItem";
|
||||
import SubMenu from "./SubMenu";
|
||||
import { parseItems } from "./utils/nodeUtil";
|
||||
import { warnItemProp } from "./utils/warnUtil";
|
||||
|
||||
/**
|
||||
* Menu modify after refactor:
|
||||
* ## Add
|
||||
* - disabled
|
||||
*
|
||||
* ## Remove
|
||||
* - openTransitionName
|
||||
* - openAnimation
|
||||
* - onDestroy
|
||||
* - siderCollapsed: Seems antd do not use this prop (Need test in antd)
|
||||
* - collapsedWidth: Seems this logic should be handle by antd Layout.Sider
|
||||
*/
|
||||
|
||||
// optimize for render
|
||||
const EMPTY_LIST = [];
|
||||
const Menu = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
prefixCls = 'rc-menu',
|
||||
rootClassName,
|
||||
style,
|
||||
className,
|
||||
styles,
|
||||
classNames: menuClassNames,
|
||||
tabIndex = 0,
|
||||
items,
|
||||
children,
|
||||
direction,
|
||||
id,
|
||||
// Mode
|
||||
mode = 'vertical',
|
||||
inlineCollapsed,
|
||||
// Disabled
|
||||
disabled,
|
||||
disabledOverflow,
|
||||
// Open
|
||||
subMenuOpenDelay = 0.1,
|
||||
subMenuCloseDelay = 0.1,
|
||||
forceSubMenuRender,
|
||||
defaultOpenKeys,
|
||||
openKeys,
|
||||
// Active
|
||||
activeKey,
|
||||
defaultActiveFirst,
|
||||
// Selection
|
||||
selectable = true,
|
||||
multiple = false,
|
||||
defaultSelectedKeys,
|
||||
selectedKeys,
|
||||
onSelect,
|
||||
onDeselect,
|
||||
// Level
|
||||
inlineIndent = 24,
|
||||
// Motion
|
||||
motion,
|
||||
defaultMotions,
|
||||
// Popup
|
||||
triggerSubMenuAction = 'hover',
|
||||
builtinPlacements,
|
||||
// Icon
|
||||
itemIcon,
|
||||
expandIcon,
|
||||
overflowedIndicator = '...',
|
||||
overflowedIndicatorPopupClassName,
|
||||
// Function
|
||||
getPopupContainer,
|
||||
// Events
|
||||
onClick,
|
||||
onOpenChange,
|
||||
onKeyDown,
|
||||
// Deprecated
|
||||
openAnimation,
|
||||
openTransitionName,
|
||||
// Internal
|
||||
_internalRenderMenuItem,
|
||||
_internalRenderSubMenuItem,
|
||||
_internalComponents,
|
||||
popupRender,
|
||||
...restProps
|
||||
} = props;
|
||||
const [childList, measureChildList] = React.useMemo(() => [parseItems(children, items, EMPTY_LIST, _internalComponents, prefixCls), parseItems(children, items, EMPTY_LIST, {}, prefixCls)], [children, items, _internalComponents]);
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const containerRef = React.useRef();
|
||||
const uuid = useId(id ? `rc-menu-uuid-${id}` : 'rc-menu-uuid');
|
||||
const isRtl = direction === 'rtl';
|
||||
|
||||
// ========================= Warn =========================
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warning(!openAnimation && !openTransitionName, '`openAnimation` and `openTransitionName` is removed. Please use `motion` or `defaultMotion` instead.');
|
||||
}
|
||||
|
||||
// ========================= Open =========================
|
||||
const [innerOpenKeys, setMergedOpenKeys] = useControlledState(defaultOpenKeys, openKeys);
|
||||
const mergedOpenKeys = innerOpenKeys || EMPTY_LIST;
|
||||
|
||||
// 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, forceFlush = false) => {
|
||||
function doUpdate() {
|
||||
setMergedOpenKeys(keys);
|
||||
onOpenChange?.(keys);
|
||||
}
|
||||
if (forceFlush) {
|
||||
flushSync(doUpdate);
|
||||
} else {
|
||||
doUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
// >>>>> Cache & Reset open keys when inlineCollapsed changed
|
||||
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] = React.useState(mergedOpenKeys);
|
||||
const mountRef = React.useRef(false);
|
||||
|
||||
// ========================= Mode =========================
|
||||
const [mergedMode, mergedInlineCollapsed] = React.useMemo(() => {
|
||||
if ((mode === 'inline' || mode === 'vertical') && inlineCollapsed) {
|
||||
return ['vertical', inlineCollapsed];
|
||||
}
|
||||
return [mode, false];
|
||||
}, [mode, inlineCollapsed]);
|
||||
const isInlineMode = mergedMode === 'inline';
|
||||
const [internalMode, setInternalMode] = React.useState(mergedMode);
|
||||
const [internalInlineCollapsed, setInternalInlineCollapsed] = React.useState(mergedInlineCollapsed);
|
||||
React.useEffect(() => {
|
||||
setInternalMode(mergedMode);
|
||||
setInternalInlineCollapsed(mergedInlineCollapsed);
|
||||
if (!mountRef.current) {
|
||||
return;
|
||||
}
|
||||
// Synchronously update MergedOpenKeys
|
||||
if (isInlineMode) {
|
||||
setMergedOpenKeys(inlineCacheOpenKeys);
|
||||
} else {
|
||||
// Trigger open event in case its in control
|
||||
triggerOpenKeys(EMPTY_LIST);
|
||||
}
|
||||
}, [mergedMode, mergedInlineCollapsed]);
|
||||
|
||||
// ====================== Responsive ======================
|
||||
const [lastVisibleIndex, setLastVisibleIndex] = React.useState(0);
|
||||
const allVisible = lastVisibleIndex >= childList.length - 1 || internalMode !== 'horizontal' || disabledOverflow;
|
||||
|
||||
// Cache
|
||||
React.useEffect(() => {
|
||||
if (isInlineMode) {
|
||||
setInlineCacheOpenKeys(mergedOpenKeys);
|
||||
}
|
||||
}, [mergedOpenKeys]);
|
||||
React.useEffect(() => {
|
||||
mountRef.current = true;
|
||||
return () => {
|
||||
mountRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ========================= Path =========================
|
||||
const {
|
||||
registerPath,
|
||||
unregisterPath,
|
||||
refreshOverflowKeys,
|
||||
isSubPathKey,
|
||||
getKeyPath,
|
||||
getKeys,
|
||||
getSubPathKeys
|
||||
} = useKeyRecords();
|
||||
const registerPathContext = React.useMemo(() => ({
|
||||
registerPath,
|
||||
unregisterPath
|
||||
}), [registerPath, unregisterPath]);
|
||||
const pathUserContext = React.useMemo(() => ({
|
||||
isSubPathKey
|
||||
}), [isSubPathKey]);
|
||||
React.useEffect(() => {
|
||||
refreshOverflowKeys(allVisible ? EMPTY_LIST : childList.slice(lastVisibleIndex + 1).map(child => child.key));
|
||||
}, [lastVisibleIndex, allVisible]);
|
||||
|
||||
// ======================== Active ========================
|
||||
const [mergedActiveKey, setMergedActiveKey] = useControlledState(activeKey || defaultActiveFirst && childList[0]?.key, activeKey);
|
||||
const onActive = useMemoCallback(key => {
|
||||
setMergedActiveKey(key);
|
||||
});
|
||||
const onInactive = useMemoCallback(() => {
|
||||
setMergedActiveKey(undefined);
|
||||
});
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
list: containerRef.current,
|
||||
focus: options => {
|
||||
const keys = getKeys();
|
||||
const {
|
||||
elements,
|
||||
key2element,
|
||||
element2key
|
||||
} = refreshElements(keys, uuid);
|
||||
const focusableElements = getFocusableElements(containerRef.current, elements);
|
||||
let shouldFocusKey;
|
||||
if (mergedActiveKey && keys.includes(mergedActiveKey)) {
|
||||
shouldFocusKey = mergedActiveKey;
|
||||
} else {
|
||||
shouldFocusKey = focusableElements[0] ? element2key.get(focusableElements[0]) : childList.find(node => !node.props.disabled)?.key;
|
||||
}
|
||||
const elementToFocus = key2element.get(shouldFocusKey);
|
||||
if (shouldFocusKey && elementToFocus) {
|
||||
elementToFocus?.focus?.(options);
|
||||
}
|
||||
},
|
||||
findItem: ({
|
||||
key: itemKey
|
||||
}) => {
|
||||
const keys = getKeys();
|
||||
const {
|
||||
key2element
|
||||
} = refreshElements(keys, uuid);
|
||||
return key2element.get(itemKey) || null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// ======================== Select ========================
|
||||
// >>>>> Select keys
|
||||
const [internalSelectKeys, setMergedSelectKeys] = useControlledState(defaultSelectedKeys || [], selectedKeys);
|
||||
const mergedSelectKeys = React.useMemo(() => {
|
||||
if (Array.isArray(internalSelectKeys)) {
|
||||
return internalSelectKeys;
|
||||
}
|
||||
if (internalSelectKeys === null || internalSelectKeys === undefined) {
|
||||
return EMPTY_LIST;
|
||||
}
|
||||
return [internalSelectKeys];
|
||||
}, [internalSelectKeys]);
|
||||
|
||||
// >>>>> Trigger select
|
||||
const triggerSelection = info => {
|
||||
if (selectable) {
|
||||
// Insert or Remove
|
||||
const {
|
||||
key: targetKey
|
||||
} = info;
|
||||
const exist = mergedSelectKeys.includes(targetKey);
|
||||
let newSelectKeys;
|
||||
if (multiple) {
|
||||
if (exist) {
|
||||
newSelectKeys = mergedSelectKeys.filter(key => key !== targetKey);
|
||||
} else {
|
||||
newSelectKeys = [...mergedSelectKeys, targetKey];
|
||||
}
|
||||
} else {
|
||||
newSelectKeys = [targetKey];
|
||||
}
|
||||
setMergedSelectKeys(newSelectKeys);
|
||||
|
||||
// Trigger event
|
||||
const selectInfo = {
|
||||
...info,
|
||||
selectedKeys: newSelectKeys
|
||||
};
|
||||
if (exist) {
|
||||
onDeselect?.(selectInfo);
|
||||
} else {
|
||||
onSelect?.(selectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Whatever selectable, always close it
|
||||
if (!multiple && mergedOpenKeys.length && internalMode !== 'inline') {
|
||||
triggerOpenKeys(EMPTY_LIST);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================= Open =========================
|
||||
/**
|
||||
* Click for item. SubMenu do not have selection status
|
||||
*/
|
||||
const onInternalClick = useMemoCallback(info => {
|
||||
onClick?.(warnItemProp(info));
|
||||
triggerSelection(info);
|
||||
});
|
||||
const onInternalOpenChange = useMemoCallback((key, open) => {
|
||||
let newOpenKeys = mergedOpenKeys.filter(k => k !== key);
|
||||
if (open) {
|
||||
newOpenKeys.push(key);
|
||||
} else if (internalMode !== 'inline') {
|
||||
// We need find all related popup to close
|
||||
const subPathKeys = getSubPathKeys(key);
|
||||
newOpenKeys = newOpenKeys.filter(k => !subPathKeys.has(k));
|
||||
}
|
||||
if (!isEqual(mergedOpenKeys, newOpenKeys, true)) {
|
||||
triggerOpenKeys(newOpenKeys, true);
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== Accessibility =====================
|
||||
const triggerAccessibilityOpen = (key, open) => {
|
||||
const nextOpen = open ?? !mergedOpenKeys.includes(key);
|
||||
onInternalOpenChange(key, nextOpen);
|
||||
};
|
||||
const onInternalKeyDown = useAccessibility(internalMode, mergedActiveKey, isRtl, uuid, containerRef, getKeys, getKeyPath, setMergedActiveKey, triggerAccessibilityOpen, onKeyDown);
|
||||
|
||||
// ======================== Effect ========================
|
||||
React.useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// ======================= Context ========================
|
||||
const privateContext = React.useMemo(() => ({
|
||||
_internalRenderMenuItem,
|
||||
_internalRenderSubMenuItem
|
||||
}), [_internalRenderMenuItem, _internalRenderSubMenuItem]);
|
||||
|
||||
// ======================== Render ========================
|
||||
|
||||
// >>>>> Children
|
||||
const wrappedChildList = internalMode !== 'horizontal' || disabledOverflow ? childList :
|
||||
// Need wrap for overflow dropdown that do not response for open
|
||||
childList.map((child, index) =>
|
||||
/*#__PURE__*/
|
||||
// Always wrap provider to avoid sub node re-mount
|
||||
React.createElement(MenuContextProvider, {
|
||||
key: child.key,
|
||||
overflowDisabled: index > lastVisibleIndex,
|
||||
classNames: menuClassNames,
|
||||
styles: styles
|
||||
}, child));
|
||||
|
||||
// >>>>> Container
|
||||
const container = /*#__PURE__*/React.createElement(Overflow, _extends({
|
||||
id: id,
|
||||
ref: containerRef,
|
||||
prefixCls: `${prefixCls}-overflow`,
|
||||
component: "ul",
|
||||
itemComponent: MenuItem,
|
||||
className: clsx(prefixCls, `${prefixCls}-root`, `${prefixCls}-${internalMode}`, className, {
|
||||
[`${prefixCls}-inline-collapsed`]: internalInlineCollapsed,
|
||||
[`${prefixCls}-rtl`]: isRtl
|
||||
}, rootClassName),
|
||||
dir: direction,
|
||||
style: style,
|
||||
role: "menu",
|
||||
tabIndex: tabIndex,
|
||||
data: wrappedChildList,
|
||||
renderRawItem: node => node,
|
||||
renderRawRest: omitItems => {
|
||||
// We use origin list since wrapped list use context to prevent open
|
||||
const len = omitItems.length;
|
||||
const originOmitItems = len ? childList.slice(-len) : null;
|
||||
return /*#__PURE__*/React.createElement(SubMenu, {
|
||||
eventKey: OVERFLOW_KEY,
|
||||
title: overflowedIndicator,
|
||||
disabled: allVisible,
|
||||
internalPopupClose: len === 0,
|
||||
popupClassName: overflowedIndicatorPopupClassName
|
||||
}, originOmitItems);
|
||||
},
|
||||
maxCount: internalMode !== 'horizontal' || disabledOverflow ? Overflow.INVALIDATE : Overflow.RESPONSIVE,
|
||||
ssr: "full",
|
||||
"data-menu-list": true,
|
||||
onVisibleChange: newLastIndex => {
|
||||
setLastVisibleIndex(newLastIndex);
|
||||
},
|
||||
onKeyDown: onInternalKeyDown
|
||||
}, restProps));
|
||||
|
||||
// >>>>> Render
|
||||
return /*#__PURE__*/React.createElement(PrivateContext.Provider, {
|
||||
value: privateContext
|
||||
}, /*#__PURE__*/React.createElement(IdContext.Provider, {
|
||||
value: uuid
|
||||
}, /*#__PURE__*/React.createElement(MenuContextProvider, {
|
||||
prefixCls: prefixCls,
|
||||
rootClassName: rootClassName,
|
||||
classNames: menuClassNames,
|
||||
styles: styles,
|
||||
mode: internalMode,
|
||||
openKeys: mergedOpenKeys,
|
||||
rtl: isRtl
|
||||
// Disabled
|
||||
,
|
||||
disabled: disabled
|
||||
// Motion
|
||||
,
|
||||
motion: mounted ? motion : null,
|
||||
defaultMotions: mounted ? defaultMotions : null
|
||||
// Active
|
||||
,
|
||||
activeKey: mergedActiveKey,
|
||||
onActive: onActive,
|
||||
onInactive: onInactive
|
||||
// Selection
|
||||
,
|
||||
selectedKeys: mergedSelectKeys
|
||||
// Level
|
||||
,
|
||||
inlineIndent: inlineIndent
|
||||
// Popup
|
||||
,
|
||||
subMenuOpenDelay: subMenuOpenDelay,
|
||||
subMenuCloseDelay: subMenuCloseDelay,
|
||||
forceSubMenuRender: forceSubMenuRender,
|
||||
builtinPlacements: builtinPlacements,
|
||||
triggerSubMenuAction: triggerSubMenuAction,
|
||||
getPopupContainer: getPopupContainer
|
||||
// Icon
|
||||
,
|
||||
itemIcon: itemIcon,
|
||||
expandIcon: expandIcon
|
||||
// Events
|
||||
,
|
||||
onItemClick: onInternalClick,
|
||||
onOpenChange: onInternalOpenChange,
|
||||
popupRender: popupRender
|
||||
}, /*#__PURE__*/React.createElement(PathUserContext.Provider, {
|
||||
value: pathUserContext
|
||||
}, container), /*#__PURE__*/React.createElement("div", {
|
||||
style: {
|
||||
display: 'none'
|
||||
},
|
||||
"aria-hidden": true
|
||||
}, /*#__PURE__*/React.createElement(PathRegisterContext.Provider, {
|
||||
value: registerPathContext
|
||||
}, measureChildList)))));
|
||||
});
|
||||
export default Menu;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuItemType } from './interface';
|
||||
export interface MenuItemProps extends Omit<MenuItemType, 'label' | 'key' | 'ref'>, Omit<React.HTMLAttributes<HTMLLIElement>, 'onClick' | 'onMouseEnter' | 'onMouseLeave' | 'onSelect'> {
|
||||
children?: React.ReactNode;
|
||||
/** @private Internal filled key. Do not set it directly */
|
||||
eventKey?: string;
|
||||
/** @private Do not use. Private warning empty usage */
|
||||
warnKey?: boolean;
|
||||
/** @deprecated No place to use this. Should remove */
|
||||
attribute?: Record<string, string>;
|
||||
}
|
||||
declare const _default: React.ForwardRefExoticComponent<MenuItemProps & React.RefAttributes<HTMLElement>>;
|
||||
export default _default;
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import { clsx } from 'clsx';
|
||||
import Overflow from '@rc-component/overflow';
|
||||
import KeyCode from "@rc-component/util/es/KeyCode";
|
||||
import omit from "@rc-component/util/es/omit";
|
||||
import { useComposeRef } from "@rc-component/util/es/ref";
|
||||
import warning from "@rc-component/util/es/warning";
|
||||
import * as React from 'react';
|
||||
import { useMenuId } from "./context/IdContext";
|
||||
import { MenuContext } from "./context/MenuContext";
|
||||
import { useFullPath, useMeasure } from "./context/PathContext";
|
||||
import PrivateContext from "./context/PrivateContext";
|
||||
import useActive from "./hooks/useActive";
|
||||
import useDirectionStyle from "./hooks/useDirectionStyle";
|
||||
import Icon from "./Icon";
|
||||
import { warnItemProp } from "./utils/warnUtil";
|
||||
// Since Menu event provide the `info.item` which point to the MenuItem node instance.
|
||||
// We have to use class component here.
|
||||
// This should be removed from doc & api in future.
|
||||
class LegacyMenuItem extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
attribute,
|
||||
elementRef,
|
||||
...restProps
|
||||
} = this.props;
|
||||
|
||||
// Here the props are eventually passed to the DOM element.
|
||||
// React does not recognize non-standard attributes.
|
||||
// Therefore, remove the props that is not used here.
|
||||
// ref: https://github.com/ant-design/ant-design/issues/41395
|
||||
const passedProps = omit(restProps, ['eventKey', 'popupClassName', 'popupOffset', 'onTitleClick']);
|
||||
warning(!attribute, '`attribute` of Menu.Item is deprecated. Please pass attribute directly.');
|
||||
return /*#__PURE__*/React.createElement(Overflow.Item, _extends({}, attribute, {
|
||||
title: typeof title === 'string' ? title : undefined
|
||||
}, passedProps, {
|
||||
ref: elementRef
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Real Menu Item component
|
||||
*/
|
||||
const InternalMenuItem = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
style,
|
||||
className,
|
||||
eventKey,
|
||||
warnKey,
|
||||
disabled,
|
||||
itemIcon,
|
||||
children,
|
||||
// Aria
|
||||
role,
|
||||
// Active
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
onFocus,
|
||||
...restProps
|
||||
} = props;
|
||||
const domDataId = useMenuId(eventKey);
|
||||
const {
|
||||
prefixCls,
|
||||
onItemClick,
|
||||
disabled: contextDisabled,
|
||||
overflowDisabled,
|
||||
// Icon
|
||||
itemIcon: contextItemIcon,
|
||||
// Select
|
||||
selectedKeys,
|
||||
// Active
|
||||
onActive
|
||||
} = React.useContext(MenuContext);
|
||||
const {
|
||||
_internalRenderMenuItem
|
||||
} = React.useContext(PrivateContext);
|
||||
const itemCls = `${prefixCls}-item`;
|
||||
const legacyMenuItemRef = React.useRef();
|
||||
const elementRef = React.useRef();
|
||||
const mergedDisabled = contextDisabled || disabled;
|
||||
const mergedEleRef = useComposeRef(ref, elementRef);
|
||||
const connectedKeys = useFullPath(eventKey);
|
||||
|
||||
// ================================ Warn ================================
|
||||
if (process.env.NODE_ENV !== 'production' && warnKey) {
|
||||
warning(false, 'MenuItem should not leave undefined `key`.');
|
||||
}
|
||||
|
||||
// ============================= Info =============================
|
||||
const getEventInfo = e => {
|
||||
return {
|
||||
key: eventKey,
|
||||
// Note: For legacy code is reversed which not like other antd component
|
||||
keyPath: [...connectedKeys].reverse(),
|
||||
item: legacyMenuItemRef.current,
|
||||
domEvent: e
|
||||
};
|
||||
};
|
||||
|
||||
// ============================= Icon =============================
|
||||
const mergedItemIcon = itemIcon || contextItemIcon;
|
||||
|
||||
// ============================ Active ============================
|
||||
const {
|
||||
active,
|
||||
...activeProps
|
||||
} = useActive(eventKey, mergedDisabled, onMouseEnter, onMouseLeave);
|
||||
|
||||
// ============================ Select ============================
|
||||
const selected = selectedKeys.includes(eventKey);
|
||||
|
||||
// ======================== DirectionStyle ========================
|
||||
const directionStyle = useDirectionStyle(connectedKeys.length);
|
||||
|
||||
// ============================ Events ============================
|
||||
const onInternalClick = e => {
|
||||
if (mergedDisabled) {
|
||||
return;
|
||||
}
|
||||
const info = getEventInfo(e);
|
||||
onClick?.(warnItemProp(info));
|
||||
onItemClick(info);
|
||||
};
|
||||
const onInternalKeyDown = e => {
|
||||
onKeyDown?.(e);
|
||||
if (e.which === KeyCode.ENTER) {
|
||||
const info = getEventInfo(e);
|
||||
|
||||
// Legacy. Key will also trigger click event
|
||||
onClick?.(warnItemProp(info));
|
||||
onItemClick(info);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used for accessibility. Helper will focus element without key board.
|
||||
* We should manually trigger an active
|
||||
*/
|
||||
const onInternalFocus = e => {
|
||||
onActive(eventKey);
|
||||
onFocus?.(e);
|
||||
};
|
||||
|
||||
// ============================ Render ============================
|
||||
const optionRoleProps = {};
|
||||
if (props.role === 'option') {
|
||||
optionRoleProps['aria-selected'] = selected;
|
||||
}
|
||||
let renderNode = /*#__PURE__*/React.createElement(LegacyMenuItem, _extends({
|
||||
ref: legacyMenuItemRef,
|
||||
elementRef: mergedEleRef,
|
||||
role: role === null ? 'none' : role || 'menuitem',
|
||||
tabIndex: disabled ? null : -1,
|
||||
"data-menu-id": overflowDisabled && domDataId ? null : domDataId
|
||||
}, omit(restProps, ['extra']), activeProps, optionRoleProps, {
|
||||
component: "li",
|
||||
"aria-disabled": disabled,
|
||||
style: {
|
||||
...directionStyle,
|
||||
...style
|
||||
},
|
||||
className: clsx(itemCls, {
|
||||
[`${itemCls}-active`]: active,
|
||||
[`${itemCls}-selected`]: selected,
|
||||
[`${itemCls}-disabled`]: mergedDisabled
|
||||
}, className),
|
||||
onClick: onInternalClick,
|
||||
onKeyDown: onInternalKeyDown,
|
||||
onFocus: onInternalFocus
|
||||
}), children, /*#__PURE__*/React.createElement(Icon, {
|
||||
props: {
|
||||
...props,
|
||||
isSelected: selected
|
||||
},
|
||||
icon: mergedItemIcon
|
||||
}));
|
||||
if (_internalRenderMenuItem) {
|
||||
renderNode = _internalRenderMenuItem(renderNode, props, {
|
||||
selected
|
||||
});
|
||||
}
|
||||
return renderNode;
|
||||
});
|
||||
function MenuItem(props, ref) {
|
||||
const {
|
||||
eventKey
|
||||
} = props;
|
||||
|
||||
// ==================== Record KeyPath ====================
|
||||
const measure = useMeasure();
|
||||
const connectedKeyPath = useFullPath(eventKey);
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
React.useEffect(() => {
|
||||
if (measure) {
|
||||
measure.registerPath(eventKey, connectedKeyPath);
|
||||
return () => {
|
||||
measure.unregisterPath(eventKey, connectedKeyPath);
|
||||
};
|
||||
}
|
||||
}, [connectedKeyPath]);
|
||||
if (measure) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ======================== Render ========================
|
||||
return /*#__PURE__*/React.createElement(InternalMenuItem, _extends({}, props, {
|
||||
ref: ref
|
||||
}));
|
||||
}
|
||||
export default /*#__PURE__*/React.forwardRef(MenuItem);
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuItemGroupType } from './interface';
|
||||
export interface MenuItemGroupProps extends Omit<MenuItemGroupType, 'type' | 'children' | 'label'> {
|
||||
title?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
/** @private Internal filled key. Do not set it directly */
|
||||
eventKey?: string;
|
||||
/** @private Do not use. Private warning empty usage */
|
||||
warnKey?: boolean;
|
||||
}
|
||||
declare const MenuItemGroup: React.ForwardRefExoticComponent<Omit<MenuItemGroupProps, "ref"> & React.RefAttributes<HTMLLIElement>>;
|
||||
export default MenuItemGroup;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import { clsx } from 'clsx';
|
||||
import omit from "@rc-component/util/es/omit";
|
||||
import * as React from 'react';
|
||||
import { MenuContext } from "./context/MenuContext";
|
||||
import { useFullPath, useMeasure } from "./context/PathContext";
|
||||
import { parseChildren } from "./utils/commonUtil";
|
||||
const InternalMenuItemGroup = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
className,
|
||||
title,
|
||||
eventKey,
|
||||
children,
|
||||
...restProps
|
||||
} = props;
|
||||
const {
|
||||
prefixCls,
|
||||
classNames: menuClassNames,
|
||||
styles
|
||||
} = React.useContext(MenuContext);
|
||||
const groupPrefixCls = `${prefixCls}-item-group`;
|
||||
return /*#__PURE__*/React.createElement("li", _extends({
|
||||
ref: ref,
|
||||
role: "presentation"
|
||||
}, restProps, {
|
||||
onClick: e => e.stopPropagation(),
|
||||
className: clsx(groupPrefixCls, className)
|
||||
}), /*#__PURE__*/React.createElement("div", {
|
||||
role: "presentation",
|
||||
className: clsx(`${groupPrefixCls}-title`, menuClassNames?.listTitle),
|
||||
style: styles?.listTitle,
|
||||
title: typeof title === 'string' ? title : undefined
|
||||
}, title), /*#__PURE__*/React.createElement("ul", {
|
||||
role: "group",
|
||||
className: clsx(`${groupPrefixCls}-list`, menuClassNames?.list),
|
||||
style: styles?.list
|
||||
}, children));
|
||||
});
|
||||
const MenuItemGroup = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
eventKey,
|
||||
children
|
||||
} = props;
|
||||
const connectedKeyPath = useFullPath(eventKey);
|
||||
const childList = parseChildren(children, connectedKeyPath);
|
||||
const measure = useMeasure();
|
||||
if (measure) {
|
||||
return childList;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(InternalMenuItemGroup, _extends({
|
||||
ref: ref
|
||||
}, omit(props, ['warnKey'])), childList);
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
MenuItemGroup.displayName = 'MenuItemGroup';
|
||||
}
|
||||
export default MenuItemGroup;
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
export interface InlineSubMenuListProps {
|
||||
id?: string;
|
||||
open: boolean;
|
||||
keyPath: string[];
|
||||
children: React.ReactNode;
|
||||
}
|
||||
export default function InlineSubMenuList({ id, open, keyPath, children }: InlineSubMenuListProps): React.JSX.Element;
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import * as React from 'react';
|
||||
import CSSMotion from '@rc-component/motion';
|
||||
import { getMotion } from "../utils/motionUtil";
|
||||
import MenuContextProvider, { MenuContext } from "../context/MenuContext";
|
||||
import SubMenuList from "./SubMenuList";
|
||||
export default function InlineSubMenuList({
|
||||
id,
|
||||
open,
|
||||
keyPath,
|
||||
children
|
||||
}) {
|
||||
const fixedMode = 'inline';
|
||||
const {
|
||||
prefixCls,
|
||||
forceSubMenuRender,
|
||||
motion,
|
||||
defaultMotions,
|
||||
mode
|
||||
} = React.useContext(MenuContext);
|
||||
|
||||
// Always use latest mode check
|
||||
const sameModeRef = React.useRef(false);
|
||||
sameModeRef.current = mode === fixedMode;
|
||||
|
||||
// We record `destroy` mark here since when mode change from `inline` to others.
|
||||
// The inline list should remove when motion end.
|
||||
const [destroy, setDestroy] = React.useState(!sameModeRef.current);
|
||||
const mergedOpen = sameModeRef.current ? open : false;
|
||||
|
||||
// ================================= Effect =================================
|
||||
// Reset destroy state when mode change back
|
||||
React.useEffect(() => {
|
||||
if (sameModeRef.current) {
|
||||
setDestroy(false);
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
// ================================= Render =================================
|
||||
const mergedMotion = {
|
||||
...getMotion(fixedMode, motion, defaultMotions)
|
||||
};
|
||||
|
||||
// No need appear since nest inlineCollapse changed
|
||||
if (keyPath.length > 1) {
|
||||
mergedMotion.motionAppear = false;
|
||||
}
|
||||
|
||||
// Hide inline list when mode changed and motion end
|
||||
const originOnVisibleChanged = mergedMotion.onVisibleChanged;
|
||||
mergedMotion.onVisibleChanged = newVisible => {
|
||||
if (!sameModeRef.current && !newVisible) {
|
||||
setDestroy(true);
|
||||
}
|
||||
return originOnVisibleChanged?.(newVisible);
|
||||
};
|
||||
if (destroy) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(MenuContextProvider, {
|
||||
mode: fixedMode,
|
||||
locked: !sameModeRef.current
|
||||
}, /*#__PURE__*/React.createElement(CSSMotion, _extends({
|
||||
visible: mergedOpen
|
||||
}, mergedMotion, {
|
||||
forceRender: forceSubMenuRender,
|
||||
removeOnLeave: false,
|
||||
leavedClassName: `${prefixCls}-hidden`
|
||||
}), ({
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}) => {
|
||||
return /*#__PURE__*/React.createElement(SubMenuList, {
|
||||
id: id,
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}, children);
|
||||
}));
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuMode } from '../interface';
|
||||
export interface PopupTriggerProps {
|
||||
prefixCls: string;
|
||||
mode: MenuMode;
|
||||
visible: boolean;
|
||||
children: React.ReactElement;
|
||||
popup: React.ReactNode;
|
||||
popupStyle?: React.CSSProperties;
|
||||
popupClassName?: string;
|
||||
popupOffset?: number[];
|
||||
disabled: boolean;
|
||||
onVisibleChange: (visible: boolean) => void;
|
||||
}
|
||||
export default function PopupTrigger({ prefixCls, visible, children, popup, popupStyle, popupClassName, popupOffset, disabled, mode, onVisibleChange, }: PopupTriggerProps): React.JSX.Element;
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import * as React from 'react';
|
||||
import Trigger from '@rc-component/trigger';
|
||||
import { clsx } from 'clsx';
|
||||
import raf from "@rc-component/util/es/raf";
|
||||
import { MenuContext } from "../context/MenuContext";
|
||||
import { placements, placementsRtl } from "../placements";
|
||||
import { getMotion } from "../utils/motionUtil";
|
||||
const popupPlacementMap = {
|
||||
horizontal: 'bottomLeft',
|
||||
vertical: 'rightTop',
|
||||
'vertical-left': 'rightTop',
|
||||
'vertical-right': 'leftTop'
|
||||
};
|
||||
export default function PopupTrigger({
|
||||
prefixCls,
|
||||
visible,
|
||||
children,
|
||||
popup,
|
||||
popupStyle,
|
||||
popupClassName,
|
||||
popupOffset,
|
||||
disabled,
|
||||
mode,
|
||||
onVisibleChange
|
||||
}) {
|
||||
const {
|
||||
getPopupContainer,
|
||||
rtl,
|
||||
subMenuOpenDelay,
|
||||
subMenuCloseDelay,
|
||||
builtinPlacements,
|
||||
triggerSubMenuAction,
|
||||
forceSubMenuRender,
|
||||
rootClassName,
|
||||
// Motion
|
||||
motion,
|
||||
defaultMotions
|
||||
} = React.useContext(MenuContext);
|
||||
const [innerVisible, setInnerVisible] = React.useState(false);
|
||||
const placement = rtl ? {
|
||||
...placementsRtl,
|
||||
...builtinPlacements
|
||||
} : {
|
||||
...placements,
|
||||
...builtinPlacements
|
||||
};
|
||||
const popupPlacement = popupPlacementMap[mode];
|
||||
const targetMotion = getMotion(mode, motion, defaultMotions);
|
||||
const targetMotionRef = React.useRef(targetMotion);
|
||||
if (mode !== 'inline') {
|
||||
/**
|
||||
* PopupTrigger is only used for vertical and horizontal types.
|
||||
* When collapsed is unfolded, the inline animation will destroy the vertical animation.
|
||||
*/
|
||||
targetMotionRef.current = targetMotion;
|
||||
}
|
||||
const mergedMotion = {
|
||||
...targetMotionRef.current,
|
||||
leavedClassName: `${prefixCls}-hidden`,
|
||||
removeOnLeave: false,
|
||||
motionAppear: true
|
||||
};
|
||||
|
||||
// Delay to change visible
|
||||
const visibleRef = React.useRef();
|
||||
React.useEffect(() => {
|
||||
visibleRef.current = raf(() => {
|
||||
setInnerVisible(visible);
|
||||
});
|
||||
return () => {
|
||||
raf.cancel(visibleRef.current);
|
||||
};
|
||||
}, [visible]);
|
||||
return /*#__PURE__*/React.createElement(Trigger, {
|
||||
prefixCls: prefixCls,
|
||||
popupClassName: clsx(`${prefixCls}-popup`, {
|
||||
[`${prefixCls}-rtl`]: rtl
|
||||
}, popupClassName, rootClassName),
|
||||
stretch: mode === 'horizontal' ? 'minWidth' : null,
|
||||
getPopupContainer: getPopupContainer,
|
||||
builtinPlacements: placement,
|
||||
popupPlacement: popupPlacement,
|
||||
popupVisible: innerVisible,
|
||||
popup: popup,
|
||||
popupStyle: popupStyle,
|
||||
popupAlign: popupOffset && {
|
||||
offset: popupOffset
|
||||
},
|
||||
action: disabled ? [] : [triggerSubMenuAction],
|
||||
mouseEnterDelay: subMenuOpenDelay,
|
||||
mouseLeaveDelay: subMenuCloseDelay,
|
||||
onPopupVisibleChange: onVisibleChange,
|
||||
forceRender: forceSubMenuRender,
|
||||
popupMotion: mergedMotion,
|
||||
fresh: true
|
||||
}, children);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
export interface SubMenuListProps extends React.HTMLAttributes<HTMLUListElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
declare const SubMenuList: React.ForwardRefExoticComponent<SubMenuListProps & React.RefAttributes<HTMLUListElement>>;
|
||||
export default SubMenuList;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { MenuContext } from "../context/MenuContext";
|
||||
const InternalSubMenuList = ({
|
||||
className,
|
||||
children,
|
||||
...restProps
|
||||
}, ref) => {
|
||||
const {
|
||||
prefixCls,
|
||||
mode,
|
||||
rtl
|
||||
} = React.useContext(MenuContext);
|
||||
return /*#__PURE__*/React.createElement("ul", _extends({
|
||||
className: clsx(prefixCls, rtl && `${prefixCls}-rtl`, `${prefixCls}-sub`, `${prefixCls}-${mode === 'inline' ? 'inline' : 'vertical'}`, className),
|
||||
role: "menu"
|
||||
}, restProps, {
|
||||
"data-menu-list": true,
|
||||
ref: ref
|
||||
}), children);
|
||||
};
|
||||
const SubMenuList = /*#__PURE__*/React.forwardRef(InternalSubMenuList);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
SubMenuList.displayName = 'SubMenuList';
|
||||
}
|
||||
export default SubMenuList;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import type { SubMenuType, PopupRender } from '../interface';
|
||||
export type SemanticName = 'list' | 'listTitle';
|
||||
export interface SubMenuProps extends Omit<SubMenuType, 'key' | 'children' | 'label'> {
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
title?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
/** @private Used for rest popup. Do not use in your prod */
|
||||
internalPopupClose?: boolean;
|
||||
/** @private Internal filled key. Do not set it directly */
|
||||
eventKey?: string;
|
||||
/** @private Do not use. Private warning empty usage */
|
||||
warnKey?: boolean;
|
||||
popupRender?: PopupRender;
|
||||
}
|
||||
declare const SubMenu: React.ForwardRefExoticComponent<Omit<SubMenuProps, "ref"> & React.RefAttributes<HTMLLIElement>>;
|
||||
export default SubMenu;
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import Overflow from '@rc-component/overflow';
|
||||
import warning from "@rc-component/util/es/warning";
|
||||
import SubMenuList from "./SubMenuList";
|
||||
import { parseChildren } from "../utils/commonUtil";
|
||||
import MenuContextProvider, { MenuContext } from "../context/MenuContext";
|
||||
import useMemoCallback from "../hooks/useMemoCallback";
|
||||
import PopupTrigger from "./PopupTrigger";
|
||||
import Icon from "../Icon";
|
||||
import useActive from "../hooks/useActive";
|
||||
import { warnItemProp } from "../utils/warnUtil";
|
||||
import useDirectionStyle from "../hooks/useDirectionStyle";
|
||||
import InlineSubMenuList from "./InlineSubMenuList";
|
||||
import { PathTrackerContext, PathUserContext, useFullPath, useMeasure } from "../context/PathContext";
|
||||
import { useMenuId } from "../context/IdContext";
|
||||
import PrivateContext from "../context/PrivateContext";
|
||||
const InternalSubMenu = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
style,
|
||||
className,
|
||||
styles,
|
||||
classNames: menuClassNames,
|
||||
title,
|
||||
eventKey,
|
||||
warnKey,
|
||||
disabled,
|
||||
internalPopupClose,
|
||||
children,
|
||||
// Icons
|
||||
itemIcon,
|
||||
expandIcon,
|
||||
// Popup
|
||||
popupClassName,
|
||||
popupOffset,
|
||||
popupStyle,
|
||||
// Events
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onTitleClick,
|
||||
onTitleMouseEnter,
|
||||
onTitleMouseLeave,
|
||||
popupRender: propsPopupRender,
|
||||
...restProps
|
||||
} = props;
|
||||
const domDataId = useMenuId(eventKey);
|
||||
const {
|
||||
prefixCls,
|
||||
mode,
|
||||
openKeys,
|
||||
// Disabled
|
||||
disabled: contextDisabled,
|
||||
overflowDisabled,
|
||||
// ActiveKey
|
||||
activeKey,
|
||||
// SelectKey
|
||||
selectedKeys,
|
||||
// Icon
|
||||
itemIcon: contextItemIcon,
|
||||
expandIcon: contextExpandIcon,
|
||||
// Events
|
||||
onItemClick,
|
||||
onOpenChange,
|
||||
onActive,
|
||||
popupRender: contextPopupRender
|
||||
} = React.useContext(MenuContext);
|
||||
const {
|
||||
_internalRenderSubMenuItem
|
||||
} = React.useContext(PrivateContext);
|
||||
const {
|
||||
isSubPathKey
|
||||
} = React.useContext(PathUserContext);
|
||||
const connectedPath = useFullPath();
|
||||
const subMenuPrefixCls = `${prefixCls}-submenu`;
|
||||
const mergedDisabled = contextDisabled || disabled;
|
||||
const elementRef = React.useRef();
|
||||
const popupRef = React.useRef();
|
||||
|
||||
// ================================ Warn ================================
|
||||
if (process.env.NODE_ENV !== 'production' && warnKey) {
|
||||
warning(false, 'SubMenu should not leave undefined `key`.');
|
||||
}
|
||||
|
||||
// ================================ Icon ================================
|
||||
const mergedItemIcon = itemIcon ?? contextItemIcon;
|
||||
const mergedExpandIcon = expandIcon ?? contextExpandIcon;
|
||||
|
||||
// ================================ Open ================================
|
||||
const originOpen = openKeys.includes(eventKey);
|
||||
const open = !overflowDisabled && originOpen;
|
||||
|
||||
// =============================== Select ===============================
|
||||
const childrenSelected = isSubPathKey(selectedKeys, eventKey);
|
||||
|
||||
// =============================== Active ===============================
|
||||
const {
|
||||
active,
|
||||
...activeProps
|
||||
} = useActive(eventKey, mergedDisabled, onTitleMouseEnter, onTitleMouseLeave);
|
||||
|
||||
// Fallback of active check to avoid hover on menu title or disabled item
|
||||
const [childrenActive, setChildrenActive] = React.useState(false);
|
||||
const triggerChildrenActive = newActive => {
|
||||
if (!mergedDisabled) {
|
||||
setChildrenActive(newActive);
|
||||
}
|
||||
};
|
||||
const onInternalMouseEnter = domEvent => {
|
||||
triggerChildrenActive(true);
|
||||
onMouseEnter?.({
|
||||
key: eventKey,
|
||||
domEvent
|
||||
});
|
||||
};
|
||||
const onInternalMouseLeave = domEvent => {
|
||||
triggerChildrenActive(false);
|
||||
onMouseLeave?.({
|
||||
key: eventKey,
|
||||
domEvent
|
||||
});
|
||||
};
|
||||
const mergedActive = React.useMemo(() => {
|
||||
if (active) {
|
||||
return active;
|
||||
}
|
||||
if (mode !== 'inline') {
|
||||
return childrenActive || isSubPathKey([activeKey], eventKey);
|
||||
}
|
||||
return false;
|
||||
}, [mode, active, activeKey, childrenActive, eventKey, isSubPathKey]);
|
||||
|
||||
// ========================== DirectionStyle ==========================
|
||||
const directionStyle = useDirectionStyle(connectedPath.length);
|
||||
|
||||
// =============================== Events ===============================
|
||||
// >>>> Title click
|
||||
const onInternalTitleClick = e => {
|
||||
// Skip if disabled
|
||||
if (mergedDisabled) {
|
||||
return;
|
||||
}
|
||||
onTitleClick?.({
|
||||
key: eventKey,
|
||||
domEvent: e
|
||||
});
|
||||
|
||||
// Trigger open by click when mode is `inline`
|
||||
if (mode === 'inline') {
|
||||
onOpenChange(eventKey, !originOpen);
|
||||
}
|
||||
};
|
||||
|
||||
// >>>> Context for children click
|
||||
const onMergedItemClick = useMemoCallback(info => {
|
||||
onClick?.(warnItemProp(info));
|
||||
onItemClick(info);
|
||||
});
|
||||
|
||||
// >>>>> Visible change
|
||||
const onPopupVisibleChange = newVisible => {
|
||||
if (mode !== 'inline') {
|
||||
onOpenChange(eventKey, newVisible);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used for accessibility. Helper will focus element without key board.
|
||||
* We should manually trigger an active
|
||||
*/
|
||||
const onInternalFocus = () => {
|
||||
onActive(eventKey);
|
||||
};
|
||||
|
||||
// =============================== Render ===============================
|
||||
const popupId = domDataId && `${domDataId}-popup`;
|
||||
const expandIconNode = React.useMemo(() => /*#__PURE__*/React.createElement(Icon, {
|
||||
icon: mode !== 'horizontal' ? mergedExpandIcon : undefined,
|
||||
props: {
|
||||
...props,
|
||||
isOpen: open,
|
||||
// [Legacy] Not sure why need this mark
|
||||
isSubMenu: true
|
||||
}
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
className: `${subMenuPrefixCls}-arrow`
|
||||
})), [mode, mergedExpandIcon, props, open, subMenuPrefixCls]);
|
||||
|
||||
// >>>>> Title
|
||||
let titleNode = /*#__PURE__*/React.createElement("div", _extends({
|
||||
role: "menuitem",
|
||||
style: directionStyle,
|
||||
className: `${subMenuPrefixCls}-title`,
|
||||
tabIndex: mergedDisabled ? null : -1,
|
||||
ref: elementRef,
|
||||
title: typeof title === 'string' ? title : null,
|
||||
"data-menu-id": overflowDisabled && domDataId ? null : domDataId,
|
||||
"aria-expanded": open,
|
||||
"aria-haspopup": true,
|
||||
"aria-controls": popupId,
|
||||
"aria-disabled": mergedDisabled,
|
||||
onClick: onInternalTitleClick,
|
||||
onFocus: onInternalFocus
|
||||
}, activeProps), title, expandIconNode);
|
||||
|
||||
// Cache mode if it change to `inline` which do not have popup motion
|
||||
const triggerModeRef = React.useRef(mode);
|
||||
if (mode !== 'inline' && connectedPath.length > 1) {
|
||||
triggerModeRef.current = 'vertical';
|
||||
} else {
|
||||
triggerModeRef.current = mode;
|
||||
}
|
||||
const popupContentTriggerMode = triggerModeRef.current;
|
||||
const renderPopupContent = React.useMemo(() => {
|
||||
const originNode = /*#__PURE__*/React.createElement(MenuContextProvider, {
|
||||
classNames: menuClassNames,
|
||||
styles: styles,
|
||||
mode: popupContentTriggerMode === 'horizontal' ? 'vertical' : popupContentTriggerMode
|
||||
}, /*#__PURE__*/React.createElement(SubMenuList, {
|
||||
id: popupId,
|
||||
ref: popupRef
|
||||
}, children));
|
||||
const mergedPopupRender = propsPopupRender || contextPopupRender;
|
||||
if (mergedPopupRender) {
|
||||
const node = mergedPopupRender(originNode, {
|
||||
item: props,
|
||||
keys: connectedPath
|
||||
});
|
||||
return node;
|
||||
}
|
||||
return originNode;
|
||||
}, [propsPopupRender, contextPopupRender, connectedPath, popupId, children, props, popupContentTriggerMode]);
|
||||
if (!overflowDisabled) {
|
||||
const triggerMode = triggerModeRef.current;
|
||||
|
||||
// Still wrap with Trigger here since we need avoid react re-mount dom node
|
||||
// Which makes motion failed
|
||||
titleNode = /*#__PURE__*/React.createElement(PopupTrigger, {
|
||||
mode: triggerMode,
|
||||
prefixCls: subMenuPrefixCls,
|
||||
visible: !internalPopupClose && open && mode !== 'inline',
|
||||
popupClassName: popupClassName,
|
||||
popupOffset: popupOffset,
|
||||
popupStyle: popupStyle,
|
||||
popup: renderPopupContent,
|
||||
disabled: mergedDisabled,
|
||||
onVisibleChange: onPopupVisibleChange
|
||||
}, titleNode);
|
||||
}
|
||||
|
||||
// >>>>> List node
|
||||
let listNode = /*#__PURE__*/React.createElement(Overflow.Item, _extends({
|
||||
ref: ref,
|
||||
role: "none"
|
||||
}, restProps, {
|
||||
component: "li",
|
||||
style: style,
|
||||
className: clsx(subMenuPrefixCls, `${subMenuPrefixCls}-${mode}`, className, {
|
||||
[`${subMenuPrefixCls}-open`]: open,
|
||||
[`${subMenuPrefixCls}-active`]: mergedActive,
|
||||
[`${subMenuPrefixCls}-selected`]: childrenSelected,
|
||||
[`${subMenuPrefixCls}-disabled`]: mergedDisabled
|
||||
}),
|
||||
onMouseEnter: onInternalMouseEnter,
|
||||
onMouseLeave: onInternalMouseLeave
|
||||
}), titleNode, !overflowDisabled && /*#__PURE__*/React.createElement(InlineSubMenuList, {
|
||||
id: popupId,
|
||||
open: open,
|
||||
keyPath: connectedPath
|
||||
}, children));
|
||||
if (_internalRenderSubMenuItem) {
|
||||
listNode = _internalRenderSubMenuItem(listNode, props, {
|
||||
selected: childrenSelected,
|
||||
active: mergedActive,
|
||||
open,
|
||||
disabled: mergedDisabled
|
||||
});
|
||||
}
|
||||
|
||||
// >>>>> Render
|
||||
return /*#__PURE__*/React.createElement(MenuContextProvider, {
|
||||
classNames: menuClassNames,
|
||||
styles: styles,
|
||||
onItemClick: onMergedItemClick,
|
||||
mode: mode === 'horizontal' ? 'vertical' : mode,
|
||||
itemIcon: mergedItemIcon,
|
||||
expandIcon: mergedExpandIcon
|
||||
}, listNode);
|
||||
});
|
||||
const SubMenu = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
eventKey,
|
||||
children
|
||||
} = props;
|
||||
const connectedKeyPath = useFullPath(eventKey);
|
||||
const childList = parseChildren(children, connectedKeyPath);
|
||||
|
||||
// ==================== Record KeyPath ====================
|
||||
const measure = useMeasure();
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
React.useEffect(() => {
|
||||
if (measure) {
|
||||
measure.registerPath(eventKey, connectedKeyPath);
|
||||
return () => {
|
||||
measure.unregisterPath(eventKey, connectedKeyPath);
|
||||
};
|
||||
}
|
||||
}, [connectedKeyPath]);
|
||||
let renderNode;
|
||||
|
||||
// ======================== Render ========================
|
||||
if (measure) {
|
||||
renderNode = childList;
|
||||
} else {
|
||||
renderNode = /*#__PURE__*/React.createElement(InternalSubMenu, _extends({
|
||||
ref: ref
|
||||
}, props), childList);
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(PathTrackerContext.Provider, {
|
||||
value: connectedKeyPath
|
||||
}, renderNode);
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
SubMenu.displayName = 'SubMenu';
|
||||
}
|
||||
export default SubMenu;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import * as React from 'react';
|
||||
export declare const IdContext: React.Context<string>;
|
||||
export declare function getMenuId(uuid: string, eventKey: string): string;
|
||||
/**
|
||||
* Get `data-menu-id`
|
||||
*/
|
||||
export declare function useMenuId(eventKey: string): string;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
export const IdContext = /*#__PURE__*/React.createContext(null);
|
||||
export function getMenuId(uuid, eventKey) {
|
||||
return `${uuid}-${eventKey}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get `data-menu-id`
|
||||
*/
|
||||
export function useMenuId(eventKey) {
|
||||
const id = React.useContext(IdContext);
|
||||
return getMenuId(id, eventKey);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import * as React from 'react';
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import type { BuiltinPlacements, MenuClickEventHandler, MenuMode, RenderIconType, TriggerSubMenuAction, PopupRender } from '../interface';
|
||||
import { SubMenuProps } from '..';
|
||||
export interface MenuContextProps {
|
||||
prefixCls: string;
|
||||
classNames?: SubMenuProps['classNames'];
|
||||
styles?: SubMenuProps['styles'];
|
||||
rootClassName?: string;
|
||||
openKeys: string[];
|
||||
rtl?: boolean;
|
||||
mode: MenuMode;
|
||||
disabled?: boolean;
|
||||
overflowDisabled?: boolean;
|
||||
activeKey: string;
|
||||
onActive: (key: string) => void;
|
||||
onInactive: (key: string) => void;
|
||||
selectedKeys: string[];
|
||||
inlineIndent: number;
|
||||
motion?: CSSMotionProps;
|
||||
defaultMotions?: Partial<{
|
||||
[key in MenuMode | 'other']: CSSMotionProps;
|
||||
}>;
|
||||
subMenuOpenDelay: number;
|
||||
subMenuCloseDelay: number;
|
||||
forceSubMenuRender?: boolean;
|
||||
builtinPlacements?: BuiltinPlacements;
|
||||
triggerSubMenuAction?: TriggerSubMenuAction;
|
||||
popupRender?: PopupRender;
|
||||
itemIcon?: RenderIconType;
|
||||
expandIcon?: RenderIconType;
|
||||
onItemClick: MenuClickEventHandler;
|
||||
onOpenChange: (key: string, open: boolean) => void;
|
||||
getPopupContainer: (node: HTMLElement) => HTMLElement;
|
||||
}
|
||||
export declare const MenuContext: React.Context<MenuContextProps>;
|
||||
export interface InheritableContextProps extends Partial<MenuContextProps> {
|
||||
children?: React.ReactNode;
|
||||
locked?: boolean;
|
||||
}
|
||||
export default function InheritableContextProvider({ children, locked, ...restProps }: InheritableContextProps): React.JSX.Element;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import * as React from 'react';
|
||||
import useMemo from "@rc-component/util/es/hooks/useMemo";
|
||||
import isEqual from "@rc-component/util/es/isEqual";
|
||||
export const MenuContext = /*#__PURE__*/React.createContext(null);
|
||||
function mergeProps(origin, target) {
|
||||
const clone = {
|
||||
...origin
|
||||
};
|
||||
Object.keys(target).forEach(key => {
|
||||
const value = target[key];
|
||||
if (value !== undefined) {
|
||||
clone[key] = value;
|
||||
}
|
||||
});
|
||||
return clone;
|
||||
}
|
||||
export default function InheritableContextProvider({
|
||||
children,
|
||||
locked,
|
||||
...restProps
|
||||
}) {
|
||||
const context = React.useContext(MenuContext);
|
||||
const inheritableContext = useMemo(() => mergeProps(context, restProps), [context, restProps], (prev, next) => !locked && (prev[0] !== next[0] || !isEqual(prev[1], next[1], true)));
|
||||
return /*#__PURE__*/React.createElement(MenuContext.Provider, {
|
||||
value: inheritableContext
|
||||
}, children);
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
export interface PathRegisterContextProps {
|
||||
registerPath: (key: string, keyPath: string[]) => void;
|
||||
unregisterPath: (key: string, keyPath: string[]) => void;
|
||||
}
|
||||
export declare const PathRegisterContext: React.Context<PathRegisterContextProps>;
|
||||
export declare function useMeasure(): PathRegisterContextProps;
|
||||
export declare const PathTrackerContext: React.Context<string[]>;
|
||||
export declare function useFullPath(eventKey?: string): string[];
|
||||
export interface PathUserContextProps {
|
||||
isSubPathKey: (pathKeys: string[], eventKey: string) => boolean;
|
||||
}
|
||||
export declare const PathUserContext: React.Context<PathUserContextProps>;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
const EmptyList = [];
|
||||
|
||||
// ========================= Path Register =========================
|
||||
|
||||
export const PathRegisterContext = /*#__PURE__*/React.createContext(null);
|
||||
export function useMeasure() {
|
||||
return React.useContext(PathRegisterContext);
|
||||
}
|
||||
|
||||
// ========================= Path Tracker ==========================
|
||||
export const PathTrackerContext = /*#__PURE__*/React.createContext(EmptyList);
|
||||
export function useFullPath(eventKey) {
|
||||
const parentKeyPath = React.useContext(PathTrackerContext);
|
||||
return React.useMemo(() => eventKey !== undefined ? [...parentKeyPath, eventKey] : parentKeyPath, [parentKeyPath, eventKey]);
|
||||
}
|
||||
|
||||
// =========================== Path User ===========================
|
||||
|
||||
export const PathUserContext = /*#__PURE__*/React.createContext(null);
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuProps } from '../Menu';
|
||||
export interface PrivateContextProps {
|
||||
_internalRenderMenuItem?: MenuProps['_internalRenderMenuItem'];
|
||||
_internalRenderSubMenuItem?: MenuProps['_internalRenderSubMenuItem'];
|
||||
}
|
||||
declare const PrivateContext: React.Context<PrivateContextProps>;
|
||||
export default PrivateContext;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import * as React from 'react';
|
||||
const PrivateContext = /*#__PURE__*/React.createContext({});
|
||||
export default PrivateContext;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuMode } from '../interface';
|
||||
/**
|
||||
* Get focusable elements from the element set under provided container
|
||||
*/
|
||||
export declare function getFocusableElements(container: HTMLElement, elements: Set<HTMLElement>): HTMLElement[];
|
||||
export declare const refreshElements: (keys: string[], id: string) => {
|
||||
elements: Set<HTMLElement>;
|
||||
key2element: Map<string, HTMLElement>;
|
||||
element2key: Map<HTMLElement, string>;
|
||||
};
|
||||
export declare function useAccessibility<T extends HTMLElement>(mode: MenuMode, activeKey: string, isRtl: boolean, id: string, containerRef: React.RefObject<HTMLUListElement>, getKeys: () => string[], getKeyPath: (key: string, includeOverflow?: boolean) => string[], triggerActiveKey: (key: string) => void, triggerAccessibilityOpen: (key: string, open?: boolean) => void, originOnKeyDown?: React.KeyboardEventHandler<T>): React.KeyboardEventHandler<T>;
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
import { getFocusNodeList } from "@rc-component/util/es/Dom/focus";
|
||||
import KeyCode from "@rc-component/util/es/KeyCode";
|
||||
import raf from "@rc-component/util/es/raf";
|
||||
import * as React from 'react';
|
||||
import { getMenuId } from "../context/IdContext";
|
||||
// destruct to reduce minify size
|
||||
const {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
DOWN,
|
||||
ENTER,
|
||||
ESC,
|
||||
HOME,
|
||||
END
|
||||
} = KeyCode;
|
||||
const ArrowKeys = [UP, DOWN, LEFT, RIGHT];
|
||||
function getOffset(mode, isRootLevel, isRtl, which) {
|
||||
const prev = 'prev';
|
||||
const next = 'next';
|
||||
const children = 'children';
|
||||
const parent = 'parent';
|
||||
|
||||
// Inline enter is special that we use unique operation
|
||||
if (mode === 'inline' && which === ENTER) {
|
||||
return {
|
||||
inlineTrigger: true
|
||||
};
|
||||
}
|
||||
const inline = {
|
||||
[UP]: prev,
|
||||
[DOWN]: next
|
||||
};
|
||||
const horizontal = {
|
||||
[LEFT]: isRtl ? next : prev,
|
||||
[RIGHT]: isRtl ? prev : next,
|
||||
[DOWN]: children,
|
||||
[ENTER]: children
|
||||
};
|
||||
const vertical = {
|
||||
[UP]: prev,
|
||||
[DOWN]: next,
|
||||
[ENTER]: children,
|
||||
[ESC]: parent,
|
||||
[LEFT]: isRtl ? children : parent,
|
||||
[RIGHT]: isRtl ? parent : children
|
||||
};
|
||||
const offsets = {
|
||||
inline,
|
||||
horizontal,
|
||||
vertical,
|
||||
inlineSub: inline,
|
||||
horizontalSub: vertical,
|
||||
verticalSub: vertical
|
||||
};
|
||||
const type = offsets[`${mode}${isRootLevel ? '' : 'Sub'}`]?.[which];
|
||||
switch (type) {
|
||||
case prev:
|
||||
return {
|
||||
offset: -1,
|
||||
sibling: true
|
||||
};
|
||||
case next:
|
||||
return {
|
||||
offset: 1,
|
||||
sibling: true
|
||||
};
|
||||
case parent:
|
||||
return {
|
||||
offset: -1,
|
||||
sibling: false
|
||||
};
|
||||
case children:
|
||||
return {
|
||||
offset: 1,
|
||||
sibling: false
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function findContainerUL(element) {
|
||||
let current = element;
|
||||
while (current) {
|
||||
if (current.getAttribute('data-menu-list')) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
// Normally should not reach this line
|
||||
/* istanbul ignore next */
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find focused element within element set provided
|
||||
*/
|
||||
function getFocusElement(activeElement, elements) {
|
||||
let current = activeElement || document.activeElement;
|
||||
while (current) {
|
||||
if (elements.has(current)) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get focusable elements from the element set under provided container
|
||||
*/
|
||||
export function getFocusableElements(container, elements) {
|
||||
const list = getFocusNodeList(container, true);
|
||||
return list.filter(ele => elements.has(ele));
|
||||
}
|
||||
function getNextFocusElement(parentQueryContainer, elements, focusMenuElement, offset = 1) {
|
||||
// Key on the menu item will not get validate parent container
|
||||
if (!parentQueryContainer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// List current level menu item elements
|
||||
const sameLevelFocusableMenuElementList = getFocusableElements(parentQueryContainer, elements);
|
||||
|
||||
// Find next focus index
|
||||
const count = sameLevelFocusableMenuElementList.length;
|
||||
let focusIndex = sameLevelFocusableMenuElementList.findIndex(ele => focusMenuElement === ele);
|
||||
if (offset < 0) {
|
||||
if (focusIndex === -1) {
|
||||
focusIndex = count - 1;
|
||||
} else {
|
||||
focusIndex -= 1;
|
||||
}
|
||||
} else if (offset > 0) {
|
||||
focusIndex += 1;
|
||||
}
|
||||
focusIndex = (focusIndex + count) % count;
|
||||
|
||||
// Focus menu item
|
||||
return sameLevelFocusableMenuElementList[focusIndex];
|
||||
}
|
||||
export const refreshElements = (keys, id) => {
|
||||
const elements = new Set();
|
||||
const key2element = new Map();
|
||||
const element2key = new Map();
|
||||
keys.forEach(key => {
|
||||
const element = document.querySelector(`[data-menu-id='${getMenuId(id, key)}']`);
|
||||
if (element) {
|
||||
elements.add(element);
|
||||
element2key.set(element, key);
|
||||
key2element.set(key, element);
|
||||
}
|
||||
});
|
||||
return {
|
||||
elements,
|
||||
key2element,
|
||||
element2key
|
||||
};
|
||||
};
|
||||
export function useAccessibility(mode, activeKey, isRtl, id, containerRef, getKeys, getKeyPath, triggerActiveKey, triggerAccessibilityOpen, originOnKeyDown) {
|
||||
const rafRef = React.useRef();
|
||||
const activeRef = React.useRef();
|
||||
activeRef.current = activeKey;
|
||||
const cleanRaf = () => {
|
||||
raf.cancel(rafRef.current);
|
||||
};
|
||||
React.useEffect(() => () => {
|
||||
cleanRaf();
|
||||
}, []);
|
||||
return e => {
|
||||
const {
|
||||
which
|
||||
} = e;
|
||||
if ([...ArrowKeys, ENTER, ESC, HOME, END].includes(which)) {
|
||||
const keys = getKeys();
|
||||
let refreshedElements = refreshElements(keys, id);
|
||||
const {
|
||||
elements,
|
||||
key2element,
|
||||
element2key
|
||||
} = refreshedElements;
|
||||
|
||||
// First we should find current focused MenuItem/SubMenu element
|
||||
const activeElement = key2element.get(activeKey);
|
||||
const focusMenuElement = getFocusElement(activeElement, elements);
|
||||
const focusMenuKey = element2key.get(focusMenuElement);
|
||||
const offsetObj = getOffset(mode, getKeyPath(focusMenuKey, true).length === 1, isRtl, which);
|
||||
|
||||
// Some mode do not have fully arrow operation like inline
|
||||
if (!offsetObj && which !== HOME && which !== END) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow prevent default to avoid page scroll
|
||||
if (ArrowKeys.includes(which) || [HOME, END].includes(which)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
const tryFocus = menuElement => {
|
||||
if (menuElement) {
|
||||
let focusTargetElement = menuElement;
|
||||
|
||||
// Focus to link instead of menu item if possible
|
||||
const link = menuElement.querySelector('a');
|
||||
if (link?.getAttribute('href')) {
|
||||
focusTargetElement = link;
|
||||
}
|
||||
const targetKey = element2key.get(menuElement);
|
||||
triggerActiveKey(targetKey);
|
||||
|
||||
/**
|
||||
* Do not `useEffect` here since `tryFocus` may trigger async
|
||||
* which makes React sync update the `activeKey`
|
||||
* that force render before `useRef` set the next activeKey
|
||||
*/
|
||||
cleanRaf();
|
||||
rafRef.current = raf(() => {
|
||||
if (activeRef.current === targetKey) {
|
||||
focusTargetElement.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
if ([HOME, END].includes(which) || offsetObj.sibling || !focusMenuElement) {
|
||||
// ========================== Sibling ==========================
|
||||
// Find walkable focus menu element container
|
||||
let parentQueryContainer;
|
||||
if (!focusMenuElement || mode === 'inline') {
|
||||
parentQueryContainer = containerRef.current;
|
||||
} else {
|
||||
parentQueryContainer = findContainerUL(focusMenuElement);
|
||||
}
|
||||
|
||||
// Get next focus element
|
||||
let targetElement;
|
||||
const focusableElements = getFocusableElements(parentQueryContainer, elements);
|
||||
if (which === HOME) {
|
||||
targetElement = focusableElements[0];
|
||||
} else if (which === END) {
|
||||
targetElement = focusableElements[focusableElements.length - 1];
|
||||
} else {
|
||||
targetElement = getNextFocusElement(parentQueryContainer, elements, focusMenuElement, offsetObj.offset);
|
||||
}
|
||||
// Focus menu item
|
||||
tryFocus(targetElement);
|
||||
|
||||
// ======================= InlineTrigger =======================
|
||||
} else if (offsetObj.inlineTrigger) {
|
||||
// Inline trigger no need switch to sub menu item
|
||||
triggerAccessibilityOpen(focusMenuKey);
|
||||
// =========================== Level ===========================
|
||||
} else if (offsetObj.offset > 0) {
|
||||
triggerAccessibilityOpen(focusMenuKey, true);
|
||||
cleanRaf();
|
||||
rafRef.current = raf(() => {
|
||||
// Async should resync elements
|
||||
refreshedElements = refreshElements(keys, id);
|
||||
const controlId = focusMenuElement.getAttribute('aria-controls');
|
||||
const subQueryContainer = document.getElementById(controlId);
|
||||
|
||||
// Get sub focusable menu item
|
||||
const targetElement = getNextFocusElement(subQueryContainer, refreshedElements.elements);
|
||||
|
||||
// Focus menu item
|
||||
tryFocus(targetElement);
|
||||
}, 5);
|
||||
} else if (offsetObj.offset < 0) {
|
||||
const keyPath = getKeyPath(focusMenuKey, true);
|
||||
const parentKey = keyPath[keyPath.length - 2];
|
||||
const parentMenuElement = key2element.get(parentKey);
|
||||
|
||||
// Focus menu item
|
||||
triggerAccessibilityOpen(parentKey, false);
|
||||
tryFocus(parentMenuElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass origin key down event
|
||||
originOnKeyDown?.(e);
|
||||
};
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuHoverEventHandler } from '../interface';
|
||||
interface ActiveObj {
|
||||
active: boolean;
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLElement>;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
export default function useActive(eventKey: string, disabled: boolean, onMouseEnter?: MenuHoverEventHandler, onMouseLeave?: MenuHoverEventHandler): ActiveObj;
|
||||
export {};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { MenuContext } from "../context/MenuContext";
|
||||
export default function useActive(eventKey, disabled, onMouseEnter, onMouseLeave) {
|
||||
const {
|
||||
// Active
|
||||
activeKey,
|
||||
onActive,
|
||||
onInactive
|
||||
} = React.useContext(MenuContext);
|
||||
const ret = {
|
||||
active: activeKey === eventKey
|
||||
};
|
||||
|
||||
// Skip when disabled
|
||||
if (!disabled) {
|
||||
ret.onMouseEnter = domEvent => {
|
||||
onMouseEnter?.({
|
||||
key: eventKey,
|
||||
domEvent
|
||||
});
|
||||
onActive(eventKey);
|
||||
};
|
||||
ret.onMouseLeave = domEvent => {
|
||||
onMouseLeave?.({
|
||||
key: eventKey,
|
||||
domEvent
|
||||
});
|
||||
onInactive(eventKey);
|
||||
};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import * as React from 'react';
|
||||
export default function useDirectionStyle(level: number): React.CSSProperties;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { MenuContext } from "../context/MenuContext";
|
||||
export default function useDirectionStyle(level) {
|
||||
const {
|
||||
mode,
|
||||
rtl,
|
||||
inlineIndent
|
||||
} = React.useContext(MenuContext);
|
||||
if (mode !== 'inline') {
|
||||
return null;
|
||||
}
|
||||
const len = level;
|
||||
return rtl ? {
|
||||
paddingRight: len * inlineIndent
|
||||
} : {
|
||||
paddingLeft: len * inlineIndent
|
||||
};
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
export declare const OVERFLOW_KEY = "rc-menu-more";
|
||||
export default function useKeyRecords(): {
|
||||
registerPath: (key: string, keyPath: string[]) => void;
|
||||
unregisterPath: (key: string, keyPath: string[]) => void;
|
||||
refreshOverflowKeys: (keys: string[]) => void;
|
||||
isSubPathKey: (pathKeys: string[], eventKey: string) => boolean;
|
||||
getKeyPath: (eventKey: string, includeOverflow?: boolean) => string[];
|
||||
getKeys: () => string[];
|
||||
getSubPathKeys: (key: string) => Set<string>;
|
||||
};
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import * as React from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import warning from "@rc-component/util/es/warning";
|
||||
import { nextSlice } from "../utils/timeUtil";
|
||||
const PATH_SPLIT = '__RC_UTIL_PATH_SPLIT__';
|
||||
const getPathStr = keyPath => keyPath.join(PATH_SPLIT);
|
||||
const getPathKeys = keyPathStr => keyPathStr.split(PATH_SPLIT);
|
||||
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 [overflowKeys, setOverflowKeys] = React.useState([]);
|
||||
const updateRef = useRef(0);
|
||||
const destroyRef = useRef(false);
|
||||
const forceUpdate = () => {
|
||||
if (!destroyRef.current) {
|
||||
internalForceUpdate({});
|
||||
}
|
||||
};
|
||||
const registerPath = useCallback((key, keyPath) => {
|
||||
// Warning for invalidate or duplicated `key`
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warning(!key2pathRef.current.has(key), `Duplicated key '${key}' used in Menu by path [${keyPath.join(' > ')}]`);
|
||||
}
|
||||
|
||||
// Fill map
|
||||
const connectedPath = getPathStr(keyPath);
|
||||
path2keyRef.current.set(connectedPath, key);
|
||||
key2pathRef.current.set(key, connectedPath);
|
||||
updateRef.current += 1;
|
||||
const id = updateRef.current;
|
||||
nextSlice(() => {
|
||||
if (id === updateRef.current) {
|
||||
forceUpdate();
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
const unregisterPath = useCallback((key, keyPath) => {
|
||||
const connectedPath = getPathStr(keyPath);
|
||||
path2keyRef.current.delete(connectedPath);
|
||||
key2pathRef.current.delete(key);
|
||||
}, []);
|
||||
const refreshOverflowKeys = useCallback(keys => {
|
||||
setOverflowKeys(keys);
|
||||
}, []);
|
||||
const getKeyPath = useCallback((eventKey, includeOverflow) => {
|
||||
const fullPath = key2pathRef.current.get(eventKey) || '';
|
||||
const keys = getPathKeys(fullPath);
|
||||
if (includeOverflow && overflowKeys.includes(keys[0])) {
|
||||
keys.unshift(OVERFLOW_KEY);
|
||||
}
|
||||
return keys;
|
||||
}, [overflowKeys]);
|
||||
const isSubPathKey = useCallback((pathKeys, eventKey) => pathKeys.filter(item => item !== undefined).some(pathKey => {
|
||||
const pathKeyList = getKeyPath(pathKey, true);
|
||||
return pathKeyList.includes(eventKey);
|
||||
}), [getKeyPath]);
|
||||
const getKeys = () => {
|
||||
const keys = [...key2pathRef.current.keys()];
|
||||
if (overflowKeys.length) {
|
||||
keys.push(OVERFLOW_KEY);
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find current key related child path keys
|
||||
*/
|
||||
const getSubPathKeys = useCallback(key => {
|
||||
const connectedPath = `${key2pathRef.current.get(key)}${PATH_SPLIT}`;
|
||||
const pathKeys = new Set();
|
||||
[...path2keyRef.current.keys()].forEach(pathKey => {
|
||||
if (pathKey.startsWith(connectedPath)) {
|
||||
pathKeys.add(path2keyRef.current.get(pathKey));
|
||||
}
|
||||
});
|
||||
return pathKeys;
|
||||
}, []);
|
||||
React.useEffect(() => () => {
|
||||
destroyRef.current = true;
|
||||
}, []);
|
||||
return {
|
||||
// Register
|
||||
registerPath,
|
||||
unregisterPath,
|
||||
refreshOverflowKeys,
|
||||
// Util
|
||||
isSubPathKey,
|
||||
getKeyPath,
|
||||
getKeys,
|
||||
getSubPathKeys
|
||||
};
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Cache callback function that always return same ref instead.
|
||||
* This is used for context optimization.
|
||||
*/
|
||||
export default function useMemoCallback<T extends (...args: any[]) => void>(func: T): T;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
|
||||
/**
|
||||
* Cache callback function that always return same ref instead.
|
||||
* This is used for context optimization.
|
||||
*/
|
||||
export default function useMemoCallback(func) {
|
||||
const funRef = React.useRef(func);
|
||||
funRef.current = func;
|
||||
const callback = React.useCallback((...args) => funRef.current?.(...args), []);
|
||||
return func ? callback : undefined;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import Menu from './Menu';
|
||||
import MenuItem from './MenuItem';
|
||||
import SubMenu from './SubMenu';
|
||||
import MenuItemGroup from './MenuItemGroup';
|
||||
import { useFullPath } from './context/PathContext';
|
||||
import Divider from './Divider';
|
||||
import type { MenuProps } from './Menu';
|
||||
import type { MenuItemProps } from './MenuItem';
|
||||
import type { SubMenuProps } from './SubMenu';
|
||||
import type { MenuItemGroupProps } from './MenuItemGroup';
|
||||
import type { MenuRef } from './interface';
|
||||
export { SubMenu, MenuItem as Item, MenuItem, MenuItemGroup, MenuItemGroup as ItemGroup, Divider,
|
||||
/** @private Only used for antd internal. Do not use in your production. */
|
||||
useFullPath, };
|
||||
export type { MenuProps, SubMenuProps, MenuItemProps, MenuItemGroupProps, MenuRef, };
|
||||
type MenuType = typeof Menu & {
|
||||
Item: typeof MenuItem;
|
||||
SubMenu: typeof SubMenu;
|
||||
ItemGroup: typeof MenuItemGroup;
|
||||
Divider: typeof Divider;
|
||||
};
|
||||
declare const ExportMenu: MenuType;
|
||||
export default ExportMenu;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import Menu from "./Menu";
|
||||
import MenuItem from "./MenuItem";
|
||||
import SubMenu from "./SubMenu";
|
||||
import MenuItemGroup from "./MenuItemGroup";
|
||||
import { useFullPath } from "./context/PathContext";
|
||||
import Divider from "./Divider";
|
||||
export { SubMenu, MenuItem as Item, MenuItem, MenuItemGroup, MenuItemGroup as ItemGroup, Divider, /** @private Only used for antd internal. Do not use in your production. */
|
||||
useFullPath };
|
||||
const ExportMenu = Menu;
|
||||
ExportMenu.Item = MenuItem;
|
||||
ExportMenu.SubMenu = SubMenu;
|
||||
ExportMenu.ItemGroup = MenuItemGroup;
|
||||
ExportMenu.Divider = Divider;
|
||||
export default ExportMenu;
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import type * as React from 'react';
|
||||
import type { SubMenuProps } from './SubMenu';
|
||||
interface ItemSharedProps {
|
||||
ref?: React.Ref<HTMLLIElement | null>;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
export interface SubMenuType extends ItemSharedProps {
|
||||
type?: 'submenu';
|
||||
label?: React.ReactNode;
|
||||
children: ItemType[];
|
||||
disabled?: boolean;
|
||||
key: string;
|
||||
rootClassName?: string;
|
||||
itemIcon?: RenderIconType;
|
||||
expandIcon?: RenderIconType;
|
||||
onMouseEnter?: MenuHoverEventHandler;
|
||||
onMouseLeave?: MenuHoverEventHandler;
|
||||
popupClassName?: string;
|
||||
popupOffset?: number[];
|
||||
popupStyle?: React.CSSProperties;
|
||||
onClick?: MenuClickEventHandler;
|
||||
onTitleClick?: (info: MenuTitleInfo) => void;
|
||||
onTitleMouseEnter?: MenuHoverEventHandler;
|
||||
onTitleMouseLeave?: MenuHoverEventHandler;
|
||||
}
|
||||
export interface MenuItemType extends ItemSharedProps {
|
||||
type?: 'item';
|
||||
label?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
itemIcon?: RenderIconType;
|
||||
extra?: React.ReactNode;
|
||||
key: React.Key;
|
||||
onMouseEnter?: MenuHoverEventHandler;
|
||||
onMouseLeave?: MenuHoverEventHandler;
|
||||
onClick?: MenuClickEventHandler;
|
||||
}
|
||||
export interface MenuItemGroupType extends ItemSharedProps {
|
||||
type: 'group';
|
||||
label?: React.ReactNode;
|
||||
children?: ItemType[];
|
||||
}
|
||||
export interface MenuDividerType extends Omit<ItemSharedProps, 'ref'> {
|
||||
type: 'divider';
|
||||
}
|
||||
export type ItemType = SubMenuType | MenuItemType | MenuItemGroupType | MenuDividerType | null;
|
||||
export type MenuMode = 'horizontal' | 'vertical' | 'inline';
|
||||
export type BuiltinPlacements = Record<string, any>;
|
||||
export type TriggerSubMenuAction = 'click' | 'hover';
|
||||
export interface RenderIconInfo {
|
||||
isSelected?: boolean;
|
||||
isOpen?: boolean;
|
||||
isSubMenu?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
export type RenderIconType = React.ReactNode | ((props: RenderIconInfo) => React.ReactNode);
|
||||
export interface MenuInfo {
|
||||
key: string;
|
||||
keyPath: string[];
|
||||
/** @deprecated This will not support in future. You should avoid to use this */
|
||||
item: React.ReactInstance;
|
||||
domEvent: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>;
|
||||
}
|
||||
export interface MenuTitleInfo {
|
||||
key: string;
|
||||
domEvent: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>;
|
||||
}
|
||||
export type MenuHoverEventHandler = (info: {
|
||||
key: string;
|
||||
domEvent: React.MouseEvent<HTMLElement>;
|
||||
}) => void;
|
||||
export interface SelectInfo extends MenuInfo {
|
||||
selectedKeys: string[];
|
||||
}
|
||||
export type SelectEventHandler = (info: SelectInfo) => void;
|
||||
export type MenuClickEventHandler = (info: MenuInfo) => void;
|
||||
export type MenuRef = {
|
||||
/**
|
||||
* Focus active child if any, or the first child which is not disabled will be focused.
|
||||
* @param options
|
||||
*/
|
||||
focus: (options?: FocusOptions) => void;
|
||||
list: HTMLUListElement;
|
||||
findItem: (params: {
|
||||
key: string;
|
||||
}) => HTMLElement | null;
|
||||
};
|
||||
export type ComponentType = 'submenu' | 'item' | 'group' | 'divider';
|
||||
export type Components = Partial<Record<ComponentType, React.ComponentType<any>>>;
|
||||
export type PopupRender = (node: React.ReactElement, info: {
|
||||
item: SubMenuProps;
|
||||
keys: string[];
|
||||
}) => React.ReactNode;
|
||||
export {};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
export declare const placements: {
|
||||
topLeft: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
topRight: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
bottomLeft: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
bottomRight: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
leftTop: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
leftBottom: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
rightTop: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
rightBottom: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
export declare const placementsRtl: {
|
||||
topLeft: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
topRight: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
bottomLeft: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
bottomRight: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
rightTop: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
rightBottom: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
leftTop: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
leftBottom: {
|
||||
points: string[];
|
||||
overflow: {
|
||||
adjustX: number;
|
||||
adjustY: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
export default placements;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
const autoAdjustOverflow = {
|
||||
adjustX: 1,
|
||||
adjustY: 1
|
||||
};
|
||||
export const placements = {
|
||||
topLeft: {
|
||||
points: ['bl', 'tl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
topRight: {
|
||||
points: ['br', 'tr'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
bottomLeft: {
|
||||
points: ['tl', 'bl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
bottomRight: {
|
||||
points: ['tr', 'br'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
leftTop: {
|
||||
points: ['tr', 'tl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
leftBottom: {
|
||||
points: ['br', 'bl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
rightTop: {
|
||||
points: ['tl', 'tr'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
rightBottom: {
|
||||
points: ['bl', 'br'],
|
||||
overflow: autoAdjustOverflow
|
||||
}
|
||||
};
|
||||
export const placementsRtl = {
|
||||
topLeft: {
|
||||
points: ['bl', 'tl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
topRight: {
|
||||
points: ['br', 'tr'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
bottomLeft: {
|
||||
points: ['tl', 'bl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
bottomRight: {
|
||||
points: ['tr', 'br'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
rightTop: {
|
||||
points: ['tr', 'tl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
rightBottom: {
|
||||
points: ['br', 'bl'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
leftTop: {
|
||||
points: ['tl', 'tr'],
|
||||
overflow: autoAdjustOverflow
|
||||
},
|
||||
leftBottom: {
|
||||
points: ['bl', 'br'],
|
||||
overflow: autoAdjustOverflow
|
||||
}
|
||||
};
|
||||
export default placements;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import * as React from 'react';
|
||||
export declare function parseChildren(children: React.ReactNode | undefined, keyPath: string[]): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import toArray from "@rc-component/util/es/Children/toArray";
|
||||
import * as React from 'react';
|
||||
export function parseChildren(children, keyPath) {
|
||||
return toArray(children).map((child, index) => {
|
||||
if ( /*#__PURE__*/React.isValidElement(child)) {
|
||||
const {
|
||||
key
|
||||
} = child;
|
||||
let eventKey = child.props?.eventKey ?? key;
|
||||
const emptyKey = eventKey === null || eventKey === undefined;
|
||||
if (emptyKey) {
|
||||
eventKey = `tmp_key-${[...keyPath, index].join('-')}`;
|
||||
}
|
||||
const cloneProps = {
|
||||
key: eventKey,
|
||||
eventKey
|
||||
};
|
||||
if (process.env.NODE_ENV !== 'production' && emptyKey) {
|
||||
cloneProps.warnKey = true;
|
||||
}
|
||||
return /*#__PURE__*/React.cloneElement(child, cloneProps);
|
||||
}
|
||||
return child;
|
||||
});
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
export declare function getMotion(mode: string, motion?: CSSMotionProps, defaultMotions?: Record<string, CSSMotionProps>): CSSMotionProps;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export function getMotion(mode, motion, defaultMotions) {
|
||||
if (motion) {
|
||||
return motion;
|
||||
}
|
||||
if (defaultMotions) {
|
||||
return defaultMotions[mode] || defaultMotions.other;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import * as React from 'react';
|
||||
import type { Components, ItemType } from '../interface';
|
||||
export declare function parseItems(children: React.ReactNode | undefined, items: ItemType[] | undefined, keyPath: string[], components: Components, prefixCls?: string): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import * as React from 'react';
|
||||
import Divider from "../Divider";
|
||||
import MenuItem from "../MenuItem";
|
||||
import MenuItemGroup from "../MenuItemGroup";
|
||||
import SubMenu from "../SubMenu";
|
||||
import { parseChildren } from "./commonUtil";
|
||||
function convertItemsToNodes(list, components, prefixCls) {
|
||||
const {
|
||||
item: MergedMenuItem,
|
||||
group: MergedMenuItemGroup,
|
||||
submenu: MergedSubMenu,
|
||||
divider: MergedDivider
|
||||
} = components;
|
||||
return (list || []).map((opt, index) => {
|
||||
if (opt && typeof opt === 'object') {
|
||||
const {
|
||||
label,
|
||||
children,
|
||||
key,
|
||||
type,
|
||||
extra,
|
||||
...restProps
|
||||
} = opt;
|
||||
const mergedKey = key ?? `tmp-${index}`;
|
||||
|
||||
// MenuItemGroup & SubMenuItem
|
||||
if (children || type === 'group') {
|
||||
if (type === 'group') {
|
||||
// Group
|
||||
return /*#__PURE__*/React.createElement(MergedMenuItemGroup, _extends({
|
||||
key: mergedKey
|
||||
}, restProps, {
|
||||
title: label
|
||||
}), convertItemsToNodes(children, components, prefixCls));
|
||||
}
|
||||
|
||||
// Sub Menu
|
||||
return /*#__PURE__*/React.createElement(MergedSubMenu, _extends({
|
||||
key: mergedKey
|
||||
}, restProps, {
|
||||
title: label
|
||||
}), convertItemsToNodes(children, components, prefixCls));
|
||||
}
|
||||
|
||||
// MenuItem & Divider
|
||||
if (type === 'divider') {
|
||||
return /*#__PURE__*/React.createElement(MergedDivider, _extends({
|
||||
key: mergedKey
|
||||
}, restProps));
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(MergedMenuItem, _extends({
|
||||
key: mergedKey
|
||||
}, restProps, {
|
||||
extra: extra
|
||||
}), label, (!!extra || extra === 0) && /*#__PURE__*/React.createElement("span", {
|
||||
className: `${prefixCls}-item-extra`
|
||||
}, extra));
|
||||
}
|
||||
return null;
|
||||
}).filter(opt => opt);
|
||||
}
|
||||
export function parseItems(children, items, keyPath, components, prefixCls) {
|
||||
let childNodes = children;
|
||||
const mergedComponents = {
|
||||
divider: Divider,
|
||||
item: MenuItem,
|
||||
group: MenuItemGroup,
|
||||
submenu: SubMenu,
|
||||
...components
|
||||
};
|
||||
if (items) {
|
||||
childNodes = convertItemsToNodes(items, mergedComponents, prefixCls);
|
||||
}
|
||||
return parseChildren(childNodes, keyPath);
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function nextSlice(callback: () => void): void;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export function nextSlice(callback) {
|
||||
/* istanbul ignore next */
|
||||
Promise.resolve().then(callback);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/// <reference types="react" />
|
||||
/**
|
||||
* `onClick` event return `info.item` which point to react node directly.
|
||||
* We should warning this since it will not work on FC.
|
||||
*/
|
||||
export declare function warnItemProp<T extends {
|
||||
item: React.ReactInstance;
|
||||
}>({ item, ...restInfo }: T): T;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import warning from "@rc-component/util/es/warning";
|
||||
|
||||
/**
|
||||
* `onClick` event return `info.item` which point to react node directly.
|
||||
* We should warning this since it will not work on FC.
|
||||
*/
|
||||
export function warnItemProp({
|
||||
item,
|
||||
...restInfo
|
||||
}) {
|
||||
Object.defineProperty(restInfo, 'item', {
|
||||
get: () => {
|
||||
warning(false, '`info.item` is deprecated since we will move to function component that not provides React Node instance in future.');
|
||||
return item;
|
||||
}
|
||||
});
|
||||
return restInfo;
|
||||
}
|
||||
Reference in New Issue
Block a user