This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2014-present yiminghe
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+464
View File
@@ -0,0 +1,464 @@
# @rc-component/menu
---
React Menu Component. port from https://github.com/kissyteam/menu
[![NPM version][npm-image]][npm-url] [![npm download][download-image]][download-url] [![build status][github-actions-image]][github-actions-url] [![Codecov][codecov-image]][codecov-url] [![bundle size][bundlephobia-image]][bundlephobia-url] [![dumi][dumi-image]][dumi-url]
[npm-image]: https://img.shields.io/npm/v/@rc-component/menu.svg?style=flat-square
[npm-url]: https://npmjs.org/package/@rc-component/menu
[github-actions-image]: https://github.com/react-component/menu/actions/workflows/main.yml/badge.svg
[github-actions-url]: https://github.com/react-component/menu/actions/workflows/main.yml
[circleci-image]: https://img.shields.io/circleci/react-component/menu/master?style=flat-square
[circleci-url]: https://circleci.com/gh/react-component/menu
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/footer/master.svg?style=flat-square
[codecov-url]: https://codecov.io/gh/react-component/footer/branch/master
[coveralls-image]: https://img.shields.io/coveralls/react-component/menu.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/react-component/menu?branch=master
[david-url]: https://david-dm.org/react-component/menu
[david-image]: https://david-dm.org/react-component/menu/status.svg?style=flat-square
[david-dev-url]: https://david-dm.org/react-component/menu?type=dev
[david-dev-image]: https://david-dm.org/react-component/menu/dev-status.svg?style=flat-square
[download-image]: https://img.shields.io/npm/dm/@rc-component/menu.svg?style=flat-square
[download-url]: https://npmjs.org/package/@rc-component/menu
[bundlephobia-url]: https://bundlephobia.com/result?p=@rc-component/menu
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/menu
[dumi-url]: https://github.com/umijs/dumi
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
## Install
[![@rc-component/menu](https://nodei.co/npm/@rc-component/menu.png)](https://npmjs.org/package/@rc-component/menu)
## Usage
```js
import Menu, { SubMenu, MenuItem } from '@rc-component/menu';
ReactDOM.render(
<Menu>
<MenuItem>1</MenuItem>
<SubMenu title="2">
<MenuItem>2-1</MenuItem>
</SubMenu>
</Menu>,
container,
);
```
## Compatibility
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Electron |
| --- | --- | --- | --- | --- |
| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## API
### Menu props
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 100px;">name</th>
<th style="width: 50px;">type</th>
<th style="width: 50px;">default</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>ref</td>
<td>React.HTMLLIElement</td>
<td></td>
<td>get dom node</td>
</tr>
<tr>
<td>className</td>
<td>String</td>
<td></td>
<td>additional css class of root dom node</td>
</tr>
<tr>
<td>mode</td>
<td>String</td>
<th>vertical</th>
<td>one of ["horizontal","inline","vertical-left","vertical-right"]</td>
</tr>
<tr>
<td>activeKey</td>
<td>String</td>
<th></th>
<td>initial and current active menu item's key.</td>
</tr>
<tr>
<td>defaultActiveFirst</td>
<td>Boolean</td>
<th>false</th>
<td>whether active first menu item when show if activeKey is not set or invalid</td>
</tr>
<tr>
<td>multiple</td>
<td>Boolean</td>
<th>false</th>
<td>whether allow multiple select</td>
</tr>
<tr>
<td>selectable</td>
<td>Boolean</td>
<th>true</th>
<td>allow selecting menu items</td>
</tr>
<tr>
<td>selectedKeys</td>
<td>String[]</td>
<th>[]</th>
<td>selected keys of items</td>
</tr>
<tr>
<td>defaultSelectedKeys</td>
<td>String[]</td>
<th>[]</th>
<td>initial selected keys of items</td>
</tr>
<tr>
<td>openKeys</td>
<td>String[]</td>
<th>[]</th>
<td>open keys of SubMenuItem</td>
</tr>
<tr>
<td>defaultOpenKeys</td>
<td>String[]</td>
<th>[]</th>
<td>initial open keys of SubMenuItem</td>
</tr>
<tr>
<td>onSelect</td>
<td>function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[]})</td>
<th></th>
<td>called when select a menu item</td>
</tr>
<tr>
<td>onClick</td>
<td>function({key:String, item:ReactComponent, domEvent:Event, keyPath: String[]})</td>
<th></th>
<td>called when click a menu item</td>
</tr>
<tr>
<td>onOpenChange</td>
<td>(openKeys:String[]) => void</td>
<th></th>
<td>called when open/close sub menu</td>
</tr>
<tr>
<td>onDeselect</td>
<td>function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[]})</td>
<th></th>
<td>called when deselect a menu item. only called when allow multiple</td>
</tr>
<tr>
<td>triggerSubMenuAction</td>
<td>Enum { hover, click }</td>
<th>hover</th>
<td>which action can trigger submenu open/close</td>
</tr>
<tr>
<td>openAnimation</td>
<td>{enter:function,leave:function}|String</td>
<th></th>
<td>animate when sub menu open or close. see @rc-component/motion for object type.</td>
</tr>
<tr>
<td>openTransition</td>
<td>String</td>
<th></th>
<td>css transitionName when sub menu open or close</td>
</tr>
<tr>
<td>subMenuOpenDelay</td>
<td>Number</td>
<th>0</th>
<td>delay time to show popup sub menu. unit: s</td>
</tr>
<tr>
<td>subMenuCloseDelay</td>
<td>Number</td>
<th>0.1</th>
<td>delay time to hide popup sub menu. unit: s</td>
</tr>
<tr>
<td>forceSubMenuRender</td>
<td>Boolean</td>
<th>false</th>
<td>whether to render submenu even if it is not visible</td>
</tr>
<tr>
<td>getPopupContainer</td>
<td>Function(menuDOMNode): HTMLElement</td>
<th>() => document.body</th>
<td>Where to render the DOM node of popup menu when the mode is horizontal or vertical</td>
</tr>
<tr>
<td>builtinPlacements</td>
<td>Object of alignConfigs for <a href="https://github.com/yiminghe/dom-align">dom-align</a></td>
<th>see <a href="./src/placements.ts">placements.ts</a></th>
<td>Describes how the popup menus should be positioned</td>
</tr>
<tr>
<td>itemIcon</td>
<td>ReactNode | (props: MenuItemProps) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
<tr>
<td>expandIcon</td>
<td>ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
<tr>
<td>direction</td>
<td>String</td>
<th></th>
<td>Layout direction of menu component, it supports RTL direction too.</td>
</tr>
<tr>
<td>inlineIndent</td>
<td>Number</td>
<th>24</th>
<td>Padding level multiplier. Right or left padding depends on param "direction".</td>
</tr>
</tbody>
</table>
### Menu.Item props
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 100px;">name</th>
<th style="width: 50px;">type</th>
<th style="width: 50px;">default</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>className</td>
<td>String</td>
<td></td>
<td>additional css class of root dom node</td>
</tr>
<tr>
<td>disabled</td>
<td>Boolean</td>
<th>false</th>
<td>no effect for click or keydown for this item</td>
</tr>
<tr>
<td>extra</td>
<td>React.ReactNode</td>
<th></th>
<td>Specify menu item extra node.</td>
</tr>
<tr>
<td>key</td>
<td>Object</td>
<th></th>
<td>corresponding to activeKey</td>
</tr>
<tr>
<td>onMouseEnter</td>
<td>Function({eventKey, domEvent})</td>
<th></th>
<td></td>
</tr>
<tr>
<td>onMouseLeave</td>
<td>Function({eventKey, domEvent})</td>
<th></th>
<td></td>
</tr>
<tr>
<td>itemIcon</td>
<td>ReactNode | (props: MenuItemProps) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
</tbody>
</table>
### Menu.SubMenu props
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 100px;">name</th>
<th style="width: 50px;">type</th>
<th style="width: 50px;">default</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>ref</td>
<td>React.HTMLLIElement</td>
<td></td>
<td>get dom node</td>
</tr>
<tr>
<td>popupClassName</td>
<td>String</td>
<td></td>
<td>additional css class of root dom node</td>
</tr>
<tr>
<td>popupStyle</td>
<td>CSSProperties</td>
<td></td>
<td>additional css style of root dom node</td>
</tr>
<tr>
<td>title</td>
<td>String/ReactElement</td>
<td></td>
<td>sub menu's content</td>
</tr>
<tr>
<td>overflowedIndicator</td>
<td>String/ReactElement</td>
<td>···</td>
<td>overflow indicator when menu overlows in horizontal mode</td>
</tr>
<tr>
<td>key</td>
<td>Object</td>
<th></th>
<td>corresponding to activeKey</td>
</tr>
<tr>
<td>disabled</td>
<td>Boolean</td>
<th>false</th>
<td>no effect for click or keydown for this item</td>
</tr>
<tr>
<td>onMouseEnter</td>
<td>Function({eventKey, domEvent})</td>
<th></th>
<td></td>
</tr>
<tr>
<td>onMouseLeave</td>
<td>Function({eventKey, domEvent})</td>
<th></th>
<td></td>
</tr>
<tr>
<td>onTitleMouseEnter</td>
<td>Function({eventKey, domEvent})</td>
<th></th>
<td></td>
</tr>
<tr>
<td>onTitleMouseLeave</td>
<td>Function({eventKey, domEvent})</td>
<th></th>
<td></td>
</tr>
<tr>
<td>onTitleClick</td>
<td>Function({eventKey, domEvent})</td>
<th></th>
<td></td>
</tr>
<tr>
<td>popupOffset</td>
<td>Array</td>
<th></th>
<td>The offset of the popup submenu, in an x, y coordinate array. e.g.: `[0,15]`</td>
</tr>
<tr>
<td>expandIcon</td>
<td>ReactNode | (props: SubMenuProps) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
<tr>
<td>itemIcon</td>
<td>ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
</tbody>
</table>
### Menu.Divider props
none
### Menu.ItemGroup props
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 100px;">name</th>
<th style="width: 50px;">type</th>
<th style="width: 50px;">default</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>ref</td>
<td>React.HTMLLIElement</td>
<td></td>
<td>get dom node</td>
</tr>
<tr>
<td>title</td>
<td>String|React.Element</td>
<th></th>
<td>title of item group</td>
</tr>
<tr>
<td>children</td>
<td>React.Element[]</td>
<th></th>
<td>MenuItems belonged to this group</td>
</tr>
</tbody>
</table>
## Development
```
npm install
npm start
```
## Example
http://localhost:8001/examples/index.md
online example: http://react-component.github.io/menu/examples/
## Test Case
```
npm test
npm run chrome-test
```
## Coverage
```
npm run coverage
```
open coverage/ dir
## License
@rc-component/menu is released under the MIT license.
+353
View File
@@ -0,0 +1,353 @@
@font-face {
font-family: 'FontAwesome';
src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot');
src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.woff') format('woff'), url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf') format('truetype'), url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
.rc-menu {
outline: none;
margin-bottom: 0;
padding-left: 0;
padding-right: 0;
list-style: none;
border: 1px solid #d9d9d9;
box-shadow: 0 0 4px #d9d9d9;
border-radius: 3px;
color: #666;
}
.rc-menu-rtl {
direction: rtl;
}
.rc-menu-hidden,
.rc-menu-submenu-hidden {
display: none;
}
.rc-menu-collapse {
overflow: hidden;
transition: height 0.3s ease-out;
}
.rc-menu-item-group-list {
margin: 0;
padding: 0;
}
.rc-menu-item-group-title {
color: #999;
line-height: 1.5;
padding: 8px 10px;
border-bottom: 1px solid #dedede;
}
.rc-menu-item-active,
.rc-menu-submenu-active > .rc-menu-submenu-title {
background-color: #eaf8fe;
}
.rc-menu-item-selected {
background-color: #eaf8fe;
transform: translateZ(0);
}
.rc-menu-submenu-selected {
background-color: #eaf8fe;
}
.rc-menu > li.rc-menu-submenu {
padding: 0;
}
.rc-menu-horizontal.rc-menu-sub,
.rc-menu-vertical.rc-menu-sub,
.rc-menu-vertical-left.rc-menu-sub,
.rc-menu-vertical-right.rc-menu-sub {
min-width: 160px;
margin-top: 0;
}
.rc-menu-item,
.rc-menu-submenu-title {
margin: 0;
position: relative;
display: block;
padding: 7px 7px 7px 16px;
white-space: nowrap;
}
.rc-menu-rtl .rc-menu-item,
.rc-menu-rtl .rc-menu-submenu-title {
padding: 7px 16px 7px 7px;
}
.rc-menu-item.rc-menu-item-disabled,
.rc-menu-submenu-title.rc-menu-item-disabled,
.rc-menu-item.rc-menu-submenu-disabled,
.rc-menu-submenu-title.rc-menu-submenu-disabled {
color: #777 !important;
}
.rc-menu-item {
display: flex;
align-items: center;
}
.rc-menu-item .rc-menu-item-extra {
margin-left: auto;
font-size: 14px;
}
.rc-menu-item-divider {
height: 1px;
margin: 1px 0;
overflow: hidden;
padding: 0;
line-height: 0;
background-color: #e5e5e5;
}
.rc-menu-submenu-popup {
position: absolute;
}
.rc-menu-submenu-popup .submenu-title-wrapper {
padding-right: 20px;
}
.rc-menu-submenu-rtl.rc-menu-submenu-popup .submenu-title-wrapper,
.rc-menu-submenu-rtl .rc-menu-submenu-popup .submenu-title-wrapper {
padding-right: 0;
padding-left: 20px;
}
.rc-menu-submenu > .rc-menu {
background-color: #fff;
}
.rc-menu .rc-menu-submenu-title .anticon,
.rc-menu .rc-menu-item .anticon {
width: 14px;
height: 14px;
margin-right: 8px;
top: -1px;
}
.rc-menu-rtl .rc-menu .rc-menu-submenu-title .anticon,
.rc-menu-rtl .rc-menu .rc-menu-item .anticon {
margin-right: 0;
margin-left: 8px;
}
.rc-menu-horizontal {
background-color: #f3f5f7;
border: none;
border-bottom: 1px solid #d9d9d9;
box-shadow: none;
white-space: nowrap;
overflow: hidden;
}
.rc-menu-horizontal > .rc-menu-item,
.rc-menu-horizontal > .rc-menu-submenu > .rc-menu-submenu-title {
padding: 15px 20px;
}
.rc-menu-horizontal > .rc-menu-submenu,
.rc-menu-horizontal > .rc-menu-item {
border-bottom: 2px solid transparent;
display: inline-block;
vertical-align: bottom;
}
.rc-menu-horizontal > .rc-menu-submenu-active,
.rc-menu-horizontal > .rc-menu-item-active {
border-bottom: 2px solid #2db7f5;
background-color: #f3f5f7;
color: #2baee9;
}
.rc-menu-horizontal:after {
content: '\20';
display: block;
height: 0;
clear: both;
}
.rc-menu-vertical,
.rc-menu-vertical-left,
.rc-menu-vertical-right,
.rc-menu-inline {
padding: 12px 0;
}
.rc-menu-vertical > .rc-menu-item,
.rc-menu-vertical-left > .rc-menu-item,
.rc-menu-vertical-right > .rc-menu-item,
.rc-menu-inline > .rc-menu-item,
.rc-menu-vertical > .rc-menu-submenu > .rc-menu-submenu-title,
.rc-menu-vertical-left > .rc-menu-submenu > .rc-menu-submenu-title,
.rc-menu-vertical-right > .rc-menu-submenu > .rc-menu-submenu-title,
.rc-menu-inline > .rc-menu-submenu > .rc-menu-submenu-title {
padding: 12px 8px 12px 24px;
}
.rc-menu-rtl.rc-menu-vertical > .rc-menu-item,
.rc-menu-rtl.rc-menu-vertical-left > .rc-menu-item,
.rc-menu-rtl.rc-menu-vertical-right > .rc-menu-item,
.rc-menu-rtl.rc-menu-inline > .rc-menu-item,
.rc-menu-rtl.rc-menu-vertical > .rc-menu-submenu > .rc-menu-submenu-title,
.rc-menu-rtl.rc-menu-vertical-left > .rc-menu-submenu > .rc-menu-submenu-title,
.rc-menu-rtl.rc-menu-vertical-right > .rc-menu-submenu > .rc-menu-submenu-title,
.rc-menu-rtl.rc-menu-inline > .rc-menu-submenu > .rc-menu-submenu-title {
padding: 12px 24px 12px 8px;
}
.rc-menu-vertical .rc-menu-submenu-arrow,
.rc-menu-vertical-left .rc-menu-submenu-arrow,
.rc-menu-vertical-right .rc-menu-submenu-arrow,
.rc-menu-inline .rc-menu-submenu-arrow {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
vertical-align: baseline;
text-align: center;
text-transform: none;
text-rendering: auto;
position: absolute;
right: 16px;
line-height: 1.5em;
}
.rc-menu-vertical .rc-menu-submenu-arrow:before,
.rc-menu-vertical-left .rc-menu-submenu-arrow:before,
.rc-menu-vertical-right .rc-menu-submenu-arrow:before,
.rc-menu-inline .rc-menu-submenu-arrow:before {
content: '\f0da';
}
.rc-menu-rtl.rc-menu-vertical .rc-menu-submenu-arrow:before,
.rc-menu-rtl.rc-menu-vertical-left .rc-menu-submenu-arrow:before,
.rc-menu-rtl.rc-menu-vertical-right .rc-menu-submenu-arrow:before,
.rc-menu-rtl.rc-menu-inline .rc-menu-submenu-arrow:before,
.rc-menu-submenu-rtl .rc-menu-vertical .rc-menu-submenu-arrow:before,
.rc-menu-submenu-rtl .rc-menu-vertical-left .rc-menu-submenu-arrow:before,
.rc-menu-submenu-rtl .rc-menu-vertical-right .rc-menu-submenu-arrow:before,
.rc-menu-submenu-rtl .rc-menu-inline .rc-menu-submenu-arrow:before {
content: '\f0d9';
}
.rc-menu-rtl.rc-menu-vertical .rc-menu-submenu-arrow,
.rc-menu-rtl.rc-menu-vertical-left .rc-menu-submenu-arrow,
.rc-menu-rtl.rc-menu-vertical-right .rc-menu-submenu-arrow,
.rc-menu-rtl.rc-menu-inline .rc-menu-submenu-arrow,
.rc-menu-submenu-rtl .rc-menu-vertical .rc-menu-submenu-arrow,
.rc-menu-submenu-rtl .rc-menu-vertical-left .rc-menu-submenu-arrow,
.rc-menu-submenu-rtl .rc-menu-vertical-right .rc-menu-submenu-arrow,
.rc-menu-submenu-rtl .rc-menu-inline .rc-menu-submenu-arrow {
right: auto;
left: 16px;
}
.rc-menu-inline .rc-menu-submenu-arrow {
transform: rotate(90deg);
transition: transform 0.3s;
}
.rc-menu-inline .rc-menu-submenu-open > .rc-menu-submenu-title .rc-menu-submenu-arrow {
transform: rotate(-90deg);
}
.rc-menu-vertical.rc-menu-sub,
.rc-menu-vertical-left.rc-menu-sub,
.rc-menu-vertical-right.rc-menu-sub {
padding: 0;
}
.rc-menu-submenu-rtl .rc-menu-vertical.rc-menu-sub,
.rc-menu-submenu-rtl .rc-menu-vertical-left.rc-menu-sub,
.rc-menu-submenu-rtl .rc-menu-vertical-right.rc-menu-sub {
direction: rtl;
}
.rc-menu-sub.rc-menu-inline {
padding: 0;
border: none;
border-radius: 0;
box-shadow: none;
}
.rc-menu-sub.rc-menu-inline > .rc-menu-item,
.rc-menu-sub.rc-menu-inline > .rc-menu-submenu > .rc-menu-submenu-title {
padding-top: 8px;
padding-bottom: 8px;
padding-right: 0;
}
.rc-menu-rtl .rc-menu-sub.rc-menu-inline > .rc-menu-item,
.rc-menu-rtl .rc-menu-sub.rc-menu-inline > .rc-menu-submenu > .rc-menu-submenu-title {
padding-left: 0;
}
.rc-menu-open-slide-up-enter,
.rc-menu-open-slide-up-appear {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-menu-open-slide-up-leave {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
.rc-menu-open-slide-up-enter.rc-menu-open-slide-up-enter-active,
.rc-menu-open-slide-up-appear.rc-menu-open-slide-up-appear-active {
animation-name: rcMenuOpenSlideUpIn;
animation-play-state: running;
}
.rc-menu-open-slide-up-leave.rc-menu-open-slide-up-leave-active {
animation-name: rcMenuOpenSlideUpOut;
animation-play-state: running;
}
@keyframes rcMenuOpenSlideUpIn {
0% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
}
@keyframes rcMenuOpenSlideUpOut {
0% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
}
.rc-menu-open-zoom-enter,
.rc-menu-open-zoom-appear {
opacity: 0;
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-menu-open-zoom-leave {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
.rc-menu-open-zoom-enter.rc-menu-open-zoom-enter-active,
.rc-menu-open-zoom-appear.rc-menu-open-zoom-appear-active {
animation-name: rcMenuOpenZoomIn;
animation-play-state: running;
}
.rc-menu-open-zoom-leave.rc-menu-open-zoom-leave-active {
animation-name: rcMenuOpenZoomOut;
animation-play-state: running;
}
.rc-menu-submenu-rtl.rc-menu-open-zoom-enter,
.rc-menu-submenu-rtl.rc-menu-open-zoom-appear,
.rc-menu-submenu-rtl.rc-menu-open-zoom-leave,
.rc-menu-submenu-rtl .rc-menu-open-zoom-enter,
.rc-menu-submenu-rtl .rc-menu-open-zoom-appear,
.rc-menu-submenu-rtl .rc-menu-open-zoom-leave {
transform-origin: top right !important;
}
@keyframes rcMenuOpenZoomIn {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@keyframes rcMenuOpenZoomOut {
0% {
transform: scale(1, 1);
}
100% {
opacity: 0;
transform: scale(0, 0);
}
}
+382
View File
@@ -0,0 +1,382 @@
@menuPrefixCls: rc-menu;
@font-face {
font-family: 'FontAwesome';
src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot');
src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix')
format('embedded-opentype'),
url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.woff')
format('woff'),
url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf')
format('truetype'),
url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?#fontawesomeregular')
format('svg');
font-weight: normal;
font-style: normal;
}
.@{menuPrefixCls} {
outline: none;
margin-bottom: 0;
padding-left: 0; // Override default ul/ol
padding-right: 0; // Override default ul/ol in RTL direction
list-style: none;
border: 1px solid #d9d9d9;
box-shadow: 0 0 4px #d9d9d9;
border-radius: 3px;
color: #666;
&-rtl {
direction: rtl;
}
&-hidden,
&-submenu-hidden {
display: none;
}
&-collapse {
overflow: hidden;
// &-active {
transition: height 0.3s ease-out;
// }
}
&-item-group-list {
margin: 0;
padding: 0;
}
&-item-group-title {
color: #999;
line-height: 1.5;
padding: 8px 10px;
border-bottom: 1px solid #dedede;
}
&-item-active,
&-submenu-active > &-submenu-title {
background-color: #eaf8fe;
}
&-item-selected {
background-color: #eaf8fe;
// fix chrome render bug
transform: translateZ(0);
}
&-submenu-selected {
background-color: #eaf8fe;
}
& > li&-submenu {
padding: 0;
}
&-horizontal&-sub,
&-vertical&-sub,
&-vertical-left&-sub,
&-vertical-right&-sub {
min-width: 160px;
margin-top: 0;
}
&-item,
&-submenu-title {
margin: 0;
position: relative;
display: block;
padding: 7px 7px 7px 16px;
white-space: nowrap;
.@{menuPrefixCls}-rtl & {
padding: 7px 16px 7px 7px;
}
// Disabled state sets text to gray and nukes hover/tab effects
&.@{menuPrefixCls}-item-disabled,
&.@{menuPrefixCls}-submenu-disabled {
color: #777 !important;
}
}
&-item {
display: flex;
align-items: center;
.@{menuPrefixCls}-item-extra {
margin-left: auto;
font-size: 14px;
}
}
&-item-divider {
height: 1px;
margin: 1px 0;
overflow: hidden;
padding: 0;
line-height: 0;
background-color: #e5e5e5;
}
&-submenu {
&-popup {
position: absolute;
.submenu-title-wrapper {
padding-right: 20px;
.@{menuPrefixCls}-submenu-rtl&,
.@{menuPrefixCls}-submenu-rtl & {
padding-right: 0;
padding-left: 20px;
}
}
}
> .@{menuPrefixCls} {
background-color: #fff;
}
}
.@{menuPrefixCls}-submenu-title,
.@{menuPrefixCls}-item {
.anticon {
width: 14px;
height: 14px;
margin-right: 8px;
top: -1px;
.@{menuPrefixCls}-rtl & {
margin-right: 0;
margin-left: 8px;
}
}
}
&-horizontal {
background-color: #f3f5f7;
border: none;
border-bottom: 1px solid #d9d9d9;
box-shadow: none;
white-space: nowrap;
overflow: hidden;
& > .@{menuPrefixCls}-item,
& > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title {
padding: 15px 20px;
}
& > .@{menuPrefixCls}-submenu,
& > .@{menuPrefixCls}-item {
border-bottom: 2px solid transparent;
display: inline-block;
vertical-align: bottom;
&-active {
border-bottom: 2px solid #2db7f5;
background-color: #f3f5f7;
color: #2baee9;
}
}
&:after {
content: '\20';
display: block;
height: 0;
clear: both;
}
}
&-vertical,
&-vertical-left,
&-vertical-right,
&-inline {
padding: 12px 0;
& > .@{menuPrefixCls}-item,
& > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title {
padding: 12px 8px 12px 24px;
.@{menuPrefixCls}-rtl& {
padding: 12px 24px 12px 8px;
}
}
.@{menuPrefixCls}-submenu-arrow {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
vertical-align: baseline;
text-align: center;
text-transform: none;
text-rendering: auto;
position: absolute;
right: 16px;
line-height: 1.5em;
&:before {
content: '\f0da';
.@{menuPrefixCls}-rtl&,
.@{menuPrefixCls}-submenu-rtl & {
content: '\f0d9';
}
}
.@{menuPrefixCls}-rtl&,
.@{menuPrefixCls}-submenu-rtl & {
right: auto;
left: 16px;
}
}
}
&-inline {
.@{menuPrefixCls}-submenu-arrow {
transform: rotate(90deg);
transition: transform 0.3s;
}
& .@{menuPrefixCls}-submenu-open > .@{menuPrefixCls}-submenu-title {
.@{menuPrefixCls}-submenu-arrow {
transform: rotate(-90deg);
}
}
}
&-vertical&-sub,
&-vertical-left&-sub,
&-vertical-right&-sub {
padding: 0;
.@{menuPrefixCls}-submenu-rtl & {
direction: rtl;
}
}
&-sub&-inline {
padding: 0;
border: none;
border-radius: 0;
box-shadow: none;
& > .@{menuPrefixCls}-item,
& > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title {
padding-top: 8px;
padding-bottom: 8px;
padding-right: 0;
.@{menuPrefixCls}-rtl & {
padding-left: 0;
}
}
}
.effect() {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
}
&-open {
&-slide-up-enter,
&-slide-up-appear {
.effect();
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
&-slide-up-leave {
.effect();
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
&-slide-up-enter&-slide-up-enter-active,
&-slide-up-appear&-slide-up-appear-active {
animation-name: rcMenuOpenSlideUpIn;
animation-play-state: running;
}
&-slide-up-leave&-slide-up-leave-active {
animation-name: rcMenuOpenSlideUpOut;
animation-play-state: running;
}
@keyframes rcMenuOpenSlideUpIn {
0% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
}
@keyframes rcMenuOpenSlideUpOut {
0% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
}
&-zoom-enter,
&-zoom-appear {
opacity: 0;
.effect();
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
&-zoom-leave {
.effect();
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
&-zoom-enter&-zoom-enter-active,
&-zoom-appear&-zoom-appear-active {
animation-name: rcMenuOpenZoomIn;
animation-play-state: running;
}
&-zoom-leave&-zoom-leave-active {
animation-name: rcMenuOpenZoomOut;
animation-play-state: running;
}
&-zoom-enter,
&-zoom-appear,
&-zoom-leave {
.@{menuPrefixCls}-submenu-rtl&,
.@{menuPrefixCls}-submenu-rtl & {
transform-origin: top right !important;
}
}
@keyframes rcMenuOpenZoomIn {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@keyframes rcMenuOpenZoomOut {
0% {
transform: scale(1, 1);
}
100% {
opacity: 0;
transform: scale(0, 0);
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
@menuPrefixCls: ~'rc-menu';
.@{menuPrefixCls} {
&:focus-visible {
box-shadow: 0 0 5px green;
}
&-horizontal {
display: flex;
flex-wrap: nowrap;
}
&-submenu {
&-hidden {
display: none;
}
}
&-overflow-item {
flex: none;
}
}
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
@@ -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;
@@ -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);
}));
}
@@ -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;
@@ -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);
}
@@ -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;
@@ -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
View File
@@ -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
View File
@@ -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;
@@ -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;
@@ -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);
}
@@ -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;
@@ -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);
}
@@ -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>;
@@ -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);
@@ -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;
@@ -0,0 +1,3 @@
import * as React from 'react';
const PrivateContext = /*#__PURE__*/React.createContext({});
export default PrivateContext;
@@ -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>;
@@ -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);
};
}
@@ -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
View File
@@ -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;
}
@@ -0,0 +1,2 @@
import * as React from 'react';
export default function useDirectionStyle(level: number): React.CSSProperties;
@@ -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
};
}
@@ -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>;
};
@@ -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
};
}
@@ -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;
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
export {};
+117
View File
@@ -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
View File
@@ -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;
@@ -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
View File
@@ -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;
});
}
@@ -0,0 +1,2 @@
import type { CSSMotionProps } from '@rc-component/motion';
export declare function getMotion(mode: string, motion?: CSSMotionProps, defaultMotions?: Record<string, CSSMotionProps>): CSSMotionProps;
@@ -0,0 +1,9 @@
export function getMotion(mode, motion, defaultMotions) {
if (motion) {
return motion;
}
if (defaultMotions) {
return defaultMotions[mode] || defaultMotions.other;
}
return undefined;
}
@@ -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
View File
@@ -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);
}
@@ -0,0 +1 @@
export declare function nextSlice(callback: () => void): void;
+4
View File
@@ -0,0 +1,4 @@
export function nextSlice(callback) {
/* istanbul ignore next */
Promise.resolve().then(callback);
}
@@ -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
View File
@@ -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;
}
+4
View File
@@ -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;
+29
View File
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = Divider;
var React = _interopRequireWildcard(require("react"));
var _clsx = require("clsx");
var _MenuContext = require("./context/MenuContext");
var _PathContext = require("./context/PathContext");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function Divider({
className,
style
}) {
const {
prefixCls
} = React.useContext(_MenuContext.MenuContext);
const measure = (0, _PathContext.useMeasure)();
if (measure) {
return null;
}
return /*#__PURE__*/React.createElement("li", {
role: "separator",
className: (0, _clsx.clsx)(`${prefixCls}-item-divider`, className),
style: style
});
}
+9
View File
@@ -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>>;
+28
View File
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = Icon;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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
View File
@@ -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;
+451
View File
@@ -0,0 +1,451 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _overflow = _interopRequireDefault(require("@rc-component/overflow"));
var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState"));
var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId"));
var _isEqual = _interopRequireDefault(require("@rc-component/util/lib/isEqual"));
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _react = _interopRequireWildcard(require("react"));
var React = _react;
var _reactDom = require("react-dom");
var _IdContext = require("./context/IdContext");
var _MenuContext = _interopRequireDefault(require("./context/MenuContext"));
var _PathContext = require("./context/PathContext");
var _PrivateContext = _interopRequireDefault(require("./context/PrivateContext"));
var _useAccessibility = require("./hooks/useAccessibility");
var _useKeyRecords = _interopRequireWildcard(require("./hooks/useKeyRecords"));
var _useMemoCallback = _interopRequireDefault(require("./hooks/useMemoCallback"));
var _MenuItem = _interopRequireDefault(require("./MenuItem"));
var _SubMenu = _interopRequireDefault(require("./SubMenu"));
var _nodeUtil = require("./utils/nodeUtil");
var _warnUtil = require("./utils/warnUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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); }
/**
* 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(() => [(0, _nodeUtil.parseItems)(children, items, EMPTY_LIST, _internalComponents, prefixCls), (0, _nodeUtil.parseItems)(children, items, EMPTY_LIST, {}, prefixCls)], [children, items, _internalComponents]);
const [mounted, setMounted] = React.useState(false);
const containerRef = React.useRef();
const uuid = (0, _useId.default)(id ? `rc-menu-uuid-${id}` : 'rc-menu-uuid');
const isRtl = direction === 'rtl';
// ========================= Warn =========================
if (process.env.NODE_ENV !== 'production') {
(0, _warning.default)(!openAnimation && !openTransitionName, '`openAnimation` and `openTransitionName` is removed. Please use `motion` or `defaultMotion` instead.');
}
// ========================= Open =========================
const [innerOpenKeys, setMergedOpenKeys] = (0, _useControlledState.default)(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) {
(0, _reactDom.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
} = (0, _useKeyRecords.default)();
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] = (0, _useControlledState.default)(activeKey || defaultActiveFirst && childList[0]?.key, activeKey);
const onActive = (0, _useMemoCallback.default)(key => {
setMergedActiveKey(key);
});
const onInactive = (0, _useMemoCallback.default)(() => {
setMergedActiveKey(undefined);
});
(0, _react.useImperativeHandle)(ref, () => {
return {
list: containerRef.current,
focus: options => {
const keys = getKeys();
const {
elements,
key2element,
element2key
} = (0, _useAccessibility.refreshElements)(keys, uuid);
const focusableElements = (0, _useAccessibility.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
} = (0, _useAccessibility.refreshElements)(keys, uuid);
return key2element.get(itemKey) || null;
}
};
});
// ======================== Select ========================
// >>>>> Select keys
const [internalSelectKeys, setMergedSelectKeys] = (0, _useControlledState.default)(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 = (0, _useMemoCallback.default)(info => {
onClick?.((0, _warnUtil.warnItemProp)(info));
triggerSelection(info);
});
const onInternalOpenChange = (0, _useMemoCallback.default)((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 (!(0, _isEqual.default)(mergedOpenKeys, newOpenKeys, true)) {
triggerOpenKeys(newOpenKeys, true);
}
});
// ==================== Accessibility =====================
const triggerAccessibilityOpen = (key, open) => {
const nextOpen = open ?? !mergedOpenKeys.includes(key);
onInternalOpenChange(key, nextOpen);
};
const onInternalKeyDown = (0, _useAccessibility.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(_MenuContext.default, {
key: child.key,
overflowDisabled: index > lastVisibleIndex,
classNames: menuClassNames,
styles: styles
}, child));
// >>>>> Container
const container = /*#__PURE__*/React.createElement(_overflow.default, _extends({
id: id,
ref: containerRef,
prefixCls: `${prefixCls}-overflow`,
component: "ul",
itemComponent: _MenuItem.default,
className: (0, _clsx.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.default, {
eventKey: _useKeyRecords.OVERFLOW_KEY,
title: overflowedIndicator,
disabled: allVisible,
internalPopupClose: len === 0,
popupClassName: overflowedIndicatorPopupClassName
}, originOmitItems);
},
maxCount: internalMode !== 'horizontal' || disabledOverflow ? _overflow.default.INVALIDATE : _overflow.default.RESPONSIVE,
ssr: "full",
"data-menu-list": true,
onVisibleChange: newLastIndex => {
setLastVisibleIndex(newLastIndex);
},
onKeyDown: onInternalKeyDown
}, restProps));
// >>>>> Render
return /*#__PURE__*/React.createElement(_PrivateContext.default.Provider, {
value: privateContext
}, /*#__PURE__*/React.createElement(_IdContext.IdContext.Provider, {
value: uuid
}, /*#__PURE__*/React.createElement(_MenuContext.default, {
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(_PathContext.PathUserContext.Provider, {
value: pathUserContext
}, container), /*#__PURE__*/React.createElement("div", {
style: {
display: 'none'
},
"aria-hidden": true
}, /*#__PURE__*/React.createElement(_PathContext.PathRegisterContext.Provider, {
value: registerPathContext
}, measureChildList)))));
});
var _default = exports.default = Menu;
+13
View File
@@ -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;
+224
View File
@@ -0,0 +1,224 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _overflow = _interopRequireDefault(require("@rc-component/overflow"));
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
var _omit = _interopRequireDefault(require("@rc-component/util/lib/omit"));
var _ref = require("@rc-component/util/lib/ref");
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var React = _interopRequireWildcard(require("react"));
var _IdContext = require("./context/IdContext");
var _MenuContext = require("./context/MenuContext");
var _PathContext = require("./context/PathContext");
var _PrivateContext = _interopRequireDefault(require("./context/PrivateContext"));
var _useActive = _interopRequireDefault(require("./hooks/useActive"));
var _useDirectionStyle = _interopRequireDefault(require("./hooks/useDirectionStyle"));
var _Icon = _interopRequireDefault(require("./Icon"));
var _warnUtil = require("./utils/warnUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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); }
// 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 = (0, _omit.default)(restProps, ['eventKey', 'popupClassName', 'popupOffset', 'onTitleClick']);
(0, _warning.default)(!attribute, '`attribute` of Menu.Item is deprecated. Please pass attribute directly.');
return /*#__PURE__*/React.createElement(_overflow.default.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 = (0, _IdContext.useMenuId)(eventKey);
const {
prefixCls,
onItemClick,
disabled: contextDisabled,
overflowDisabled,
// Icon
itemIcon: contextItemIcon,
// Select
selectedKeys,
// Active
onActive
} = React.useContext(_MenuContext.MenuContext);
const {
_internalRenderMenuItem
} = React.useContext(_PrivateContext.default);
const itemCls = `${prefixCls}-item`;
const legacyMenuItemRef = React.useRef();
const elementRef = React.useRef();
const mergedDisabled = contextDisabled || disabled;
const mergedEleRef = (0, _ref.useComposeRef)(ref, elementRef);
const connectedKeys = (0, _PathContext.useFullPath)(eventKey);
// ================================ Warn ================================
if (process.env.NODE_ENV !== 'production' && warnKey) {
(0, _warning.default)(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
} = (0, _useActive.default)(eventKey, mergedDisabled, onMouseEnter, onMouseLeave);
// ============================ Select ============================
const selected = selectedKeys.includes(eventKey);
// ======================== DirectionStyle ========================
const directionStyle = (0, _useDirectionStyle.default)(connectedKeys.length);
// ============================ Events ============================
const onInternalClick = e => {
if (mergedDisabled) {
return;
}
const info = getEventInfo(e);
onClick?.((0, _warnUtil.warnItemProp)(info));
onItemClick(info);
};
const onInternalKeyDown = e => {
onKeyDown?.(e);
if (e.which === _KeyCode.default.ENTER) {
const info = getEventInfo(e);
// Legacy. Key will also trigger click event
onClick?.((0, _warnUtil.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
}, (0, _omit.default)(restProps, ['extra']), activeProps, optionRoleProps, {
component: "li",
"aria-disabled": disabled,
style: {
...directionStyle,
...style
},
className: (0, _clsx.clsx)(itemCls, {
[`${itemCls}-active`]: active,
[`${itemCls}-selected`]: selected,
[`${itemCls}-disabled`]: mergedDisabled
}, className),
onClick: onInternalClick,
onKeyDown: onInternalKeyDown,
onFocus: onInternalFocus
}), children, /*#__PURE__*/React.createElement(_Icon.default, {
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 = (0, _PathContext.useMeasure)();
const connectedKeyPath = (0, _PathContext.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
}));
}
var _default = exports.default = /*#__PURE__*/React.forwardRef(MenuItem);
+12
View File
@@ -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;
+66
View File
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _omit = _interopRequireDefault(require("@rc-component/util/lib/omit"));
var React = _interopRequireWildcard(require("react"));
var _MenuContext = require("./context/MenuContext");
var _PathContext = require("./context/PathContext");
var _commonUtil = require("./utils/commonUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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); }
const InternalMenuItemGroup = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
className,
title,
eventKey,
children,
...restProps
} = props;
const {
prefixCls,
classNames: menuClassNames,
styles
} = React.useContext(_MenuContext.MenuContext);
const groupPrefixCls = `${prefixCls}-item-group`;
return /*#__PURE__*/React.createElement("li", _extends({
ref: ref,
role: "presentation"
}, restProps, {
onClick: e => e.stopPropagation(),
className: (0, _clsx.clsx)(groupPrefixCls, className)
}), /*#__PURE__*/React.createElement("div", {
role: "presentation",
className: (0, _clsx.clsx)(`${groupPrefixCls}-title`, menuClassNames?.listTitle),
style: styles?.listTitle,
title: typeof title === 'string' ? title : undefined
}, title), /*#__PURE__*/React.createElement("ul", {
role: "group",
className: (0, _clsx.clsx)(`${groupPrefixCls}-list`, menuClassNames?.list),
style: styles?.list
}, children));
});
const MenuItemGroup = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
eventKey,
children
} = props;
const connectedKeyPath = (0, _PathContext.useFullPath)(eventKey);
const childList = (0, _commonUtil.parseChildren)(children, connectedKeyPath);
const measure = (0, _PathContext.useMeasure)();
if (measure) {
return childList;
}
return /*#__PURE__*/React.createElement(InternalMenuItemGroup, _extends({
ref: ref
}, (0, _omit.default)(props, ['warnKey'])), childList);
});
if (process.env.NODE_ENV !== 'production') {
MenuItemGroup.displayName = 'MenuItemGroup';
}
var _default = exports.default = MenuItemGroup;
@@ -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;
@@ -0,0 +1,88 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = InlineSubMenuList;
var React = _interopRequireWildcard(require("react"));
var _motion = _interopRequireDefault(require("@rc-component/motion"));
var _motionUtil = require("../utils/motionUtil");
var _MenuContext = _interopRequireWildcard(require("../context/MenuContext"));
var _SubMenuList = _interopRequireDefault(require("./SubMenuList"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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); }
function InlineSubMenuList({
id,
open,
keyPath,
children
}) {
const fixedMode = 'inline';
const {
prefixCls,
forceSubMenuRender,
motion,
defaultMotions,
mode
} = React.useContext(_MenuContext.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 = {
...(0, _motionUtil.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(_MenuContext.default, {
mode: fixedMode,
locked: !sameModeRef.current
}, /*#__PURE__*/React.createElement(_motion.default, _extends({
visible: mergedOpen
}, mergedMotion, {
forceRender: forceSubMenuRender,
removeOnLeave: false,
leavedClassName: `${prefixCls}-hidden`
}), ({
className: motionClassName,
style: motionStyle
}) => {
return /*#__PURE__*/React.createElement(_SubMenuList.default, {
id: id,
className: motionClassName,
style: motionStyle
}, children);
}));
}
@@ -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;
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = PopupTrigger;
var React = _interopRequireWildcard(require("react"));
var _trigger = _interopRequireDefault(require("@rc-component/trigger"));
var _clsx = require("clsx");
var _raf = _interopRequireDefault(require("@rc-component/util/lib/raf"));
var _MenuContext = require("../context/MenuContext");
var _placements = require("../placements");
var _motionUtil = require("../utils/motionUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const popupPlacementMap = {
horizontal: 'bottomLeft',
vertical: 'rightTop',
'vertical-left': 'rightTop',
'vertical-right': 'leftTop'
};
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.MenuContext);
const [innerVisible, setInnerVisible] = React.useState(false);
const placement = rtl ? {
..._placements.placementsRtl,
...builtinPlacements
} : {
..._placements.placements,
...builtinPlacements
};
const popupPlacement = popupPlacementMap[mode];
const targetMotion = (0, _motionUtil.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 = (0, _raf.default)(() => {
setInnerVisible(visible);
});
return () => {
_raf.default.cancel(visibleRef.current);
};
}, [visible]);
return /*#__PURE__*/React.createElement(_trigger.default, {
prefixCls: prefixCls,
popupClassName: (0, _clsx.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);
}
@@ -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;
@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _clsx = require("clsx");
var _MenuContext = require("../context/MenuContext");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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); }
const InternalSubMenuList = ({
className,
children,
...restProps
}, ref) => {
const {
prefixCls,
mode,
rtl
} = React.useContext(_MenuContext.MenuContext);
return /*#__PURE__*/React.createElement("ul", _extends({
className: (0, _clsx.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';
}
var _default = exports.default = SubMenuList;
+18
View File
@@ -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;
+337
View File
@@ -0,0 +1,337 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _clsx = require("clsx");
var _overflow = _interopRequireDefault(require("@rc-component/overflow"));
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _SubMenuList = _interopRequireDefault(require("./SubMenuList"));
var _commonUtil = require("../utils/commonUtil");
var _MenuContext = _interopRequireWildcard(require("../context/MenuContext"));
var _useMemoCallback = _interopRequireDefault(require("../hooks/useMemoCallback"));
var _PopupTrigger = _interopRequireDefault(require("./PopupTrigger"));
var _Icon = _interopRequireDefault(require("../Icon"));
var _useActive = _interopRequireDefault(require("../hooks/useActive"));
var _warnUtil = require("../utils/warnUtil");
var _useDirectionStyle = _interopRequireDefault(require("../hooks/useDirectionStyle"));
var _InlineSubMenuList = _interopRequireDefault(require("./InlineSubMenuList"));
var _PathContext = require("../context/PathContext");
var _IdContext = require("../context/IdContext");
var _PrivateContext = _interopRequireDefault(require("../context/PrivateContext"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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); }
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 = (0, _IdContext.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.MenuContext);
const {
_internalRenderSubMenuItem
} = React.useContext(_PrivateContext.default);
const {
isSubPathKey
} = React.useContext(_PathContext.PathUserContext);
const connectedPath = (0, _PathContext.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) {
(0, _warning.default)(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
} = (0, _useActive.default)(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 = (0, _useDirectionStyle.default)(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 = (0, _useMemoCallback.default)(info => {
onClick?.((0, _warnUtil.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.default, {
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(_MenuContext.default, {
classNames: menuClassNames,
styles: styles,
mode: popupContentTriggerMode === 'horizontal' ? 'vertical' : popupContentTriggerMode
}, /*#__PURE__*/React.createElement(_SubMenuList.default, {
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.default, {
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.default.Item, _extends({
ref: ref,
role: "none"
}, restProps, {
component: "li",
style: style,
className: (0, _clsx.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.default, {
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(_MenuContext.default, {
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 = (0, _PathContext.useFullPath)(eventKey);
const childList = (0, _commonUtil.parseChildren)(children, connectedKeyPath);
// ==================== Record KeyPath ====================
const measure = (0, _PathContext.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(_PathContext.PathTrackerContext.Provider, {
value: connectedKeyPath
}, renderNode);
});
if (process.env.NODE_ENV !== 'production') {
SubMenu.displayName = 'SubMenu';
}
var _default = exports.default = SubMenu;
@@ -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;
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.IdContext = void 0;
exports.getMenuId = getMenuId;
exports.useMenuId = useMenuId;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const IdContext = exports.IdContext = /*#__PURE__*/React.createContext(null);
function getMenuId(uuid, eventKey) {
return `${uuid}-${eventKey}`;
}
/**
* Get `data-menu-id`
*/
function useMenuId(eventKey) {
const id = React.useContext(IdContext);
return getMenuId(id, eventKey);
}
@@ -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;
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MenuContext = void 0;
exports.default = InheritableContextProvider;
var React = _interopRequireWildcard(require("react"));
var _useMemo = _interopRequireDefault(require("@rc-component/util/lib/hooks/useMemo"));
var _isEqual = _interopRequireDefault(require("@rc-component/util/lib/isEqual"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const MenuContext = exports.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;
}
function InheritableContextProvider({
children,
locked,
...restProps
}) {
const context = React.useContext(MenuContext);
const inheritableContext = (0, _useMemo.default)(() => mergeProps(context, restProps), [context, restProps], (prev, next) => !locked && (prev[0] !== next[0] || !(0, _isEqual.default)(prev[1], next[1], true)));
return /*#__PURE__*/React.createElement(MenuContext.Provider, {
value: inheritableContext
}, children);
}
@@ -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>;
@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PathUserContext = exports.PathTrackerContext = exports.PathRegisterContext = void 0;
exports.useFullPath = useFullPath;
exports.useMeasure = useMeasure;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const EmptyList = [];
// ========================= Path Register =========================
const PathRegisterContext = exports.PathRegisterContext = /*#__PURE__*/React.createContext(null);
function useMeasure() {
return React.useContext(PathRegisterContext);
}
// ========================= Path Tracker ==========================
const PathTrackerContext = exports.PathTrackerContext = /*#__PURE__*/React.createContext(EmptyList);
function useFullPath(eventKey) {
const parentKeyPath = React.useContext(PathTrackerContext);
return React.useMemo(() => eventKey !== undefined ? [...parentKeyPath, eventKey] : parentKeyPath, [parentKeyPath, eventKey]);
}
// =========================== Path User ===========================
const PathUserContext = exports.PathUserContext = /*#__PURE__*/React.createContext(null);
@@ -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;
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const PrivateContext = /*#__PURE__*/React.createContext({});
var _default = exports.default = PrivateContext;
@@ -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>;
@@ -0,0 +1,293 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getFocusableElements = getFocusableElements;
exports.refreshElements = void 0;
exports.useAccessibility = useAccessibility;
var _focus = require("@rc-component/util/lib/Dom/focus");
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
var _raf = _interopRequireDefault(require("@rc-component/util/lib/raf"));
var React = _interopRequireWildcard(require("react"));
var _IdContext = require("../context/IdContext");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// destruct to reduce minify size
const {
LEFT,
RIGHT,
UP,
DOWN,
ENTER,
ESC,
HOME,
END
} = _KeyCode.default;
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
*/
function getFocusableElements(container, elements) {
const list = (0, _focus.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];
}
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='${(0, _IdContext.getMenuId)(id, key)}']`);
if (element) {
elements.add(element);
element2key.set(element, key);
key2element.set(key, element);
}
});
return {
elements,
key2element,
element2key
};
};
exports.refreshElements = refreshElements;
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.default.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 = (0, _raf.default)(() => {
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 = (0, _raf.default)(() => {
// 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);
};
}
@@ -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 {};
+40
View File
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useActive;
var React = _interopRequireWildcard(require("react"));
var _MenuContext = require("../context/MenuContext");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function useActive(eventKey, disabled, onMouseEnter, onMouseLeave) {
const {
// Active
activeKey,
onActive,
onInactive
} = React.useContext(_MenuContext.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;
}
@@ -0,0 +1,2 @@
import * as React from 'react';
export default function useDirectionStyle(level: number): React.CSSProperties;
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useDirectionStyle;
var React = _interopRequireWildcard(require("react"));
var _MenuContext = require("../context/MenuContext");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function useDirectionStyle(level) {
const {
mode,
rtl,
inlineIndent
} = React.useContext(_MenuContext.MenuContext);
if (mode !== 'inline') {
return null;
}
const len = level;
return rtl ? {
paddingRight: len * inlineIndent
} : {
paddingLeft: len * inlineIndent
};
}
@@ -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>;
};
@@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.OVERFLOW_KEY = void 0;
exports.default = useKeyRecords;
var _react = _interopRequireWildcard(require("react"));
var React = _react;
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _timeUtil = require("../utils/timeUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const PATH_SPLIT = '__RC_UTIL_PATH_SPLIT__';
const getPathStr = keyPath => keyPath.join(PATH_SPLIT);
const getPathKeys = keyPathStr => keyPathStr.split(PATH_SPLIT);
const OVERFLOW_KEY = exports.OVERFLOW_KEY = 'rc-menu-more';
function useKeyRecords() {
const [, internalForceUpdate] = React.useState({});
const key2pathRef = (0, _react.useRef)(new Map());
const path2keyRef = (0, _react.useRef)(new Map());
const [overflowKeys, setOverflowKeys] = React.useState([]);
const updateRef = (0, _react.useRef)(0);
const destroyRef = (0, _react.useRef)(false);
const forceUpdate = () => {
if (!destroyRef.current) {
internalForceUpdate({});
}
};
const registerPath = (0, _react.useCallback)((key, keyPath) => {
// Warning for invalidate or duplicated `key`
if (process.env.NODE_ENV !== 'production') {
(0, _warning.default)(!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;
(0, _timeUtil.nextSlice)(() => {
if (id === updateRef.current) {
forceUpdate();
}
});
}, []);
const unregisterPath = (0, _react.useCallback)((key, keyPath) => {
const connectedPath = getPathStr(keyPath);
path2keyRef.current.delete(connectedPath);
key2pathRef.current.delete(key);
}, []);
const refreshOverflowKeys = (0, _react.useCallback)(keys => {
setOverflowKeys(keys);
}, []);
const getKeyPath = (0, _react.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 = (0, _react.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 = (0, _react.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
};
}
@@ -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;
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useMemoCallback;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* Cache callback function that always return same ref instead.
* This is used for context optimization.
*/
function useMemoCallback(func) {
const funRef = React.useRef(func);
funRef.current = func;
const callback = React.useCallback((...args) => funRef.current?.(...args), []);
return func ? callback : undefined;
}
+23
View File
@@ -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;
+61
View File
@@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "Divider", {
enumerable: true,
get: function () {
return _Divider.default;
}
});
Object.defineProperty(exports, "Item", {
enumerable: true,
get: function () {
return _MenuItem.default;
}
});
Object.defineProperty(exports, "ItemGroup", {
enumerable: true,
get: function () {
return _MenuItemGroup.default;
}
});
Object.defineProperty(exports, "MenuItem", {
enumerable: true,
get: function () {
return _MenuItem.default;
}
});
Object.defineProperty(exports, "MenuItemGroup", {
enumerable: true,
get: function () {
return _MenuItemGroup.default;
}
});
Object.defineProperty(exports, "SubMenu", {
enumerable: true,
get: function () {
return _SubMenu.default;
}
});
exports.default = void 0;
Object.defineProperty(exports, "useFullPath", {
enumerable: true,
get: function () {
return _PathContext.useFullPath;
}
});
var _Menu = _interopRequireDefault(require("./Menu"));
var _MenuItem = _interopRequireDefault(require("./MenuItem"));
var _SubMenu = _interopRequireDefault(require("./SubMenu"));
var _MenuItemGroup = _interopRequireDefault(require("./MenuItemGroup"));
var _PathContext = require("./context/PathContext");
var _Divider = _interopRequireDefault(require("./Divider"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const ExportMenu = _Menu.default;
ExportMenu.Item = _MenuItem.default;
ExportMenu.SubMenu = _SubMenu.default;
ExportMenu.ItemGroup = _MenuItemGroup.default;
ExportMenu.Divider = _Divider.default;
var _default = exports.default = ExportMenu;
+94
View File
@@ -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 {};
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
+117
View File
@@ -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;
+79
View File
@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.placementsRtl = exports.placements = exports.default = void 0;
const autoAdjustOverflow = {
adjustX: 1,
adjustY: 1
};
const placements = exports.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
}
};
const placementsRtl = exports.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
}
};
var _default = exports.default = placements;
@@ -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>>[];

Some files were not shown because too many files have changed in this diff Show More