1
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
|
||||
|
||||
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.
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
# @rc-component/trigger
|
||||
|
||||
React Trigger Component
|
||||
|
||||
[![NPM version][npm-image]][npm-url]
|
||||
[![npm download][download-image]][download-url]
|
||||
[![build status][github-actions-image]][github-actions-url]
|
||||
[![Test coverage][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/trigger.svg?style=flat-square
|
||||
[npm-url]: https://npmjs.org/package/@rc-component/trigger
|
||||
[github-actions-image]: https://github.com/react-component/trigger/actions/workflows/main.yml/badge.svg
|
||||
[github-actions-url]: https://github.com/react-component/trigger/actions/workflows/main.yml
|
||||
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/trigger/master.svg?style=flat-square
|
||||
[codecov-url]: https://codecov.io/gh/react-component/trigger/branch/master
|
||||
[david-url]: https://david-dm.org/react-component/trigger
|
||||
[david-image]: https://david-dm.org/react-component/trigger/status.svg?style=flat-square
|
||||
[david-dev-url]: https://david-dm.org/react-component/trigger?type=dev
|
||||
[david-dev-image]: https://david-dm.org/react-component/trigger/dev-status.svg?style=flat-square
|
||||
[download-image]: https://img.shields.io/npm/dm/@rc-component/trigger.svg?style=flat-square
|
||||
[download-url]: https://npmjs.org/package/@rc-component/trigger
|
||||
[bundlephobia-url]: https://bundlephobia.com/result?p=@rc-component/trigger
|
||||
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/trigger
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
|
||||
## Install
|
||||
|
||||
[](https://npmjs.org/package/@rc-component/trigger)
|
||||
|
||||
## Usage
|
||||
|
||||
Include the default [styling](https://github.com/react-component/trigger/blob/master/assets/index.less#L4:L11) and then:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Trigger from '@rc-component/trigger';
|
||||
|
||||
ReactDOM.render(
|
||||
<Trigger
|
||||
action={['click']}
|
||||
popup={<span>popup</span>}
|
||||
popupAlign={{
|
||||
points: ['tl', 'bl'],
|
||||
offset: [0, 3],
|
||||
}}
|
||||
>
|
||||
<a href="#">hover</a>
|
||||
</Trigger>,
|
||||
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 |
|
||||
|
||||
## Example
|
||||
|
||||
http://localhost:9001
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### 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>alignPoint</td>
|
||||
<td>bool</td>
|
||||
<td>false</td>
|
||||
<td>Popup will align with mouse position (support action of 'click', 'hover' and 'contextMenu')</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popupClassName</td>
|
||||
<td>string</td>
|
||||
<td></td>
|
||||
<td>additional className added to popup</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>forceRender</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>whether render popup before first show</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>destroyPopupOnHide</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>whether destroy popup when hide</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>getPopupClassNameFromAlign</td>
|
||||
<td>getPopupClassNameFromAlign(align: Object):String</td>
|
||||
<td></td>
|
||||
<td>additional className added to popup according to align</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>action</td>
|
||||
<td>string[]</td>
|
||||
<td>['hover']</td>
|
||||
<td>which actions cause popup shown. enum of 'hover','click','focus','contextMenu'</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mouseEnterDelay</td>
|
||||
<td>number</td>
|
||||
<td>0</td>
|
||||
<td>delay time to show when mouse enter. unit: s.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mouseLeaveDelay</td>
|
||||
<td>number</td>
|
||||
<td>0.1</td>
|
||||
<td>delay time to hide when mouse leave. unit: s.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popupStyle</td>
|
||||
<td>Object</td>
|
||||
<td></td>
|
||||
<td>additional style of popup</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>prefixCls</td>
|
||||
<td>String</td>
|
||||
<td>rc-trigger-popup</td>
|
||||
<td>prefix class name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popupTransitionName</td>
|
||||
<td>String|Object</td>
|
||||
<td></td>
|
||||
<td>https://github.com/react-component/animate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maskTransitionName</td>
|
||||
<td>String|Object</td>
|
||||
<td></td>
|
||||
<td>https://github.com/react-component/animate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>onPopupVisibleChange</td>
|
||||
<td>Function</td>
|
||||
<td></td>
|
||||
<td>call when popup visible is changed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mask</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>whether to support mask</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maskClosable</td>
|
||||
<td>boolean</td>
|
||||
<td>true</td>
|
||||
<td>whether to support click mask to hide</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popupVisible</td>
|
||||
<td>boolean</td>
|
||||
<td></td>
|
||||
<td>whether popup is visible</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>zIndex</td>
|
||||
<td>number</td>
|
||||
<td></td>
|
||||
<td>popup's zIndex</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultPopupVisible</td>
|
||||
<td>boolean</td>
|
||||
<td></td>
|
||||
<td>whether popup is visible initially</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popupAlign</td>
|
||||
<td>Object: alignConfig of [dom-align](https://github.com/yiminghe/dom-align)</td>
|
||||
<td></td>
|
||||
<td>popup 's align config</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>onPopupAlign</td>
|
||||
<td>function(popupDomNode, align)</td>
|
||||
<td></td>
|
||||
<td>callback when popup node is aligned</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popup</td>
|
||||
<td>React.Element | function() => React.Element</td>
|
||||
<td></td>
|
||||
<td>popup content</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>getPopupContainer</td>
|
||||
<td>getPopupContainer(): HTMLElement</td>
|
||||
<td></td>
|
||||
<td>function returning html node which will act as popup container</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>getDocument</td>
|
||||
<td>getDocument(): HTMLElement</td>
|
||||
<td></td>
|
||||
<td>function returning document node which will be attached click event to close trigger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popupPlacement</td>
|
||||
<td>string</td>
|
||||
<td></td>
|
||||
<td>use preset popup align config from builtinPlacements, can be merged by popupAlign prop</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>builtinPlacements</td>
|
||||
<td>object</td>
|
||||
<td></td>
|
||||
<td>builtin placement align map. used by placement prop</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>stretch</td>
|
||||
<td>string</td>
|
||||
<td></td>
|
||||
<td>Let popup div stretch with trigger element. enums of 'width', 'minWidth', 'height', 'minHeight'. (You can also mixed with 'height minWidth')</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Test Case
|
||||
|
||||
```
|
||||
npm test
|
||||
npm run coverage
|
||||
```
|
||||
|
||||
open coverage/ dir
|
||||
|
||||
## License
|
||||
|
||||
rc-trigger is released under the MIT license.
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
.rc-trigger-popup {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
z-index: 1050;
|
||||
}
|
||||
.rc-trigger-popup-hidden {
|
||||
display: none;
|
||||
}
|
||||
.rc-trigger-popup-zoom-enter,
|
||||
.rc-trigger-popup-zoom-appear {
|
||||
opacity: 0;
|
||||
animation-play-state: paused;
|
||||
animation-timing-function: cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
.rc-trigger-popup-zoom-leave {
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
animation-play-state: paused;
|
||||
animation-timing-function: cubic-bezier(0.6, -0.3, 0.74, 0.05);
|
||||
}
|
||||
.rc-trigger-popup-zoom-enter.rc-trigger-popup-zoom-enter-active,
|
||||
.rc-trigger-popup-zoom-appear.rc-trigger-popup-zoom-appear-active {
|
||||
animation-name: rcTriggerZoomIn;
|
||||
animation-play-state: running;
|
||||
}
|
||||
.rc-trigger-popup-zoom-leave.rc-trigger-popup-zoom-leave-active {
|
||||
animation-name: rcTriggerZoomOut;
|
||||
animation-play-state: running;
|
||||
}
|
||||
.rc-trigger-popup-arrow {
|
||||
z-index: 1;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
background: #000;
|
||||
border-radius: 100vw;
|
||||
box-shadow: 0 0 0 3px black;
|
||||
}
|
||||
@keyframes rcTriggerZoomIn {
|
||||
0% {
|
||||
transform: scale(0, 0);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes rcTriggerZoomOut {
|
||||
0% {
|
||||
transform: scale(1, 1);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0, 0);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.rc-trigger-popup-unique-container {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid red;
|
||||
background: green;
|
||||
}
|
||||
.rc-trigger-popup-unique-container-hidden {
|
||||
display: none;
|
||||
}
|
||||
.rc-trigger-popup-unique-container-visible {
|
||||
transition: all 0.1s;
|
||||
}
|
||||
.rc-trigger-popup-unique-controlled {
|
||||
border-color: rgba(0, 0, 0, 0.01) !important;
|
||||
background: transparent !important;
|
||||
z-index: 1;
|
||||
}
|
||||
.rc-trigger-popup-motion-content-fade-appear {
|
||||
opacity: 0;
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
|
||||
}
|
||||
.rc-trigger-popup-motion-content-fade-appear.rc-trigger-popup-motion-content-fade-appear-active {
|
||||
animation-name: rcTriggerFadeIn;
|
||||
}
|
||||
@keyframes rcTriggerFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.rc-trigger-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: #373737;
|
||||
background-color: rgba(55, 55, 55, 0.6);
|
||||
height: 100%;
|
||||
filter: alpha(opacity=50);
|
||||
z-index: 1050;
|
||||
}
|
||||
.rc-trigger-popup-mask-hidden {
|
||||
display: none;
|
||||
}
|
||||
.rc-trigger-popup-fade-enter,
|
||||
.rc-trigger-popup-fade-appear {
|
||||
opacity: 0;
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.rc-trigger-popup-fade-leave {
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.rc-trigger-popup-fade-enter.rc-trigger-popup-fade-enter-active,
|
||||
.rc-trigger-popup-fade-appear.rc-trigger-popup-fade-appear-active {
|
||||
animation-name: rcTriggerMaskFadeIn;
|
||||
animation-play-state: running;
|
||||
}
|
||||
.rc-trigger-popup-fade-leave.rc-trigger-popup-fade-leave-active {
|
||||
animation-name: rcDialogFadeOut;
|
||||
animation-play-state: running;
|
||||
}
|
||||
@keyframes rcTriggerMaskFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes rcDialogFadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.rc-trigger-popup-mobile {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
}
|
||||
.rc-trigger-popup-mobile.raise-enter,
|
||||
.rc-trigger-popup-mobile.raise-appear {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
.rc-trigger-popup-mobile.raise-enter-active,
|
||||
.rc-trigger-popup-mobile.raise-appear-active {
|
||||
transition: all 0.3s;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.rc-trigger-popup-mobile.raise-leave {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.rc-trigger-popup-mobile.raise-leave-active {
|
||||
transition: all 0.3s;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
.rc-trigger-popup-mobile-mask.fade-enter,
|
||||
.rc-trigger-popup-mobile-mask.fade-appear {
|
||||
opacity: 0;
|
||||
}
|
||||
.rc-trigger-popup-mobile-mask.fade-enter-active,
|
||||
.rc-trigger-popup-mobile-mask.fade-appear-active {
|
||||
transition: all 0.3s;
|
||||
opacity: 1;
|
||||
}
|
||||
.rc-trigger-popup-mobile-mask.fade-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
.rc-trigger-popup-mobile-mask.fade-leave-active {
|
||||
transition: all 0.3s;
|
||||
opacity: 0;
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
@triggerPrefixCls: rc-trigger-popup;
|
||||
|
||||
.@{triggerPrefixCls} {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
z-index: 1050;
|
||||
|
||||
&-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.effect() {
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
&-zoom-enter,
|
||||
&-zoom-appear {
|
||||
opacity: 0;
|
||||
animation-play-state: paused;
|
||||
animation-timing-function: cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||
.effect();
|
||||
}
|
||||
|
||||
&-zoom-leave {
|
||||
.effect();
|
||||
animation-play-state: paused;
|
||||
animation-timing-function: cubic-bezier(0.6, -0.3, 0.74, 0.05);
|
||||
}
|
||||
|
||||
&-zoom-enter&-zoom-enter-active,
|
||||
&-zoom-appear&-zoom-appear-active {
|
||||
animation-name: rcTriggerZoomIn;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
&-zoom-leave&-zoom-leave-active {
|
||||
animation-name: rcTriggerZoomOut;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
z-index: 1;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
background: #000;
|
||||
border-radius: 100vw;
|
||||
box-shadow: 0 0 0 3px black;
|
||||
}
|
||||
|
||||
@keyframes rcTriggerZoomIn {
|
||||
0% {
|
||||
transform: scale(0, 0);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes rcTriggerZoomOut {
|
||||
0% {
|
||||
transform: scale(1, 1);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0, 0);
|
||||
transform-origin: var(--arrow-x, 50%) var(--arrow-y, 50%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Unique Container ===============
|
||||
&-unique-container {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid red;
|
||||
background: green;
|
||||
|
||||
&-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-visible {
|
||||
transition: all 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
// Debug
|
||||
&-unique-controlled {
|
||||
border-color: rgba(0, 0, 0, 0.01) !important;
|
||||
background: transparent !important;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// Motion Content
|
||||
&-motion-content {
|
||||
// Fade motion
|
||||
&-fade-appear {
|
||||
opacity: 0;
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
|
||||
}
|
||||
|
||||
&-fade-appear&-fade-appear-active {
|
||||
animation-name: rcTriggerFadeIn;
|
||||
}
|
||||
|
||||
@keyframes rcTriggerFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import './index/Mask';
|
||||
@import './index/Mobile';
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
.@{triggerPrefixCls} {
|
||||
&-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: rgb(55, 55, 55);
|
||||
background-color: rgba(55, 55, 55, 0.6);
|
||||
height: 100%;
|
||||
filter: alpha(opacity=50);
|
||||
z-index: 1050;
|
||||
|
||||
&-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-effect() {
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
|
||||
}
|
||||
|
||||
&-fade-enter,
|
||||
&-fade-appear {
|
||||
opacity: 0;
|
||||
.fade-effect();
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
&-fade-leave {
|
||||
.fade-effect();
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
&-fade-enter&-fade-enter-active,
|
||||
&-fade-appear&-fade-appear-active {
|
||||
animation-name: rcTriggerMaskFadeIn;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
&-fade-leave&-fade-leave-active {
|
||||
animation-name: rcDialogFadeOut;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes rcTriggerMaskFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rcDialogFadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
.@{triggerPrefixCls} {
|
||||
&-mobile {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
|
||||
// Motion
|
||||
&.raise {
|
||||
&-enter,
|
||||
&-appear {
|
||||
transform: translateY(100%);
|
||||
|
||||
&-active {
|
||||
transition: all 0.3s;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&-leave {
|
||||
transform: translateY(0);
|
||||
|
||||
&-active {
|
||||
transition: all 0.3s;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mask
|
||||
&-mask {
|
||||
&.fade {
|
||||
&-enter,
|
||||
&-appear {
|
||||
opacity: 0;
|
||||
|
||||
&-active {
|
||||
transition: all 0.3s;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-leave {
|
||||
opacity: 1;
|
||||
|
||||
&-active {
|
||||
transition: all 0.3s;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
|
||||
export interface ArrowProps {
|
||||
prefixCls: string;
|
||||
align: AlignType;
|
||||
arrow: ArrowTypeOuter;
|
||||
arrowPos: ArrowPos;
|
||||
}
|
||||
export default function Arrow(props: ArrowProps): React.JSX.Element;
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { clsx } from 'clsx';
|
||||
import * as React from 'react';
|
||||
export default function Arrow(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
align,
|
||||
arrow,
|
||||
arrowPos
|
||||
} = props;
|
||||
const {
|
||||
className,
|
||||
content,
|
||||
style
|
||||
} = arrow || {};
|
||||
const {
|
||||
x = 0,
|
||||
y = 0
|
||||
} = arrowPos;
|
||||
const arrowRef = React.useRef(null);
|
||||
|
||||
// Skip if no align
|
||||
if (!align || !align.points) {
|
||||
return null;
|
||||
}
|
||||
const alignStyle = {
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
// Skip if no need to align
|
||||
if (align.autoArrow !== false) {
|
||||
const popupPoints = align.points[0];
|
||||
const targetPoints = align.points[1];
|
||||
const popupTB = popupPoints[0];
|
||||
const popupLR = popupPoints[1];
|
||||
const targetTB = targetPoints[0];
|
||||
const targetLR = targetPoints[1];
|
||||
|
||||
// Top & Bottom
|
||||
if (popupTB === targetTB || !['t', 'b'].includes(popupTB)) {
|
||||
alignStyle.top = y;
|
||||
} else if (popupTB === 't') {
|
||||
alignStyle.top = 0;
|
||||
} else {
|
||||
alignStyle.bottom = 0;
|
||||
}
|
||||
|
||||
// Left & Right
|
||||
if (popupLR === targetLR || !['l', 'r'].includes(popupLR)) {
|
||||
alignStyle.left = x;
|
||||
} else if (popupLR === 'l') {
|
||||
alignStyle.left = 0;
|
||||
} else {
|
||||
alignStyle.right = 0;
|
||||
}
|
||||
}
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
ref: arrowRef,
|
||||
className: clsx(`${prefixCls}-arrow`, className),
|
||||
style: {
|
||||
...alignStyle,
|
||||
...style
|
||||
}
|
||||
}, content);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import * as React from 'react';
|
||||
export interface MaskProps {
|
||||
prefixCls: string;
|
||||
open?: boolean;
|
||||
zIndex?: number;
|
||||
mask?: boolean;
|
||||
motion?: CSSMotionProps;
|
||||
mobile?: boolean;
|
||||
}
|
||||
export default function Mask(props: MaskProps): React.JSX.Element;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
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 CSSMotion from '@rc-component/motion';
|
||||
import * as React from 'react';
|
||||
export default function Mask(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
open,
|
||||
zIndex,
|
||||
mask,
|
||||
motion,
|
||||
mobile
|
||||
} = props;
|
||||
if (!mask) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(CSSMotion, _extends({}, motion, {
|
||||
motionAppear: true,
|
||||
visible: open,
|
||||
removeOnLeave: true
|
||||
}), ({
|
||||
className
|
||||
}) => /*#__PURE__*/React.createElement("div", {
|
||||
style: {
|
||||
zIndex
|
||||
},
|
||||
className: clsx(`${prefixCls}-mask`, mobile && `${prefixCls}-mobile-mask`, className)
|
||||
}));
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import * as React from 'react';
|
||||
export interface PopupContentProps {
|
||||
children?: React.ReactNode;
|
||||
cache?: boolean;
|
||||
}
|
||||
declare const PopupContent: React.MemoExoticComponent<({ children }: PopupContentProps) => React.ReactElement<any, string | React.JSXElementConstructor<any>>>;
|
||||
export default PopupContent;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
const PopupContent = /*#__PURE__*/React.memo(({
|
||||
children
|
||||
}) => children, (_, next) => next.cache);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
PopupContent.displayName = 'PopupContent';
|
||||
}
|
||||
export default PopupContent;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import { type ResizeObserverProps } from '@rc-component/resize-observer';
|
||||
import * as React from 'react';
|
||||
import type { TriggerProps } from '../';
|
||||
import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
|
||||
import type { PortalProps } from '@rc-component/portal';
|
||||
export interface MobileConfig {
|
||||
mask?: boolean;
|
||||
/** Set popup motion. You can ref `rc-motion` for more info. */
|
||||
motion?: CSSMotionProps;
|
||||
/** Set mask motion. You can ref `rc-motion` for more info. */
|
||||
maskMotion?: CSSMotionProps;
|
||||
}
|
||||
export interface PopupProps {
|
||||
onEsc?: PortalProps['onEsc'];
|
||||
prefixCls: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
popup?: TriggerProps['popup'];
|
||||
target: HTMLElement;
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onPointerEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onPointerDownCapture?: React.MouseEventHandler<HTMLDivElement>;
|
||||
zIndex?: number;
|
||||
mask?: boolean;
|
||||
onVisibleChanged: (visible: boolean) => void;
|
||||
align?: AlignType;
|
||||
arrow?: ArrowTypeOuter;
|
||||
arrowPos: ArrowPos;
|
||||
open: boolean;
|
||||
/** Tell Portal that should keep in screen. e.g. should wait all motion end */
|
||||
keepDom: boolean;
|
||||
fresh?: boolean;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
motion?: CSSMotionProps;
|
||||
maskMotion?: CSSMotionProps;
|
||||
forceRender?: boolean;
|
||||
getPopupContainer?: TriggerProps['getPopupContainer'];
|
||||
autoDestroy?: boolean;
|
||||
portal: React.ComponentType<any>;
|
||||
children?: React.ReactElement;
|
||||
ready: boolean;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
offsetR: number;
|
||||
offsetB: number;
|
||||
onAlign: VoidFunction;
|
||||
onPrepare: () => Promise<void>;
|
||||
stretch?: string;
|
||||
targetWidth?: number;
|
||||
targetHeight?: number;
|
||||
onResize?: ResizeObserverProps['onResize'];
|
||||
mobile?: MobileConfig;
|
||||
}
|
||||
declare const Popup: React.ForwardRefExoticComponent<PopupProps & React.RefAttributes<HTMLDivElement>>;
|
||||
export default Popup;
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
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 CSSMotion from '@rc-component/motion';
|
||||
import ResizeObserver from '@rc-component/resize-observer';
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import { composeRef } from "@rc-component/util/es/ref";
|
||||
import * as React from 'react';
|
||||
import Arrow from "./Arrow";
|
||||
import Mask from "./Mask";
|
||||
import PopupContent from "./PopupContent";
|
||||
import useOffsetStyle from "../hooks/useOffsetStyle";
|
||||
import { useEvent } from '@rc-component/util';
|
||||
const Popup = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
onEsc,
|
||||
popup,
|
||||
className,
|
||||
prefixCls,
|
||||
style,
|
||||
target,
|
||||
onVisibleChanged,
|
||||
// Open
|
||||
open,
|
||||
keepDom,
|
||||
fresh,
|
||||
// Click
|
||||
onClick,
|
||||
// Mask
|
||||
mask,
|
||||
// Arrow
|
||||
arrow,
|
||||
arrowPos,
|
||||
align,
|
||||
// Motion
|
||||
motion,
|
||||
maskMotion,
|
||||
// Mobile
|
||||
mobile,
|
||||
// Portal
|
||||
forceRender,
|
||||
getPopupContainer,
|
||||
autoDestroy,
|
||||
portal: Portal,
|
||||
children,
|
||||
zIndex,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onPointerEnter,
|
||||
onPointerDownCapture,
|
||||
ready,
|
||||
offsetX,
|
||||
offsetY,
|
||||
offsetR,
|
||||
offsetB,
|
||||
onAlign,
|
||||
onPrepare,
|
||||
// Resize
|
||||
onResize,
|
||||
stretch,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
} = props;
|
||||
const popupContent = typeof popup === 'function' ? popup() : popup;
|
||||
|
||||
// We can not remove holder only when motion finished.
|
||||
const isNodeVisible = open || keepDom;
|
||||
|
||||
// ========================= Mobile =========================
|
||||
const isMobile = !!mobile;
|
||||
|
||||
// ========================== Mask ==========================
|
||||
const [mergedMask, mergedMaskMotion, mergedPopupMotion] = React.useMemo(() => {
|
||||
if (mobile) {
|
||||
return [mobile.mask, mobile.maskMotion, mobile.motion];
|
||||
}
|
||||
return [mask, maskMotion, motion];
|
||||
}, [mobile, mask, maskMotion, motion]);
|
||||
|
||||
// ======================= Container ========================
|
||||
const getPopupContainerNeedParams = getPopupContainer?.length > 0;
|
||||
const [show, setShow] = React.useState(!getPopupContainer || !getPopupContainerNeedParams);
|
||||
|
||||
// Delay to show since `getPopupContainer` need target element
|
||||
useLayoutEffect(() => {
|
||||
if (!show && getPopupContainerNeedParams && target) {
|
||||
setShow(true);
|
||||
}
|
||||
}, [show, getPopupContainerNeedParams, target]);
|
||||
|
||||
// ========================= Resize =========================
|
||||
const onInternalResize = useEvent((size, ele) => {
|
||||
onResize?.(size, ele);
|
||||
onAlign();
|
||||
});
|
||||
|
||||
// ========================= Styles =========================
|
||||
const offsetStyle = useOffsetStyle(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY);
|
||||
|
||||
// ========================= Render =========================
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// >>>>> Misc
|
||||
const miscStyle = {};
|
||||
if (stretch) {
|
||||
if (stretch.includes('height') && targetHeight) {
|
||||
miscStyle.height = targetHeight;
|
||||
} else if (stretch.includes('minHeight') && targetHeight) {
|
||||
miscStyle.minHeight = targetHeight;
|
||||
}
|
||||
if (stretch.includes('width') && targetWidth) {
|
||||
miscStyle.width = targetWidth;
|
||||
} else if (stretch.includes('minWidth') && targetWidth) {
|
||||
miscStyle.minWidth = targetWidth;
|
||||
}
|
||||
}
|
||||
if (!open) {
|
||||
miscStyle.pointerEvents = 'none';
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(Portal, {
|
||||
open: forceRender || isNodeVisible,
|
||||
getContainer: getPopupContainer && (() => getPopupContainer(target)),
|
||||
autoDestroy: autoDestroy,
|
||||
onEsc: onEsc
|
||||
}, /*#__PURE__*/React.createElement(Mask, {
|
||||
prefixCls: prefixCls,
|
||||
open: open,
|
||||
zIndex: zIndex,
|
||||
mask: mergedMask,
|
||||
motion: mergedMaskMotion,
|
||||
mobile: isMobile
|
||||
}), /*#__PURE__*/React.createElement(ResizeObserver, {
|
||||
onResize: onInternalResize,
|
||||
disabled: !open
|
||||
}, resizeObserverRef => {
|
||||
return /*#__PURE__*/React.createElement(CSSMotion, _extends({
|
||||
motionAppear: true,
|
||||
motionEnter: true,
|
||||
motionLeave: true,
|
||||
removeOnLeave: false,
|
||||
forceRender: forceRender,
|
||||
leavedClassName: `${prefixCls}-hidden`
|
||||
}, mergedPopupMotion, {
|
||||
onAppearPrepare: onPrepare,
|
||||
onEnterPrepare: onPrepare,
|
||||
visible: open,
|
||||
onVisibleChanged: nextVisible => {
|
||||
motion?.onVisibleChanged?.(nextVisible);
|
||||
onVisibleChanged(nextVisible);
|
||||
}
|
||||
}), ({
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}, motionRef) => {
|
||||
const cls = clsx(prefixCls, motionClassName, className, {
|
||||
[`${prefixCls}-mobile`]: isMobile
|
||||
});
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
ref: composeRef(resizeObserverRef, ref, motionRef),
|
||||
className: cls,
|
||||
style: {
|
||||
'--arrow-x': `${arrowPos.x || 0}px`,
|
||||
'--arrow-y': `${arrowPos.y || 0}px`,
|
||||
...offsetStyle,
|
||||
...miscStyle,
|
||||
...motionStyle,
|
||||
boxSizing: 'border-box',
|
||||
zIndex,
|
||||
...style
|
||||
},
|
||||
onMouseEnter: onMouseEnter,
|
||||
onMouseLeave: onMouseLeave,
|
||||
onPointerEnter: onPointerEnter,
|
||||
onClick: onClick,
|
||||
onPointerDownCapture: onPointerDownCapture
|
||||
}, arrow && /*#__PURE__*/React.createElement(Arrow, {
|
||||
prefixCls: prefixCls,
|
||||
arrow: arrow,
|
||||
arrowPos: arrowPos,
|
||||
align: align
|
||||
}), /*#__PURE__*/React.createElement(PopupContent, {
|
||||
cache: !open && !fresh
|
||||
}, popupContent));
|
||||
});
|
||||
}), children);
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Popup.displayName = 'Popup';
|
||||
}
|
||||
export default Popup;
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import type { AlignType, ArrowPos } from '../interface';
|
||||
export interface UniqueContainerProps {
|
||||
prefixCls: string;
|
||||
isMobile: boolean;
|
||||
ready: boolean;
|
||||
open: boolean;
|
||||
align: AlignType;
|
||||
offsetR: number;
|
||||
offsetB: number;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
arrowPos?: ArrowPos;
|
||||
popupSize?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
motion?: CSSMotionProps;
|
||||
uniqueContainerClassName?: string;
|
||||
uniqueContainerStyle?: React.CSSProperties;
|
||||
}
|
||||
declare const UniqueContainer: (props: UniqueContainerProps) => React.JSX.Element;
|
||||
export default UniqueContainer;
|
||||
Generated
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import React from 'react';
|
||||
import useOffsetStyle from "../hooks/useOffsetStyle";
|
||||
import { clsx } from 'clsx';
|
||||
import CSSMotion from '@rc-component/motion';
|
||||
const UniqueContainer = props => {
|
||||
const {
|
||||
prefixCls,
|
||||
isMobile,
|
||||
ready,
|
||||
open,
|
||||
align,
|
||||
offsetR,
|
||||
offsetB,
|
||||
offsetX,
|
||||
offsetY,
|
||||
arrowPos,
|
||||
popupSize,
|
||||
motion,
|
||||
uniqueContainerClassName,
|
||||
uniqueContainerStyle
|
||||
} = props;
|
||||
const containerCls = `${prefixCls}-unique-container`;
|
||||
const [motionVisible, setMotionVisible] = React.useState(false);
|
||||
|
||||
// ========================= Styles =========================
|
||||
const offsetStyle = useOffsetStyle(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY);
|
||||
|
||||
// Cache for offsetStyle when ready is true
|
||||
const cachedOffsetStyleRef = React.useRef(offsetStyle);
|
||||
|
||||
// Update cached offset style when ready is true
|
||||
if (ready) {
|
||||
cachedOffsetStyleRef.current = offsetStyle;
|
||||
}
|
||||
|
||||
// Apply popup size if available
|
||||
const sizeStyle = {};
|
||||
if (popupSize) {
|
||||
sizeStyle.width = popupSize.width;
|
||||
sizeStyle.height = popupSize.height;
|
||||
}
|
||||
|
||||
// ========================= Render =========================
|
||||
return /*#__PURE__*/React.createElement(CSSMotion, _extends({
|
||||
motionAppear: true,
|
||||
motionEnter: true,
|
||||
motionLeave: true,
|
||||
removeOnLeave: false,
|
||||
leavedClassName: `${containerCls}-hidden`
|
||||
}, motion, {
|
||||
visible: open,
|
||||
onVisibleChanged: nextVisible => {
|
||||
setMotionVisible(nextVisible);
|
||||
}
|
||||
}), ({
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}) => {
|
||||
const cls = clsx(containerCls, motionClassName, uniqueContainerClassName, {
|
||||
[`${containerCls}-visible`]: motionVisible
|
||||
});
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: cls,
|
||||
style: {
|
||||
'--arrow-x': `${arrowPos?.x || 0}px`,
|
||||
'--arrow-y': `${arrowPos?.y || 0}px`,
|
||||
...cachedOffsetStyleRef.current,
|
||||
...sizeStyle,
|
||||
...motionStyle,
|
||||
...uniqueContainerStyle
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
export default UniqueContainer;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { type UniqueShowOptions } from '../context';
|
||||
export interface UniqueProviderProps {
|
||||
children: React.ReactNode;
|
||||
/** Additional handle options data to do the customize info */
|
||||
postTriggerProps?: (options: UniqueShowOptions) => UniqueShowOptions;
|
||||
}
|
||||
declare const UniqueProvider: ({ children, postTriggerProps, }: UniqueProviderProps) => React.JSX.Element;
|
||||
export default UniqueProvider;
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
import * as React from 'react';
|
||||
import Portal from '@rc-component/portal';
|
||||
import TriggerContext, { UniqueContext } from "../context";
|
||||
import useDelay from "../hooks/useDelay";
|
||||
import useAlign from "../hooks/useAlign";
|
||||
import Popup from "../Popup";
|
||||
import { useEvent } from '@rc-component/util';
|
||||
import useTargetState from "./useTargetState";
|
||||
import { isDOM } from "@rc-component/util/es/Dom/findDOMNode";
|
||||
import UniqueContainer from "./UniqueContainer";
|
||||
import { clsx } from 'clsx';
|
||||
import { getAlignPopupClassName } from "../util";
|
||||
const UniqueProvider = ({
|
||||
children,
|
||||
postTriggerProps
|
||||
}) => {
|
||||
const [trigger, open, options, onTargetVisibleChanged] = useTargetState();
|
||||
|
||||
// ========================== Options ===========================
|
||||
const mergedOptions = React.useMemo(() => {
|
||||
if (!options || !postTriggerProps) {
|
||||
return options;
|
||||
}
|
||||
return postTriggerProps(options);
|
||||
}, [options, postTriggerProps]);
|
||||
|
||||
// =========================== Popup ============================
|
||||
const [popupEle, setPopupEle] = React.useState(null);
|
||||
const [popupSize, setPopupSize] = React.useState(null);
|
||||
|
||||
// Used for forwardRef popup. Not use internal
|
||||
const externalPopupRef = React.useRef(null);
|
||||
const setPopupRef = useEvent(node => {
|
||||
externalPopupRef.current = node;
|
||||
if (isDOM(node) && popupEle !== node) {
|
||||
setPopupEle(node);
|
||||
}
|
||||
});
|
||||
|
||||
// ========================== Register ==========================
|
||||
// Store the isOpen function from the latest show call
|
||||
const isOpenRef = React.useRef(null);
|
||||
const delayInvoke = useDelay();
|
||||
const show = useEvent((showOptions, isOpen) => {
|
||||
// Store the isOpen function for later use in hide
|
||||
isOpenRef.current = isOpen;
|
||||
delayInvoke(() => {
|
||||
trigger(showOptions);
|
||||
}, showOptions.delay);
|
||||
});
|
||||
const hide = delay => {
|
||||
delayInvoke(() => {
|
||||
// Check if we should still hide by calling the isOpen function
|
||||
// If isOpen returns true, it means another trigger wants to keep it open
|
||||
if (isOpenRef.current?.()) {
|
||||
return; // Don't hide if something else wants it open
|
||||
}
|
||||
trigger(false);
|
||||
// Don't clear target, currentNode, options immediately, wait until animation completes
|
||||
}, delay);
|
||||
};
|
||||
|
||||
// Callback after animation completes
|
||||
const onVisibleChanged = useEvent(visible => {
|
||||
// Call useTargetState callback to handle animation state
|
||||
onTargetVisibleChanged(visible);
|
||||
});
|
||||
|
||||
// =========================== Align ============================
|
||||
const [ready, offsetX, offsetY, offsetR, offsetB, arrowX, arrowY,
|
||||
|
||||
// scaleX - not used in UniqueProvider
|
||||
,,
|
||||
// scaleY - not used in UniqueProvider
|
||||
alignInfo, onAlign] = useAlign(open, popupEle, mergedOptions?.target, mergedOptions?.popupPlacement, mergedOptions?.builtinPlacements || {}, mergedOptions?.popupAlign, undefined,
|
||||
// onPopupAlign
|
||||
false // isMobile
|
||||
);
|
||||
const alignedClassName = React.useMemo(() => {
|
||||
if (!mergedOptions) {
|
||||
return '';
|
||||
}
|
||||
const baseClassName = getAlignPopupClassName(mergedOptions.builtinPlacements || {}, mergedOptions.prefixCls || '', alignInfo, false // alignPoint is false for UniqueProvider
|
||||
);
|
||||
return clsx(baseClassName, mergedOptions.getPopupClassNameFromAlign?.(alignInfo));
|
||||
}, [alignInfo, mergedOptions?.getPopupClassNameFromAlign, mergedOptions?.builtinPlacements, mergedOptions?.prefixCls]);
|
||||
const contextValue = React.useMemo(() => ({
|
||||
show,
|
||||
hide
|
||||
}), []);
|
||||
|
||||
// =========================== Align ============================
|
||||
React.useEffect(() => {
|
||||
onAlign();
|
||||
}, [mergedOptions?.target]);
|
||||
|
||||
// =========================== Motion ===========================
|
||||
const onPrepare = useEvent(() => {
|
||||
onAlign();
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// ======================== Trigger Context =====================
|
||||
const subPopupElements = React.useRef({});
|
||||
const parentContext = React.useContext(TriggerContext);
|
||||
const triggerContextValue = React.useMemo(() => ({
|
||||
registerSubPopup: (id, subPopupEle) => {
|
||||
subPopupElements.current[id] = subPopupEle;
|
||||
parentContext?.registerSubPopup(id, subPopupEle);
|
||||
}
|
||||
}), [parentContext]);
|
||||
|
||||
// =========================== Render ===========================
|
||||
const prefixCls = mergedOptions?.prefixCls;
|
||||
return /*#__PURE__*/React.createElement(UniqueContext.Provider, {
|
||||
value: contextValue
|
||||
}, children, mergedOptions && /*#__PURE__*/React.createElement(TriggerContext.Provider, {
|
||||
value: triggerContextValue
|
||||
}, /*#__PURE__*/React.createElement(Popup, {
|
||||
ref: setPopupRef,
|
||||
portal: Portal,
|
||||
onEsc: mergedOptions.onEsc,
|
||||
prefixCls: prefixCls,
|
||||
popup: mergedOptions.popup,
|
||||
className: clsx(mergedOptions.popupClassName, alignedClassName, `${prefixCls}-unique-controlled`),
|
||||
style: mergedOptions.popupStyle,
|
||||
target: mergedOptions.target,
|
||||
open: open,
|
||||
keepDom: true,
|
||||
fresh: true,
|
||||
autoDestroy: false,
|
||||
onVisibleChanged: onVisibleChanged,
|
||||
ready: ready,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
offsetR: offsetR,
|
||||
offsetB: offsetB,
|
||||
onAlign: onAlign,
|
||||
onPrepare: onPrepare,
|
||||
onResize: size => setPopupSize({
|
||||
width: size.offsetWidth,
|
||||
height: size.offsetHeight
|
||||
}),
|
||||
arrowPos: {
|
||||
x: arrowX,
|
||||
y: arrowY
|
||||
},
|
||||
align: alignInfo,
|
||||
zIndex: mergedOptions.zIndex,
|
||||
mask: mergedOptions.mask,
|
||||
arrow: mergedOptions.arrow,
|
||||
motion: mergedOptions.popupMotion,
|
||||
maskMotion: mergedOptions.maskMotion,
|
||||
getPopupContainer: mergedOptions.getPopupContainer
|
||||
}, /*#__PURE__*/React.createElement(UniqueContainer, {
|
||||
prefixCls: prefixCls,
|
||||
isMobile: false,
|
||||
ready: ready,
|
||||
open: open,
|
||||
align: alignInfo,
|
||||
offsetR: offsetR,
|
||||
offsetB: offsetB,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
arrowPos: {
|
||||
x: arrowX,
|
||||
y: arrowY
|
||||
},
|
||||
popupSize: popupSize,
|
||||
motion: mergedOptions.popupMotion,
|
||||
uniqueContainerClassName: clsx(mergedOptions.uniqueContainerClassName, alignedClassName),
|
||||
uniqueContainerStyle: mergedOptions.uniqueContainerStyle
|
||||
}))));
|
||||
};
|
||||
export default UniqueProvider;
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
import type { UniqueShowOptions } from '../context';
|
||||
/**
|
||||
* Control the state of popup bind target:
|
||||
* 1. When set `target`. Do show the popup.
|
||||
* 2. When `target` is removed. Do hide the popup.
|
||||
* 3. When `target` change to another one:
|
||||
* a. We wait motion finish of previous popup.
|
||||
* b. Then we set new target and show the popup.
|
||||
* 4. During appear/enter animation, cache new options and apply after animation completes.
|
||||
*/
|
||||
export default function useTargetState(): [
|
||||
trigger: (options: UniqueShowOptions | false) => void,
|
||||
open: boolean,
|
||||
cacheOptions: UniqueShowOptions | null,
|
||||
onVisibleChanged: (visible: boolean) => void
|
||||
];
|
||||
Generated
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { useEvent } from '@rc-component/util';
|
||||
/**
|
||||
* Control the state of popup bind target:
|
||||
* 1. When set `target`. Do show the popup.
|
||||
* 2. When `target` is removed. Do hide the popup.
|
||||
* 3. When `target` change to another one:
|
||||
* a. We wait motion finish of previous popup.
|
||||
* b. Then we set new target and show the popup.
|
||||
* 4. During appear/enter animation, cache new options and apply after animation completes.
|
||||
*/
|
||||
export default function useTargetState() {
|
||||
const [options, setOptions] = React.useState(null);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [isAnimating, setIsAnimating] = React.useState(false);
|
||||
const pendingOptionsRef = React.useRef(null);
|
||||
const trigger = useEvent(nextOptions => {
|
||||
if (nextOptions === false) {
|
||||
// Clear pending options when hiding
|
||||
pendingOptionsRef.current = null;
|
||||
setOpen(false);
|
||||
} else {
|
||||
if (isAnimating && open) {
|
||||
// If animating (appear or enter), cache new options
|
||||
pendingOptionsRef.current = nextOptions;
|
||||
} else {
|
||||
setOpen(true);
|
||||
// Set new options
|
||||
setOptions(nextOptions);
|
||||
pendingOptionsRef.current = null;
|
||||
|
||||
// Only mark as animating when transitioning from closed to open
|
||||
if (!open) {
|
||||
setIsAnimating(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const onVisibleChanged = useEvent(visible => {
|
||||
if (visible) {
|
||||
// Animation enter completed, check if there are pending options
|
||||
setIsAnimating(false);
|
||||
if (pendingOptionsRef.current) {
|
||||
// Apply pending options
|
||||
setOptions(pendingOptionsRef.current);
|
||||
pendingOptionsRef.current = null;
|
||||
}
|
||||
} else {
|
||||
// Animation leave completed
|
||||
setIsAnimating(false);
|
||||
pendingOptionsRef.current = null;
|
||||
}
|
||||
});
|
||||
return [trigger, open, options, onVisibleChanged];
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import * as React from 'react';
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import type { PortalProps } from '@rc-component/portal';
|
||||
import type { TriggerProps } from './index';
|
||||
import type { AlignType, ArrowTypeOuter, BuildInPlacements } from './interface';
|
||||
export interface TriggerContextProps {
|
||||
registerSubPopup: (id: string, node: HTMLElement) => void;
|
||||
}
|
||||
declare const TriggerContext: React.Context<TriggerContextProps>;
|
||||
export default TriggerContext;
|
||||
export interface UniqueShowOptions {
|
||||
id: string;
|
||||
popup: TriggerProps['popup'];
|
||||
target: HTMLElement;
|
||||
delay: number;
|
||||
prefixCls?: string;
|
||||
popupClassName?: string;
|
||||
uniqueContainerClassName?: string;
|
||||
uniqueContainerStyle?: React.CSSProperties;
|
||||
popupStyle?: React.CSSProperties;
|
||||
popupPlacement?: string;
|
||||
builtinPlacements?: BuildInPlacements;
|
||||
popupAlign?: AlignType;
|
||||
zIndex?: number;
|
||||
mask?: boolean;
|
||||
maskClosable?: boolean;
|
||||
popupMotion?: CSSMotionProps;
|
||||
maskMotion?: CSSMotionProps;
|
||||
arrow?: ArrowTypeOuter;
|
||||
getPopupContainer?: TriggerProps['getPopupContainer'];
|
||||
getPopupClassNameFromAlign?: (align: AlignType) => string;
|
||||
onEsc?: PortalProps['onEsc'];
|
||||
}
|
||||
export interface UniqueContextProps {
|
||||
show: (options: UniqueShowOptions, isOpen: () => boolean) => void;
|
||||
hide: (delay: number) => void;
|
||||
}
|
||||
export declare const UniqueContext: React.Context<UniqueContextProps>;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
|
||||
// ===================== Nest =====================
|
||||
|
||||
const TriggerContext = /*#__PURE__*/React.createContext(null);
|
||||
export default TriggerContext;
|
||||
|
||||
// ==================== Unique ====================
|
||||
|
||||
export const UniqueContext = /*#__PURE__*/React.createContext(null);
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import type { ActionType } from '../interface';
|
||||
type InternalActionType = ActionType | 'touch';
|
||||
type ActionTypes = InternalActionType | InternalActionType[];
|
||||
export default function useAction(action: ActionTypes, showAction?: ActionTypes, hideAction?: ActionTypes): [showAction: Set<InternalActionType>, hideAction: Set<InternalActionType>];
|
||||
export {};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
function toArray(val) {
|
||||
return val ? Array.isArray(val) ? val : [val] : [];
|
||||
}
|
||||
export default function useAction(action, showAction, hideAction) {
|
||||
return React.useMemo(() => {
|
||||
const mergedShowAction = toArray(showAction ?? action);
|
||||
const mergedHideAction = toArray(hideAction ?? action);
|
||||
const showActionSet = new Set(mergedShowAction);
|
||||
const hideActionSet = new Set(mergedHideAction);
|
||||
if (showActionSet.has('hover') && !showActionSet.has('click')) {
|
||||
showActionSet.add('touch');
|
||||
}
|
||||
if (hideActionSet.has('hover') && !hideActionSet.has('click')) {
|
||||
hideActionSet.add('touch');
|
||||
}
|
||||
return [showActionSet, hideActionSet];
|
||||
}, [action, showAction, hideAction]);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import type { TriggerProps } from '..';
|
||||
import type { AlignType } from '../interface';
|
||||
export default function useAlign(open: boolean, popupEle: HTMLElement, target: HTMLElement | [x: number, y: number], placement: string, builtinPlacements: any, popupAlign?: AlignType, onPopupAlign?: TriggerProps['onPopupAlign'], mobile?: boolean): [
|
||||
ready: boolean,
|
||||
offsetX: number,
|
||||
offsetY: number,
|
||||
offsetR: number,
|
||||
offsetB: number,
|
||||
arrowX: number,
|
||||
arrowY: number,
|
||||
scaleX: number,
|
||||
scaleY: number,
|
||||
align: AlignType,
|
||||
onAlign: VoidFunction
|
||||
];
|
||||
+531
@@ -0,0 +1,531 @@
|
||||
import { isDOM } from "@rc-component/util/es/Dom/findDOMNode";
|
||||
import isVisible from "@rc-component/util/es/Dom/isVisible";
|
||||
import useEvent from "@rc-component/util/es/hooks/useEvent";
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import * as React from 'react';
|
||||
import { collectScroller, getVisibleArea, getWin, toNum } from "../util";
|
||||
function getUnitOffset(size, offset = 0) {
|
||||
const offsetStr = `${offset}`;
|
||||
const cells = offsetStr.match(/^(.*)\%$/);
|
||||
if (cells) {
|
||||
return size * (parseFloat(cells[1]) / 100);
|
||||
}
|
||||
return parseFloat(offsetStr);
|
||||
}
|
||||
function getNumberOffset(rect, offset) {
|
||||
const [offsetX, offsetY] = offset || [];
|
||||
return [getUnitOffset(rect.width, offsetX), getUnitOffset(rect.height, offsetY)];
|
||||
}
|
||||
function splitPoints(points = '') {
|
||||
return [points[0], points[1]];
|
||||
}
|
||||
function getAlignPoint(rect, points) {
|
||||
const topBottom = points[0];
|
||||
const leftRight = points[1];
|
||||
let x;
|
||||
let y;
|
||||
|
||||
// Top & Bottom
|
||||
if (topBottom === 't') {
|
||||
y = rect.y;
|
||||
} else if (topBottom === 'b') {
|
||||
y = rect.y + rect.height;
|
||||
} else {
|
||||
y = rect.y + rect.height / 2;
|
||||
}
|
||||
|
||||
// Left & Right
|
||||
if (leftRight === 'l') {
|
||||
x = rect.x;
|
||||
} else if (leftRight === 'r') {
|
||||
x = rect.x + rect.width;
|
||||
} else {
|
||||
x = rect.x + rect.width / 2;
|
||||
}
|
||||
return {
|
||||
x,
|
||||
y
|
||||
};
|
||||
}
|
||||
function reversePoints(points, index) {
|
||||
const reverseMap = {
|
||||
t: 'b',
|
||||
b: 't',
|
||||
l: 'r',
|
||||
r: 'l'
|
||||
};
|
||||
const clone = [...points];
|
||||
clone[index] = reverseMap[points[index]] || 'c';
|
||||
return clone;
|
||||
}
|
||||
function flatPoints(points) {
|
||||
return points.join('');
|
||||
}
|
||||
export default function useAlign(open, popupEle, target, placement, builtinPlacements, popupAlign, onPopupAlign, mobile) {
|
||||
const [offsetInfo, setOffsetInfo] = React.useState({
|
||||
ready: false,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
offsetR: 0,
|
||||
offsetB: 0,
|
||||
arrowX: 0,
|
||||
arrowY: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
align: builtinPlacements[placement] || {}
|
||||
});
|
||||
const alignCountRef = React.useRef(0);
|
||||
const scrollerList = React.useMemo(() => {
|
||||
if (!popupEle || mobile) {
|
||||
return [];
|
||||
}
|
||||
return collectScroller(popupEle);
|
||||
}, [popupEle]);
|
||||
|
||||
// ========================= Flip ==========================
|
||||
// We will memo flip info.
|
||||
// If size change to make flip, it will memo the flip info and use it in next align.
|
||||
const prevFlipRef = React.useRef({});
|
||||
const resetFlipCache = () => {
|
||||
prevFlipRef.current = {};
|
||||
};
|
||||
if (!open) {
|
||||
resetFlipCache();
|
||||
}
|
||||
|
||||
// ========================= Align =========================
|
||||
const onAlign = useEvent(() => {
|
||||
if (popupEle && target && open && !mobile) {
|
||||
const popupElement = popupEle;
|
||||
const doc = popupElement.ownerDocument;
|
||||
const win = getWin(popupElement);
|
||||
const {
|
||||
position: popupPosition
|
||||
} = win.getComputedStyle(popupElement);
|
||||
const originLeft = popupElement.style.left;
|
||||
const originTop = popupElement.style.top;
|
||||
const originRight = popupElement.style.right;
|
||||
const originBottom = popupElement.style.bottom;
|
||||
const originOverflow = popupElement.style.overflow;
|
||||
|
||||
// Placement
|
||||
const placementInfo = {
|
||||
...builtinPlacements[placement],
|
||||
...popupAlign
|
||||
};
|
||||
|
||||
// placeholder element
|
||||
const placeholderElement = doc.createElement('div');
|
||||
popupElement.parentElement?.appendChild(placeholderElement);
|
||||
placeholderElement.style.left = `${popupElement.offsetLeft}px`;
|
||||
placeholderElement.style.top = `${popupElement.offsetTop}px`;
|
||||
placeholderElement.style.position = popupPosition;
|
||||
placeholderElement.style.height = `${popupElement.offsetHeight}px`;
|
||||
placeholderElement.style.width = `${popupElement.offsetWidth}px`;
|
||||
|
||||
// Reset first
|
||||
popupElement.style.left = '0';
|
||||
popupElement.style.top = '0';
|
||||
popupElement.style.right = 'auto';
|
||||
popupElement.style.bottom = 'auto';
|
||||
popupElement.style.overflow = 'hidden';
|
||||
|
||||
// Calculate align style, we should consider `transform` case
|
||||
let targetRect;
|
||||
if (Array.isArray(target)) {
|
||||
targetRect = {
|
||||
x: target[0],
|
||||
y: target[1],
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
} else {
|
||||
const rect = target.getBoundingClientRect();
|
||||
rect.x = rect.x ?? rect.left;
|
||||
rect.y = rect.y ?? rect.top;
|
||||
targetRect = {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
};
|
||||
}
|
||||
const popupRect = popupElement.getBoundingClientRect();
|
||||
const {
|
||||
height,
|
||||
width
|
||||
} = win.getComputedStyle(popupElement);
|
||||
popupRect.x = popupRect.x ?? popupRect.left;
|
||||
popupRect.y = popupRect.y ?? popupRect.top;
|
||||
const {
|
||||
clientWidth,
|
||||
clientHeight,
|
||||
scrollWidth,
|
||||
scrollHeight,
|
||||
scrollTop,
|
||||
scrollLeft
|
||||
} = doc.documentElement;
|
||||
const popupHeight = popupRect.height;
|
||||
const popupWidth = popupRect.width;
|
||||
const targetHeight = targetRect.height;
|
||||
const targetWidth = targetRect.width;
|
||||
|
||||
// Get bounding of visible area
|
||||
const visibleRegion = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: clientWidth,
|
||||
bottom: clientHeight
|
||||
};
|
||||
const scrollRegion = {
|
||||
left: -scrollLeft,
|
||||
top: -scrollTop,
|
||||
right: scrollWidth - scrollLeft,
|
||||
bottom: scrollHeight - scrollTop
|
||||
};
|
||||
let {
|
||||
htmlRegion
|
||||
} = placementInfo;
|
||||
const VISIBLE = 'visible';
|
||||
const VISIBLE_FIRST = 'visibleFirst';
|
||||
if (htmlRegion !== 'scroll' && htmlRegion !== VISIBLE_FIRST) {
|
||||
htmlRegion = VISIBLE;
|
||||
}
|
||||
const isVisibleFirst = htmlRegion === VISIBLE_FIRST;
|
||||
const scrollRegionArea = getVisibleArea(scrollRegion, scrollerList);
|
||||
const visibleRegionArea = getVisibleArea(visibleRegion, scrollerList);
|
||||
const visibleArea = htmlRegion === VISIBLE ? visibleRegionArea : scrollRegionArea;
|
||||
|
||||
// When set to `visibleFirst`,
|
||||
// the check `adjust` logic will use `visibleRegion` for check first.
|
||||
const adjustCheckVisibleArea = isVisibleFirst ? visibleRegionArea : visibleArea;
|
||||
|
||||
// Record right & bottom align data
|
||||
popupElement.style.left = 'auto';
|
||||
popupElement.style.top = 'auto';
|
||||
popupElement.style.right = '0';
|
||||
popupElement.style.bottom = '0';
|
||||
const popupMirrorRect = popupElement.getBoundingClientRect();
|
||||
|
||||
// Reset back
|
||||
popupElement.style.left = originLeft;
|
||||
popupElement.style.top = originTop;
|
||||
popupElement.style.right = originRight;
|
||||
popupElement.style.bottom = originBottom;
|
||||
popupElement.style.overflow = originOverflow;
|
||||
popupElement.parentElement?.removeChild(placeholderElement);
|
||||
|
||||
// Calculate scale
|
||||
const scaleX = toNum(Math.round(popupWidth / parseFloat(width) * 1000) / 1000);
|
||||
const scaleY = toNum(Math.round(popupHeight / parseFloat(height) * 1000) / 1000);
|
||||
|
||||
// No need to align since it's not visible in view
|
||||
if (scaleX === 0 || scaleY === 0 || isDOM(target) && !isVisible(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Offset
|
||||
const {
|
||||
offset,
|
||||
targetOffset
|
||||
} = placementInfo;
|
||||
let [popupOffsetX, popupOffsetY] = getNumberOffset(popupRect, offset);
|
||||
const [targetOffsetX, targetOffsetY] = getNumberOffset(targetRect, targetOffset);
|
||||
targetRect.x -= targetOffsetX;
|
||||
targetRect.y -= targetOffsetY;
|
||||
|
||||
// Points
|
||||
const [popupPoint, targetPoint] = placementInfo.points || [];
|
||||
const targetPoints = splitPoints(targetPoint);
|
||||
const popupPoints = splitPoints(popupPoint);
|
||||
const targetAlignPoint = getAlignPoint(targetRect, targetPoints);
|
||||
const popupAlignPoint = getAlignPoint(popupRect, popupPoints);
|
||||
|
||||
// Real align info may not same as origin one
|
||||
const nextAlignInfo = {
|
||||
...placementInfo
|
||||
};
|
||||
let nextPoints = [popupPoints, targetPoints];
|
||||
|
||||
// Next Offset
|
||||
let nextOffsetX = targetAlignPoint.x - popupAlignPoint.x + popupOffsetX;
|
||||
let nextOffsetY = targetAlignPoint.y - popupAlignPoint.y + popupOffsetY;
|
||||
|
||||
// ============== Intersection ===============
|
||||
// Get area by position. Used for check if flip area is better
|
||||
function getIntersectionVisibleArea(offsetX, offsetY, area = visibleArea) {
|
||||
const l = popupRect.x + offsetX;
|
||||
const t = popupRect.y + offsetY;
|
||||
const r = l + popupWidth;
|
||||
const b = t + popupHeight;
|
||||
const visibleL = Math.max(l, area.left);
|
||||
const visibleT = Math.max(t, area.top);
|
||||
const visibleR = Math.min(r, area.right);
|
||||
const visibleB = Math.min(b, area.bottom);
|
||||
return Math.max(0, (visibleR - visibleL) * (visibleB - visibleT));
|
||||
}
|
||||
const originIntersectionVisibleArea = getIntersectionVisibleArea(nextOffsetX, nextOffsetY);
|
||||
|
||||
// As `visibleFirst`, we prepare this for check
|
||||
const originIntersectionRecommendArea = getIntersectionVisibleArea(nextOffsetX, nextOffsetY, visibleRegionArea);
|
||||
|
||||
// ========================== Overflow ===========================
|
||||
const targetAlignPointTL = getAlignPoint(targetRect, ['t', 'l']);
|
||||
const popupAlignPointTL = getAlignPoint(popupRect, ['t', 'l']);
|
||||
const targetAlignPointBR = getAlignPoint(targetRect, ['b', 'r']);
|
||||
const popupAlignPointBR = getAlignPoint(popupRect, ['b', 'r']);
|
||||
const overflow = placementInfo.overflow || {};
|
||||
const {
|
||||
adjustX,
|
||||
adjustY,
|
||||
shiftX,
|
||||
shiftY
|
||||
} = overflow;
|
||||
const supportAdjust = val => {
|
||||
if (typeof val === 'boolean') {
|
||||
return val;
|
||||
}
|
||||
return val >= 0;
|
||||
};
|
||||
|
||||
// Prepare position
|
||||
let nextPopupY;
|
||||
let nextPopupBottom;
|
||||
let nextPopupX;
|
||||
let nextPopupRight;
|
||||
function syncNextPopupPosition() {
|
||||
nextPopupY = popupRect.y + nextOffsetY;
|
||||
nextPopupBottom = nextPopupY + popupHeight;
|
||||
nextPopupX = popupRect.x + nextOffsetX;
|
||||
nextPopupRight = nextPopupX + popupWidth;
|
||||
}
|
||||
syncNextPopupPosition();
|
||||
|
||||
// >>>>>>>>>> Top & Bottom
|
||||
const needAdjustY = supportAdjust(adjustY);
|
||||
const sameTB = popupPoints[0] === targetPoints[0];
|
||||
|
||||
// Bottom to Top
|
||||
if (needAdjustY && popupPoints[0] === 't' && (nextPopupBottom > adjustCheckVisibleArea.bottom || prevFlipRef.current.bt)) {
|
||||
let tmpNextOffsetY = nextOffsetY;
|
||||
if (sameTB) {
|
||||
tmpNextOffsetY -= popupHeight - targetHeight;
|
||||
} else {
|
||||
tmpNextOffsetY = targetAlignPointTL.y - popupAlignPointBR.y - popupOffsetY;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.bt = true;
|
||||
nextOffsetY = tmpNextOffsetY;
|
||||
popupOffsetY = -popupOffsetY;
|
||||
nextPoints = [reversePoints(nextPoints[0], 0), reversePoints(nextPoints[1], 0)];
|
||||
} else {
|
||||
prevFlipRef.current.bt = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Top to Bottom
|
||||
if (needAdjustY && popupPoints[0] === 'b' && (nextPopupY < adjustCheckVisibleArea.top || prevFlipRef.current.tb)) {
|
||||
let tmpNextOffsetY = nextOffsetY;
|
||||
if (sameTB) {
|
||||
tmpNextOffsetY += popupHeight - targetHeight;
|
||||
} else {
|
||||
tmpNextOffsetY = targetAlignPointBR.y - popupAlignPointTL.y - popupOffsetY;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.tb = true;
|
||||
nextOffsetY = tmpNextOffsetY;
|
||||
popupOffsetY = -popupOffsetY;
|
||||
nextPoints = [reversePoints(nextPoints[0], 0), reversePoints(nextPoints[1], 0)];
|
||||
} else {
|
||||
prevFlipRef.current.tb = false;
|
||||
}
|
||||
}
|
||||
|
||||
// >>>>>>>>>> Left & Right
|
||||
const needAdjustX = supportAdjust(adjustX);
|
||||
|
||||
// >>>>> Flip
|
||||
const sameLR = popupPoints[1] === targetPoints[1];
|
||||
|
||||
// Right to Left
|
||||
if (needAdjustX && popupPoints[1] === 'l' && (nextPopupRight > adjustCheckVisibleArea.right || prevFlipRef.current.rl)) {
|
||||
let tmpNextOffsetX = nextOffsetX;
|
||||
if (sameLR) {
|
||||
tmpNextOffsetX -= popupWidth - targetWidth;
|
||||
} else {
|
||||
tmpNextOffsetX = targetAlignPointTL.x - popupAlignPointBR.x - popupOffsetX;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.rl = true;
|
||||
nextOffsetX = tmpNextOffsetX;
|
||||
popupOffsetX = -popupOffsetX;
|
||||
nextPoints = [reversePoints(nextPoints[0], 1), reversePoints(nextPoints[1], 1)];
|
||||
} else {
|
||||
prevFlipRef.current.rl = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Left to Right
|
||||
if (needAdjustX && popupPoints[1] === 'r' && (nextPopupX < adjustCheckVisibleArea.left || prevFlipRef.current.lr)) {
|
||||
let tmpNextOffsetX = nextOffsetX;
|
||||
if (sameLR) {
|
||||
tmpNextOffsetX += popupWidth - targetWidth;
|
||||
} else {
|
||||
tmpNextOffsetX = targetAlignPointBR.x - popupAlignPointTL.x - popupOffsetX;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.lr = true;
|
||||
nextOffsetX = tmpNextOffsetX;
|
||||
popupOffsetX = -popupOffsetX;
|
||||
nextPoints = [reversePoints(nextPoints[0], 1), reversePoints(nextPoints[1], 1)];
|
||||
} else {
|
||||
prevFlipRef.current.lr = false;
|
||||
}
|
||||
}
|
||||
nextAlignInfo.points = [flatPoints(nextPoints[0]), flatPoints(nextPoints[1])];
|
||||
|
||||
// ============================ Shift ============================
|
||||
syncNextPopupPosition();
|
||||
const numShiftX = shiftX === true ? 0 : shiftX;
|
||||
if (typeof numShiftX === 'number') {
|
||||
// Left
|
||||
if (nextPopupX < visibleRegionArea.left) {
|
||||
nextOffsetX -= nextPopupX - visibleRegionArea.left - popupOffsetX;
|
||||
if (targetRect.x + targetWidth < visibleRegionArea.left + numShiftX) {
|
||||
nextOffsetX += targetRect.x - visibleRegionArea.left + targetWidth - numShiftX;
|
||||
}
|
||||
}
|
||||
|
||||
// Right
|
||||
if (nextPopupRight > visibleRegionArea.right) {
|
||||
nextOffsetX -= nextPopupRight - visibleRegionArea.right - popupOffsetX;
|
||||
if (targetRect.x > visibleRegionArea.right - numShiftX) {
|
||||
nextOffsetX += targetRect.x - visibleRegionArea.right + numShiftX;
|
||||
}
|
||||
}
|
||||
}
|
||||
const numShiftY = shiftY === true ? 0 : shiftY;
|
||||
if (typeof numShiftY === 'number') {
|
||||
// Top
|
||||
if (nextPopupY < visibleRegionArea.top) {
|
||||
nextOffsetY -= nextPopupY - visibleRegionArea.top - popupOffsetY;
|
||||
|
||||
// When target if far away from visible area
|
||||
// Stop shift
|
||||
if (targetRect.y + targetHeight < visibleRegionArea.top + numShiftY) {
|
||||
nextOffsetY += targetRect.y - visibleRegionArea.top + targetHeight - numShiftY;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom
|
||||
if (nextPopupBottom > visibleRegionArea.bottom) {
|
||||
nextOffsetY -= nextPopupBottom - visibleRegionArea.bottom - popupOffsetY;
|
||||
if (targetRect.y > visibleRegionArea.bottom - numShiftY) {
|
||||
nextOffsetY += targetRect.y - visibleRegionArea.bottom + numShiftY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ Arrow ============================
|
||||
// Arrow center align
|
||||
const popupLeft = popupRect.x + nextOffsetX;
|
||||
const popupRight = popupLeft + popupWidth;
|
||||
const popupTop = popupRect.y + nextOffsetY;
|
||||
const popupBottom = popupTop + popupHeight;
|
||||
const targetLeft = targetRect.x;
|
||||
const targetRight = targetLeft + targetWidth;
|
||||
const targetTop = targetRect.y;
|
||||
const targetBottom = targetTop + targetHeight;
|
||||
|
||||
/** Max left of the popup and target element */
|
||||
const maxLeft = Math.max(popupLeft, targetLeft);
|
||||
/** Min right of the popup and target element */
|
||||
const minRight = Math.min(popupRight, targetRight);
|
||||
|
||||
/** The center X of popup & target cross area */
|
||||
const xCenter = (maxLeft + minRight) / 2;
|
||||
/** Arrow X of popup offset */
|
||||
const nextArrowX = xCenter - popupLeft;
|
||||
const maxTop = Math.max(popupTop, targetTop);
|
||||
const minBottom = Math.min(popupBottom, targetBottom);
|
||||
const yCenter = (maxTop + minBottom) / 2;
|
||||
const nextArrowY = yCenter - popupTop;
|
||||
onPopupAlign?.(popupEle, nextAlignInfo);
|
||||
|
||||
// Additional calculate right & bottom position
|
||||
let offsetX4Right = popupMirrorRect.right - popupRect.x - (nextOffsetX + popupRect.width);
|
||||
let offsetY4Bottom = popupMirrorRect.bottom - popupRect.y - (nextOffsetY + popupRect.height);
|
||||
if (scaleX === 1) {
|
||||
nextOffsetX = Math.floor(nextOffsetX);
|
||||
offsetX4Right = Math.floor(offsetX4Right);
|
||||
}
|
||||
if (scaleY === 1) {
|
||||
nextOffsetY = Math.floor(nextOffsetY);
|
||||
offsetY4Bottom = Math.floor(offsetY4Bottom);
|
||||
}
|
||||
const nextOffsetInfo = {
|
||||
ready: true,
|
||||
offsetX: nextOffsetX / scaleX,
|
||||
offsetY: nextOffsetY / scaleY,
|
||||
offsetR: offsetX4Right / scaleX,
|
||||
offsetB: offsetY4Bottom / scaleY,
|
||||
arrowX: nextArrowX / scaleX,
|
||||
arrowY: nextArrowY / scaleY,
|
||||
scaleX,
|
||||
scaleY,
|
||||
align: nextAlignInfo
|
||||
};
|
||||
setOffsetInfo(nextOffsetInfo);
|
||||
}
|
||||
});
|
||||
const triggerAlign = () => {
|
||||
alignCountRef.current += 1;
|
||||
const id = alignCountRef.current;
|
||||
|
||||
// Merge all align requirement into one frame
|
||||
Promise.resolve().then(() => {
|
||||
if (alignCountRef.current === id) {
|
||||
onAlign();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Reset ready status when placement & open changed
|
||||
const resetReady = () => {
|
||||
setOffsetInfo(ori => ({
|
||||
...ori,
|
||||
ready: false
|
||||
}));
|
||||
};
|
||||
useLayoutEffect(resetReady, [placement]);
|
||||
useLayoutEffect(() => {
|
||||
if (!open) {
|
||||
resetReady();
|
||||
}
|
||||
}, [open]);
|
||||
return [offsetInfo.ready, offsetInfo.offsetX, offsetInfo.offsetY, offsetInfo.offsetR, offsetInfo.offsetB, offsetInfo.arrowX, offsetInfo.arrowY, offsetInfo.scaleX, offsetInfo.scaleY, offsetInfo.align, triggerAlign];
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function useDelay(): (callback: VoidFunction, delay: number) => void;
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
export default function useDelay() {
|
||||
const delayRef = React.useRef(null);
|
||||
const clearDelay = () => {
|
||||
if (delayRef.current) {
|
||||
clearTimeout(delayRef.current);
|
||||
delayRef.current = null;
|
||||
}
|
||||
};
|
||||
const delayInvoke = (callback, delay) => {
|
||||
clearDelay();
|
||||
if (delay === 0) {
|
||||
callback();
|
||||
} else {
|
||||
delayRef.current = setTimeout(() => {
|
||||
callback();
|
||||
}, delay * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// Clean up on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
clearDelay();
|
||||
};
|
||||
}, []);
|
||||
return delayInvoke;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type * as React from 'react';
|
||||
import type { AlignType } from '../interface';
|
||||
export default function useOffsetStyle(isMobile: boolean, ready: boolean, open: boolean, align: AlignType, offsetR: number, offsetB: number, offsetX: number, offsetY: number): React.CSSProperties;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
export default function useOffsetStyle(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY) {
|
||||
// >>>>> Offset
|
||||
const AUTO = 'auto';
|
||||
const offsetStyle = isMobile ? {} : {
|
||||
left: '-1000vw',
|
||||
top: '-1000vh',
|
||||
right: AUTO,
|
||||
bottom: AUTO
|
||||
};
|
||||
|
||||
// Set align style
|
||||
if (!isMobile && (ready || !open)) {
|
||||
const {
|
||||
points
|
||||
} = align;
|
||||
const dynamicInset = align.dynamicInset || align._experimental?.dynamicInset;
|
||||
const alignRight = dynamicInset && points[0][1] === 'r';
|
||||
const alignBottom = dynamicInset && points[0][0] === 'b';
|
||||
if (alignRight) {
|
||||
offsetStyle.right = offsetR;
|
||||
offsetStyle.left = AUTO;
|
||||
} else {
|
||||
offsetStyle.left = offsetX;
|
||||
offsetStyle.right = AUTO;
|
||||
}
|
||||
if (alignBottom) {
|
||||
offsetStyle.bottom = offsetB;
|
||||
offsetStyle.top = AUTO;
|
||||
} else {
|
||||
offsetStyle.top = offsetY;
|
||||
offsetStyle.bottom = AUTO;
|
||||
}
|
||||
}
|
||||
return offsetStyle;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function useWatch(open: boolean, target: HTMLElement, popup: HTMLElement, onAlign: VoidFunction, onScroll: VoidFunction): void;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import { collectScroller, getWin } from "../util";
|
||||
export default function useWatch(open, target, popup, onAlign, onScroll) {
|
||||
useLayoutEffect(() => {
|
||||
if (open && target && popup) {
|
||||
const targetElement = target;
|
||||
const popupElement = popup;
|
||||
const targetScrollList = collectScroller(targetElement);
|
||||
const popupScrollList = collectScroller(popupElement);
|
||||
const win = getWin(popupElement);
|
||||
const mergedList = new Set([win, ...targetScrollList, ...popupScrollList]);
|
||||
function notifyScroll() {
|
||||
onAlign();
|
||||
onScroll();
|
||||
}
|
||||
mergedList.forEach(scroller => {
|
||||
scroller.addEventListener('scroll', notifyScroll, {
|
||||
passive: true
|
||||
});
|
||||
});
|
||||
win.addEventListener('resize', notifyScroll, {
|
||||
passive: true
|
||||
});
|
||||
|
||||
// First time always do align
|
||||
onAlign();
|
||||
return () => {
|
||||
mergedList.forEach(scroller => {
|
||||
scroller.removeEventListener('scroll', notifyScroll);
|
||||
win.removeEventListener('resize', notifyScroll);
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [open, target, popup]);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Close if click on the window.
|
||||
* Return the function that click on the Popup element.
|
||||
*/
|
||||
export default function useWinClick(open: boolean, clickToHide: boolean, targetEle: HTMLElement, popupEle: HTMLElement, mask: boolean, maskClosable: boolean, inPopupOrChild: (target: EventTarget) => boolean, triggerOpen: (open: boolean) => void): () => void;
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { getShadowRoot } from "@rc-component/util/es/Dom/shadow";
|
||||
import { warning } from "@rc-component/util/es/warning";
|
||||
import * as React from 'react';
|
||||
import { getWin } from "../util";
|
||||
|
||||
/**
|
||||
* Close if click on the window.
|
||||
* Return the function that click on the Popup element.
|
||||
*/
|
||||
export default function useWinClick(open, clickToHide, targetEle, popupEle, mask, maskClosable, inPopupOrChild, triggerOpen) {
|
||||
const openRef = React.useRef(open);
|
||||
openRef.current = open;
|
||||
const popupPointerDownRef = React.useRef(false);
|
||||
|
||||
// Click to hide is special action since click popup element should not hide
|
||||
React.useEffect(() => {
|
||||
if (clickToHide && popupEle && (!mask || maskClosable)) {
|
||||
const onPointerDown = () => {
|
||||
popupPointerDownRef.current = false;
|
||||
};
|
||||
const onTriggerClose = e => {
|
||||
if (openRef.current && !inPopupOrChild(e.composedPath?.()?.[0] || e.target) && !popupPointerDownRef.current) {
|
||||
triggerOpen(false);
|
||||
}
|
||||
};
|
||||
const win = getWin(popupEle);
|
||||
win.addEventListener('pointerdown', onPointerDown, true);
|
||||
win.addEventListener('mousedown', onTriggerClose, true);
|
||||
win.addEventListener('contextmenu', onTriggerClose, true);
|
||||
|
||||
// shadow root
|
||||
const targetShadowRoot = getShadowRoot(targetEle);
|
||||
if (targetShadowRoot) {
|
||||
targetShadowRoot.addEventListener('mousedown', onTriggerClose, true);
|
||||
targetShadowRoot.addEventListener('contextmenu', onTriggerClose, true);
|
||||
}
|
||||
|
||||
// Warning if target and popup not in same root
|
||||
if (process.env.NODE_ENV !== 'production' && targetEle) {
|
||||
const targetRoot = targetEle.getRootNode?.();
|
||||
const popupRoot = popupEle.getRootNode?.();
|
||||
warning(targetRoot === popupRoot, `trigger element and popup element should in same shadow root.`);
|
||||
}
|
||||
return () => {
|
||||
win.removeEventListener('pointerdown', onPointerDown, true);
|
||||
win.removeEventListener('mousedown', onTriggerClose, true);
|
||||
win.removeEventListener('contextmenu', onTriggerClose, true);
|
||||
if (targetShadowRoot) {
|
||||
targetShadowRoot.removeEventListener('mousedown', onTriggerClose, true);
|
||||
targetShadowRoot.removeEventListener('contextmenu', onTriggerClose, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [clickToHide, targetEle, popupEle, mask, maskClosable]);
|
||||
function onPopupPointerDown() {
|
||||
popupPointerDownRef.current = true;
|
||||
}
|
||||
return onPopupPointerDown;
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import * as React from 'react';
|
||||
import { type MobileConfig } from './Popup';
|
||||
import type { ActionType, AlignType, ArrowTypeOuter, BuildInPlacements } from './interface';
|
||||
export type { ActionType, AlignType, ArrowTypeOuter as ArrowType, BuildInPlacements, };
|
||||
import UniqueProvider, { type UniqueProviderProps } from './UniqueProvider';
|
||||
export { UniqueProvider };
|
||||
export type { UniqueProviderProps };
|
||||
export interface TriggerRef {
|
||||
nativeElement: HTMLElement;
|
||||
popupElement: HTMLDivElement;
|
||||
forceAlign: VoidFunction;
|
||||
}
|
||||
export interface TriggerProps {
|
||||
children: React.ReactElement<any> | ((info: {
|
||||
open: boolean;
|
||||
}) => React.ReactElement<any>);
|
||||
action?: ActionType | ActionType[];
|
||||
showAction?: ActionType[];
|
||||
hideAction?: ActionType[];
|
||||
prefixCls?: string;
|
||||
zIndex?: number;
|
||||
onPopupAlign?: (element: HTMLElement, align: AlignType) => void;
|
||||
stretch?: string;
|
||||
popupVisible?: boolean;
|
||||
defaultPopupVisible?: boolean;
|
||||
onOpenChange?: (visible: boolean) => void;
|
||||
afterOpenChange?: (visible: boolean) => void;
|
||||
/** @deprecated Use `onOpenChange` instead */
|
||||
onPopupVisibleChange?: (visible: boolean) => void;
|
||||
/** @deprecated Use `afterOpenChange` instead */
|
||||
afterPopupVisibleChange?: (visible: boolean) => void;
|
||||
getPopupContainer?: (node: HTMLElement) => HTMLElement;
|
||||
forceRender?: boolean;
|
||||
autoDestroy?: boolean;
|
||||
mask?: boolean;
|
||||
maskClosable?: boolean;
|
||||
/** Set popup motion. You can ref `rc-motion` for more info. */
|
||||
popupMotion?: CSSMotionProps;
|
||||
/** Set mask motion. You can ref `rc-motion` for more info. */
|
||||
maskMotion?: CSSMotionProps;
|
||||
mouseEnterDelay?: number;
|
||||
mouseLeaveDelay?: number;
|
||||
focusDelay?: number;
|
||||
blurDelay?: number;
|
||||
popup: React.ReactNode | (() => React.ReactNode);
|
||||
popupPlacement?: string;
|
||||
builtinPlacements?: BuildInPlacements;
|
||||
popupAlign?: AlignType;
|
||||
popupClassName?: string;
|
||||
/** Pass to `UniqueProvider` UniqueContainer */
|
||||
uniqueContainerClassName?: string;
|
||||
/** Pass to `UniqueProvider` UniqueContainer */
|
||||
uniqueContainerStyle?: React.CSSProperties;
|
||||
popupStyle?: React.CSSProperties;
|
||||
getPopupClassNameFromAlign?: (align: AlignType) => string;
|
||||
onPopupClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
alignPoint?: boolean;
|
||||
/**
|
||||
* Trigger will memo content when close.
|
||||
* This may affect the case if want to keep content update.
|
||||
* Set `fresh` to `false` will always keep update.
|
||||
*/
|
||||
fresh?: boolean;
|
||||
/**
|
||||
* Config with UniqueProvider to shared the floating popup.
|
||||
*/
|
||||
unique?: boolean;
|
||||
arrow?: boolean | ArrowTypeOuter;
|
||||
/**
|
||||
* @private Bump fixed position at bottom in mobile.
|
||||
* Will replace the config of root props.
|
||||
* This will directly trade as mobile view which will not check what real is.
|
||||
* This is internal usage currently, do not use in your prod.
|
||||
*/
|
||||
mobile?: MobileConfig;
|
||||
}
|
||||
export declare function generateTrigger(PortalComponent?: React.ComponentType<any>): React.ForwardRefExoticComponent<TriggerProps & React.RefAttributes<TriggerRef>>;
|
||||
declare const _default: React.ForwardRefExoticComponent<TriggerProps & React.RefAttributes<TriggerRef>>;
|
||||
export default _default;
|
||||
+562
@@ -0,0 +1,562 @@
|
||||
import Portal from '@rc-component/portal';
|
||||
import { clsx } from 'clsx';
|
||||
import { useResizeObserver } from '@rc-component/resize-observer';
|
||||
import { getDOM, isDOM } from "@rc-component/util/es/Dom/findDOMNode";
|
||||
import { getShadowRoot } from "@rc-component/util/es/Dom/shadow";
|
||||
import { getNodeRef, useComposeRef } from "@rc-component/util/es/ref";
|
||||
import useEvent from "@rc-component/util/es/hooks/useEvent";
|
||||
import useId from "@rc-component/util/es/hooks/useId";
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import * as React from 'react';
|
||||
import Popup from "./Popup";
|
||||
import TriggerContext, { UniqueContext } from "./context";
|
||||
import useAction from "./hooks/useAction";
|
||||
import useAlign from "./hooks/useAlign";
|
||||
import useDelay from "./hooks/useDelay";
|
||||
import useWatch from "./hooks/useWatch";
|
||||
import useWinClick from "./hooks/useWinClick";
|
||||
import { getAlignPopupClassName } from "./util";
|
||||
import UniqueProvider from "./UniqueProvider";
|
||||
import { useControlledState } from '@rc-component/util';
|
||||
import { flushSync } from 'react-dom';
|
||||
export { UniqueProvider };
|
||||
|
||||
// Removed Props List
|
||||
// Seems this can be auto
|
||||
// getDocument?: (element?: HTMLElement) => Document;
|
||||
|
||||
// New version will not wrap popup with `rc-trigger-popup-content` when multiple children
|
||||
|
||||
export function generateTrigger(PortalComponent = Portal) {
|
||||
const Trigger = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
prefixCls = 'rc-trigger-popup',
|
||||
children,
|
||||
// Action
|
||||
action = 'hover',
|
||||
showAction,
|
||||
hideAction,
|
||||
// Open
|
||||
popupVisible,
|
||||
defaultPopupVisible,
|
||||
onOpenChange,
|
||||
afterOpenChange,
|
||||
onPopupVisibleChange,
|
||||
afterPopupVisibleChange,
|
||||
// Delay
|
||||
mouseEnterDelay,
|
||||
mouseLeaveDelay = 0.1,
|
||||
focusDelay,
|
||||
blurDelay,
|
||||
// Mask
|
||||
mask,
|
||||
maskClosable = true,
|
||||
// Portal
|
||||
getPopupContainer,
|
||||
forceRender,
|
||||
autoDestroy,
|
||||
// Popup
|
||||
popup,
|
||||
popupClassName,
|
||||
uniqueContainerClassName,
|
||||
uniqueContainerStyle,
|
||||
popupStyle,
|
||||
popupPlacement,
|
||||
builtinPlacements = {},
|
||||
popupAlign,
|
||||
zIndex,
|
||||
stretch,
|
||||
getPopupClassNameFromAlign,
|
||||
fresh,
|
||||
unique,
|
||||
alignPoint,
|
||||
onPopupClick,
|
||||
onPopupAlign,
|
||||
// Arrow
|
||||
arrow,
|
||||
// Motion
|
||||
popupMotion,
|
||||
maskMotion,
|
||||
// Private
|
||||
mobile,
|
||||
...restProps
|
||||
} = props;
|
||||
const mergedAutoDestroy = autoDestroy || false;
|
||||
const openUncontrolled = popupVisible === undefined;
|
||||
|
||||
// =========================== Mobile ===========================
|
||||
const isMobile = !!mobile;
|
||||
|
||||
// ========================== Context ===========================
|
||||
const subPopupElements = React.useRef({});
|
||||
const parentContext = React.useContext(TriggerContext);
|
||||
const context = React.useMemo(() => {
|
||||
return {
|
||||
registerSubPopup: (id, subPopupEle) => {
|
||||
subPopupElements.current[id] = subPopupEle;
|
||||
parentContext?.registerSubPopup(id, subPopupEle);
|
||||
}
|
||||
};
|
||||
}, [parentContext]);
|
||||
|
||||
// ======================== UniqueContext =========================
|
||||
const uniqueContext = React.useContext(UniqueContext);
|
||||
|
||||
// =========================== Popup ============================
|
||||
const id = useId();
|
||||
const [popupEle, setPopupEle] = React.useState(null);
|
||||
|
||||
// Used for forwardRef popup. Not use internal
|
||||
const externalPopupRef = React.useRef(null);
|
||||
const setPopupRef = useEvent(node => {
|
||||
externalPopupRef.current = node;
|
||||
if (isDOM(node) && popupEle !== node) {
|
||||
setPopupEle(node);
|
||||
}
|
||||
parentContext?.registerSubPopup(id, node);
|
||||
});
|
||||
|
||||
// =========================== Target ===========================
|
||||
// Use state to control here since `useRef` update not trigger render
|
||||
const [targetEle, setTargetEle] = React.useState(null);
|
||||
|
||||
// Used for forwardRef target. Not use internal
|
||||
const externalForwardRef = React.useRef(null);
|
||||
const setTargetRef = useEvent(node => {
|
||||
const domNode = getDOM(node);
|
||||
if (isDOM(domNode) && targetEle !== domNode) {
|
||||
setTargetEle(domNode);
|
||||
externalForwardRef.current = domNode;
|
||||
}
|
||||
});
|
||||
const cloneProps = {};
|
||||
const inPopupOrChild = useEvent(ele => {
|
||||
const childDOM = targetEle;
|
||||
return childDOM?.contains(ele) || getShadowRoot(childDOM)?.host === ele || ele === childDOM || popupEle?.contains(ele) || getShadowRoot(popupEle)?.host === ele || ele === popupEle || Object.values(subPopupElements.current).some(subPopupEle => subPopupEle?.contains(ele) || ele === subPopupEle);
|
||||
});
|
||||
|
||||
// =========================== Arrow ============================
|
||||
const innerArrow = arrow ? {
|
||||
// true and Object likely
|
||||
...(arrow !== true ? arrow : {})
|
||||
} : null;
|
||||
|
||||
// ============================ Open ============================
|
||||
const [internalOpen, setInternalOpen] = useControlledState(defaultPopupVisible || false, popupVisible);
|
||||
const mergedOpen = internalOpen || false;
|
||||
|
||||
// ========================== Children ==========================
|
||||
const child = React.useMemo(() => {
|
||||
const nextChild = typeof children === 'function' ? children({
|
||||
open: mergedOpen
|
||||
}) : children;
|
||||
return React.Children.only(nextChild);
|
||||
}, [children, mergedOpen]);
|
||||
const originChildProps = child?.props || {};
|
||||
|
||||
// Support ref
|
||||
const isOpen = useEvent(() => mergedOpen);
|
||||
|
||||
// Extract common options for UniqueProvider
|
||||
const getUniqueOptions = useEvent((delay = 0) => ({
|
||||
popup,
|
||||
target: targetEle,
|
||||
delay,
|
||||
prefixCls,
|
||||
popupClassName,
|
||||
uniqueContainerClassName,
|
||||
uniqueContainerStyle,
|
||||
popupStyle,
|
||||
popupPlacement,
|
||||
builtinPlacements,
|
||||
popupAlign,
|
||||
zIndex,
|
||||
mask,
|
||||
maskClosable,
|
||||
popupMotion,
|
||||
maskMotion,
|
||||
arrow: innerArrow,
|
||||
getPopupContainer,
|
||||
getPopupClassNameFromAlign,
|
||||
id,
|
||||
onEsc
|
||||
}));
|
||||
|
||||
// Handle controlled state changes for UniqueProvider
|
||||
// Only sync to UniqueProvider when it's controlled mode
|
||||
// If there is a parentContext, don't call uniqueContext methods
|
||||
useLayoutEffect(() => {
|
||||
if (uniqueContext && unique && targetEle && !openUncontrolled && !parentContext) {
|
||||
if (mergedOpen) {
|
||||
uniqueContext.show(getUniqueOptions(mouseEnterDelay), isOpen);
|
||||
} else {
|
||||
uniqueContext.hide(mouseLeaveDelay);
|
||||
}
|
||||
}
|
||||
}, [mergedOpen, targetEle]);
|
||||
const openRef = React.useRef(mergedOpen);
|
||||
openRef.current = mergedOpen;
|
||||
const internalTriggerOpen = useEvent(nextOpen => {
|
||||
flushSync(() => {
|
||||
if (mergedOpen !== nextOpen) {
|
||||
setInternalOpen(nextOpen);
|
||||
onOpenChange?.(nextOpen);
|
||||
onPopupVisibleChange?.(nextOpen);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger for delay
|
||||
const delayInvoke = useDelay();
|
||||
const triggerOpen = (nextOpen, delay = 0) => {
|
||||
// If it's controlled mode, always use internal trigger logic
|
||||
// UniqueProvider will be synced through useLayoutEffect
|
||||
if (popupVisible !== undefined) {
|
||||
delayInvoke(() => {
|
||||
internalTriggerOpen(nextOpen);
|
||||
}, delay);
|
||||
return;
|
||||
}
|
||||
|
||||
// If UniqueContext exists and not controlled, pass delay to Provider instead of handling it internally
|
||||
// If there is a parentContext, don't call uniqueContext methods
|
||||
if (uniqueContext && unique && openUncontrolled && !parentContext) {
|
||||
if (nextOpen) {
|
||||
uniqueContext.show(getUniqueOptions(delay), isOpen);
|
||||
} else {
|
||||
uniqueContext.hide(delay);
|
||||
}
|
||||
return;
|
||||
}
|
||||
delayInvoke(() => {
|
||||
internalTriggerOpen(nextOpen);
|
||||
}, delay);
|
||||
};
|
||||
function onEsc({
|
||||
top
|
||||
}) {
|
||||
if (top) {
|
||||
triggerOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================== Motion ============================
|
||||
const [inMotion, setInMotion] = React.useState(false);
|
||||
useLayoutEffect(firstMount => {
|
||||
if (!firstMount || mergedOpen) {
|
||||
setInMotion(true);
|
||||
}
|
||||
}, [mergedOpen]);
|
||||
const [motionPrepareResolve, setMotionPrepareResolve] = React.useState(null);
|
||||
|
||||
// =========================== Align ============================
|
||||
const [mousePos, setMousePos] = React.useState(null);
|
||||
const setMousePosByEvent = event => {
|
||||
setMousePos([event.clientX, event.clientY]);
|
||||
};
|
||||
const [ready, offsetX, offsetY, offsetR, offsetB, arrowX, arrowY, scaleX, scaleY, alignInfo, onAlign] = useAlign(mergedOpen, popupEle, alignPoint && mousePos !== null ? mousePos : targetEle, popupPlacement, builtinPlacements, popupAlign, onPopupAlign, isMobile);
|
||||
const [showActions, hideActions] = useAction(action, showAction, hideAction);
|
||||
const clickToShow = showActions.has('click');
|
||||
const clickToHide = hideActions.has('click') || hideActions.has('contextMenu');
|
||||
const triggerAlign = useEvent(() => {
|
||||
if (!inMotion) {
|
||||
onAlign();
|
||||
}
|
||||
});
|
||||
const onScroll = () => {
|
||||
if (openRef.current && alignPoint && clickToHide) {
|
||||
triggerOpen(false);
|
||||
}
|
||||
};
|
||||
useWatch(mergedOpen, targetEle, popupEle, triggerAlign, onScroll);
|
||||
useLayoutEffect(() => {
|
||||
triggerAlign();
|
||||
}, [mousePos, popupPlacement]);
|
||||
|
||||
// When no builtinPlacements and popupAlign changed
|
||||
useLayoutEffect(() => {
|
||||
if (mergedOpen && !builtinPlacements?.[popupPlacement]) {
|
||||
triggerAlign();
|
||||
}
|
||||
}, [JSON.stringify(popupAlign)]);
|
||||
const alignedClassName = React.useMemo(() => {
|
||||
const baseClassName = getAlignPopupClassName(builtinPlacements, prefixCls, alignInfo, alignPoint);
|
||||
return clsx(baseClassName, getPopupClassNameFromAlign?.(alignInfo));
|
||||
}, [alignInfo, getPopupClassNameFromAlign, builtinPlacements, prefixCls, alignPoint]);
|
||||
|
||||
// ============================ Refs ============================
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
nativeElement: externalForwardRef.current,
|
||||
popupElement: externalPopupRef.current,
|
||||
forceAlign: triggerAlign
|
||||
}));
|
||||
|
||||
// ========================== Stretch ===========================
|
||||
const [targetWidth, setTargetWidth] = React.useState(0);
|
||||
const [targetHeight, setTargetHeight] = React.useState(0);
|
||||
const syncTargetSize = () => {
|
||||
if (stretch && targetEle) {
|
||||
const rect = targetEle.getBoundingClientRect();
|
||||
setTargetWidth(rect.width);
|
||||
setTargetHeight(rect.height);
|
||||
}
|
||||
};
|
||||
const onTargetResize = () => {
|
||||
syncTargetSize();
|
||||
triggerAlign();
|
||||
};
|
||||
|
||||
// ========================== Motion ============================
|
||||
const onVisibleChanged = visible => {
|
||||
setInMotion(false);
|
||||
onAlign();
|
||||
afterOpenChange?.(visible);
|
||||
afterPopupVisibleChange?.(visible);
|
||||
};
|
||||
|
||||
// We will trigger align when motion is in prepare
|
||||
const onPrepare = () => new Promise(resolve => {
|
||||
syncTargetSize();
|
||||
setMotionPrepareResolve(() => resolve);
|
||||
});
|
||||
useLayoutEffect(() => {
|
||||
if (motionPrepareResolve) {
|
||||
onAlign();
|
||||
motionPrepareResolve();
|
||||
setMotionPrepareResolve(null);
|
||||
}
|
||||
}, [motionPrepareResolve]);
|
||||
|
||||
// =========================== Action ===========================
|
||||
/**
|
||||
* Util wrapper for trigger action
|
||||
* @param eventName Listen event name
|
||||
* @param nextOpen Next open state after trigger
|
||||
* @param delay Delay to trigger open change
|
||||
* @param callback Callback if current event need additional action
|
||||
* @param ignoreCheck Ignore current event if check return true
|
||||
*/
|
||||
function wrapperAction(eventName, nextOpen, delay, callback, ignoreCheck) {
|
||||
cloneProps[eventName] = (event, ...args) => {
|
||||
if (!ignoreCheck || !ignoreCheck()) {
|
||||
callback?.(event);
|
||||
triggerOpen(nextOpen, delay);
|
||||
}
|
||||
|
||||
// Pass to origin
|
||||
originChildProps[eventName]?.(event, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
// ======================= Action: Touch ========================
|
||||
const touchToShow = showActions.has('touch');
|
||||
const touchToHide = hideActions.has('touch');
|
||||
|
||||
/** Used for prevent `hover` event conflict with mobile env */
|
||||
const touchedRef = React.useRef(false);
|
||||
if (touchToShow || touchToHide) {
|
||||
cloneProps.onTouchStart = (...args) => {
|
||||
touchedRef.current = true;
|
||||
if (openRef.current && touchToHide) {
|
||||
triggerOpen(false);
|
||||
} else if (!openRef.current && touchToShow) {
|
||||
triggerOpen(true);
|
||||
}
|
||||
|
||||
// Pass to origin
|
||||
originChildProps.onTouchStart?.(...args);
|
||||
};
|
||||
}
|
||||
|
||||
// ======================= Action: Click ========================
|
||||
if (clickToShow || clickToHide) {
|
||||
cloneProps.onClick = (event, ...args) => {
|
||||
if (openRef.current && clickToHide) {
|
||||
triggerOpen(false);
|
||||
} else if (!openRef.current && clickToShow) {
|
||||
setMousePosByEvent(event);
|
||||
triggerOpen(true);
|
||||
}
|
||||
|
||||
// Pass to origin
|
||||
originChildProps.onClick?.(event, ...args);
|
||||
touchedRef.current = false;
|
||||
};
|
||||
}
|
||||
|
||||
// Click to hide is special action since click popup element should not hide
|
||||
const onPopupPointerDown = useWinClick(mergedOpen, clickToHide || touchToHide, targetEle, popupEle, mask, maskClosable, inPopupOrChild, triggerOpen);
|
||||
|
||||
// ======================= Action: Hover ========================
|
||||
const hoverToShow = showActions.has('hover');
|
||||
const hoverToHide = hideActions.has('hover');
|
||||
let onPopupMouseEnter;
|
||||
let onPopupMouseLeave;
|
||||
const ignoreMouseTrigger = () => {
|
||||
return touchedRef.current;
|
||||
};
|
||||
if (hoverToShow) {
|
||||
const onMouseEnterCallback = event => {
|
||||
setMousePosByEvent(event);
|
||||
};
|
||||
|
||||
// Compatible with old browser which not support pointer event
|
||||
wrapperAction('onMouseEnter', true, mouseEnterDelay, onMouseEnterCallback, ignoreMouseTrigger);
|
||||
wrapperAction('onPointerEnter', true, mouseEnterDelay, onMouseEnterCallback, ignoreMouseTrigger);
|
||||
onPopupMouseEnter = event => {
|
||||
// Only trigger re-open when popup is visible
|
||||
if ((mergedOpen || inMotion) && popupEle?.contains(event.target)) {
|
||||
triggerOpen(true, mouseEnterDelay);
|
||||
}
|
||||
};
|
||||
|
||||
// Align Point
|
||||
if (alignPoint) {
|
||||
cloneProps.onMouseMove = event => {
|
||||
originChildProps.onMouseMove?.(event);
|
||||
};
|
||||
}
|
||||
}
|
||||
if (hoverToHide) {
|
||||
wrapperAction('onMouseLeave', false, mouseLeaveDelay, undefined, ignoreMouseTrigger);
|
||||
wrapperAction('onPointerLeave', false, mouseLeaveDelay, undefined, ignoreMouseTrigger);
|
||||
onPopupMouseLeave = () => {
|
||||
triggerOpen(false, mouseLeaveDelay);
|
||||
};
|
||||
}
|
||||
|
||||
// ======================= Action: Focus ========================
|
||||
if (showActions.has('focus')) {
|
||||
wrapperAction('onFocus', true, focusDelay);
|
||||
}
|
||||
if (hideActions.has('focus')) {
|
||||
wrapperAction('onBlur', false, blurDelay);
|
||||
}
|
||||
|
||||
// ==================== Action: ContextMenu =====================
|
||||
if (showActions.has('contextMenu')) {
|
||||
cloneProps.onContextMenu = (event, ...args) => {
|
||||
if (openRef.current && hideActions.has('contextMenu')) {
|
||||
triggerOpen(false);
|
||||
} else {
|
||||
setMousePosByEvent(event);
|
||||
triggerOpen(true);
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
// Pass to origin
|
||||
originChildProps.onContextMenu?.(event, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
// ============================ Perf ============================
|
||||
const rendedRef = React.useRef(false);
|
||||
rendedRef.current ||= forceRender || mergedOpen || inMotion;
|
||||
|
||||
// =========================== Render ===========================
|
||||
const mergedChildrenProps = {
|
||||
...originChildProps,
|
||||
...cloneProps
|
||||
};
|
||||
|
||||
// Pass props into cloneProps for nest usage
|
||||
const passedProps = {};
|
||||
const passedEventList = ['onContextMenu', 'onClick', 'onMouseDown', 'onTouchStart', 'onMouseEnter', 'onMouseLeave', 'onFocus', 'onBlur'];
|
||||
passedEventList.forEach(eventName => {
|
||||
if (restProps[eventName]) {
|
||||
passedProps[eventName] = (...args) => {
|
||||
mergedChildrenProps[eventName]?.(...args);
|
||||
restProps[eventName](...args);
|
||||
};
|
||||
}
|
||||
});
|
||||
const arrowPos = {
|
||||
x: arrowX,
|
||||
y: arrowY
|
||||
};
|
||||
|
||||
// =================== Resize Observer ===================
|
||||
// Use hook to observe target element resize
|
||||
// Pass targetEle directly instead of a function so the hook will re-observe when target changes
|
||||
useResizeObserver(mergedOpen, targetEle, onTargetResize);
|
||||
|
||||
// Compose refs
|
||||
const mergedRef = useComposeRef(setTargetRef, getNodeRef(child));
|
||||
|
||||
// Child Node
|
||||
const triggerNode = /*#__PURE__*/React.cloneElement(child, {
|
||||
...mergedChildrenProps,
|
||||
...passedProps,
|
||||
ref: mergedRef
|
||||
});
|
||||
|
||||
// Render
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, triggerNode, rendedRef.current && (!uniqueContext || !unique) && /*#__PURE__*/React.createElement(TriggerContext.Provider, {
|
||||
value: context
|
||||
}, /*#__PURE__*/React.createElement(Popup, {
|
||||
portal: PortalComponent,
|
||||
ref: setPopupRef,
|
||||
prefixCls: prefixCls,
|
||||
popup: popup,
|
||||
className: clsx(popupClassName, !isMobile && alignedClassName),
|
||||
style: popupStyle,
|
||||
target: targetEle,
|
||||
onMouseEnter: onPopupMouseEnter,
|
||||
onMouseLeave: onPopupMouseLeave
|
||||
// https://github.com/ant-design/ant-design/issues/43924
|
||||
,
|
||||
onPointerEnter: onPopupMouseEnter,
|
||||
zIndex: zIndex
|
||||
// Open
|
||||
,
|
||||
open: mergedOpen,
|
||||
keepDom: inMotion,
|
||||
fresh: fresh
|
||||
// Click
|
||||
,
|
||||
onClick: onPopupClick,
|
||||
onPointerDownCapture: onPopupPointerDown
|
||||
// Mask
|
||||
,
|
||||
mask: mask
|
||||
// Motion
|
||||
,
|
||||
motion: popupMotion,
|
||||
maskMotion: maskMotion,
|
||||
onVisibleChanged: onVisibleChanged,
|
||||
onPrepare: onPrepare
|
||||
// Portal
|
||||
,
|
||||
forceRender: forceRender,
|
||||
autoDestroy: mergedAutoDestroy,
|
||||
getPopupContainer: getPopupContainer,
|
||||
onEsc: onEsc
|
||||
// Arrow
|
||||
,
|
||||
align: alignInfo,
|
||||
arrow: innerArrow,
|
||||
arrowPos: arrowPos
|
||||
// Align
|
||||
,
|
||||
ready: ready,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
offsetR: offsetR,
|
||||
offsetB: offsetB,
|
||||
onAlign: triggerAlign
|
||||
// Stretch
|
||||
,
|
||||
stretch: stretch,
|
||||
targetWidth: targetWidth / scaleX,
|
||||
targetHeight: targetHeight / scaleY
|
||||
// Mobile
|
||||
,
|
||||
mobile: mobile
|
||||
})));
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Trigger.displayName = 'Trigger';
|
||||
}
|
||||
return Trigger;
|
||||
}
|
||||
export default generateTrigger(Portal);
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/// <reference types="react" />
|
||||
export type Placement = 'top' | 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
|
||||
export type AlignPointTopBottom = 't' | 'b' | 'c';
|
||||
export type AlignPointLeftRight = 'l' | 'r' | 'c';
|
||||
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
|
||||
export type AlignPoint = `${AlignPointTopBottom}${AlignPointLeftRight}`;
|
||||
export type OffsetType = number | `${number}%`;
|
||||
export interface AlignType {
|
||||
/**
|
||||
* move point of source node to align with point of target node.
|
||||
* Such as ['tr','cc'], align top right point of source node with center point of target node.
|
||||
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
|
||||
points?: (string | AlignPoint)[];
|
||||
/**
|
||||
* @private Do not use in your production code
|
||||
*/
|
||||
_experimental?: Record<string, any>;
|
||||
/**
|
||||
* offset source node by offset[0] in x and offset[1] in y.
|
||||
* If offset contains percentage string value, it is relative to sourceNode region.
|
||||
*/
|
||||
offset?: OffsetType[];
|
||||
/**
|
||||
* offset target node by offset[0] in x and offset[1] in y.
|
||||
* If targetOffset contains percentage string value, it is relative to targetNode region.
|
||||
*/
|
||||
targetOffset?: OffsetType[];
|
||||
/**
|
||||
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
|
||||
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
|
||||
*/
|
||||
overflow?: {
|
||||
adjustX?: boolean | number;
|
||||
adjustY?: boolean | number;
|
||||
shiftX?: boolean | number;
|
||||
shiftY?: boolean | number;
|
||||
};
|
||||
/** Auto adjust arrow position */
|
||||
autoArrow?: boolean;
|
||||
/**
|
||||
* Config visible region check of html node. Default `visible`:
|
||||
* - `visible`:
|
||||
* The visible region of user browser window.
|
||||
* Use `clientHeight` for check.
|
||||
* If `visible` region not satisfy, fallback to `scroll`.
|
||||
* - `scroll`:
|
||||
* The whole region of the html scroll area.
|
||||
* Use `scrollHeight` for check.
|
||||
* - `visibleFirst`:
|
||||
* Similar to `visible`, but if `visible` region not satisfy, fallback to `scroll`.
|
||||
*/
|
||||
htmlRegion?: 'visible' | 'scroll' | 'visibleFirst';
|
||||
/**
|
||||
* Auto chose position with `top` or `bottom` by the align result
|
||||
*/
|
||||
dynamicInset?: boolean;
|
||||
/**
|
||||
* Whether use css right instead of left to position
|
||||
*/
|
||||
useCssRight?: boolean;
|
||||
/**
|
||||
* Whether use css bottom instead of top to position
|
||||
*/
|
||||
useCssBottom?: boolean;
|
||||
/**
|
||||
* Whether use css transform instead of left/top/right/bottom to position if browser supports.
|
||||
* Defaults to false.
|
||||
*/
|
||||
useCssTransform?: boolean;
|
||||
ignoreShake?: boolean;
|
||||
}
|
||||
export interface ArrowTypeOuter {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
content?: React.ReactNode;
|
||||
}
|
||||
export type ArrowPos = {
|
||||
x?: number;
|
||||
y?: number;
|
||||
};
|
||||
export type BuildInPlacements = Record<string, AlignType>;
|
||||
export type StretchType = string;
|
||||
export type ActionType = 'hover' | 'focus' | 'click' | 'contextMenu';
|
||||
export type AnimationType = string;
|
||||
export type TransitionNameType = string;
|
||||
export interface Point {
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
}
|
||||
export interface CommonEventHandler {
|
||||
remove: () => void;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { UniqueProvider } from './index';
|
||||
declare const _default: React.ForwardRefExoticComponent<import("./index").TriggerProps & React.RefAttributes<import("./index").TriggerRef>>;
|
||||
export default _default;
|
||||
export { UniqueProvider };
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { generateTrigger, UniqueProvider } from "./index";
|
||||
const MockPortal = ({
|
||||
open,
|
||||
autoDestroy,
|
||||
children,
|
||||
getContainer
|
||||
}) => {
|
||||
const [visible, setVisible] = React.useState(open);
|
||||
React.useEffect(() => {
|
||||
getContainer?.();
|
||||
});
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
setVisible(true);
|
||||
} else if (autoDestroy) {
|
||||
setVisible(false);
|
||||
}
|
||||
}, [open, autoDestroy]);
|
||||
return visible ? children : null;
|
||||
};
|
||||
export default generateTrigger(MockPortal);
|
||||
export { UniqueProvider };
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import type { AlignType, BuildInPlacements } from './interface';
|
||||
export declare function getAlignPopupClassName(builtinPlacements: BuildInPlacements, prefixCls: string, align: AlignType, isAlignPoint: boolean): string;
|
||||
export declare function getWin(ele: HTMLElement): Window & typeof globalThis;
|
||||
/**
|
||||
* Get all the scrollable parent elements of the element
|
||||
* @param ele The element to be detected
|
||||
* @param areaOnly Only return the parent which will cut visible area
|
||||
*/
|
||||
export declare function collectScroller(ele: HTMLElement): HTMLElement[];
|
||||
export declare function toNum(num: number, defaultValue?: number): number;
|
||||
export interface VisibleArea {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* **************************************
|
||||
* * Border *
|
||||
* * ************************** *
|
||||
* * * * * *
|
||||
* * B * * S * B *
|
||||
* * o * * c * o *
|
||||
* * r * Content * r * r *
|
||||
* * d * * o * d *
|
||||
* * e * * l * e *
|
||||
* * r ******************** l * r *
|
||||
* * * Scroll * *
|
||||
* * ************************** *
|
||||
* * Border *
|
||||
* **************************************
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Get visible area of element
|
||||
*/
|
||||
export declare function getVisibleArea(initArea: VisibleArea, scrollerList?: HTMLElement[]): {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
};
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
function isPointsEq(a1 = [], a2 = [], isAlignPoint) {
|
||||
const getVal = (a, index) => a[index] || '';
|
||||
if (isAlignPoint) {
|
||||
return getVal(a1, 0) === getVal(a2, 0);
|
||||
}
|
||||
return getVal(a1, 0) === getVal(a2, 0) && getVal(a1, 1) === getVal(a2, 1);
|
||||
}
|
||||
export function getAlignPopupClassName(builtinPlacements, prefixCls, align, isAlignPoint) {
|
||||
const {
|
||||
points
|
||||
} = align;
|
||||
const placements = Object.keys(builtinPlacements);
|
||||
for (let i = 0; i < placements.length; i += 1) {
|
||||
const placement = placements[i];
|
||||
if (isPointsEq(builtinPlacements[placement]?.points, points, isAlignPoint)) {
|
||||
return `${prefixCls}-placement-${placement}`;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
export function getWin(ele) {
|
||||
return ele.ownerDocument.defaultView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the scrollable parent elements of the element
|
||||
* @param ele The element to be detected
|
||||
* @param areaOnly Only return the parent which will cut visible area
|
||||
*/
|
||||
export function collectScroller(ele) {
|
||||
const scrollerList = [];
|
||||
let current = ele?.parentElement;
|
||||
const scrollStyle = ['hidden', 'scroll', 'clip', 'auto'];
|
||||
while (current) {
|
||||
const {
|
||||
overflowX,
|
||||
overflowY,
|
||||
overflow
|
||||
} = getWin(current).getComputedStyle(current);
|
||||
if ([overflowX, overflowY, overflow].some(o => scrollStyle.includes(o))) {
|
||||
scrollerList.push(current);
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
return scrollerList;
|
||||
}
|
||||
export function toNum(num, defaultValue = 1) {
|
||||
return Number.isNaN(num) ? defaultValue : num;
|
||||
}
|
||||
function getPxValue(val) {
|
||||
return toNum(parseFloat(val), 0);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* **************************************
|
||||
* * Border *
|
||||
* * ************************** *
|
||||
* * * * * *
|
||||
* * B * * S * B *
|
||||
* * o * * c * o *
|
||||
* * r * Content * r * r *
|
||||
* * d * * o * d *
|
||||
* * e * * l * e *
|
||||
* * r ******************** l * r *
|
||||
* * * Scroll * *
|
||||
* * ************************** *
|
||||
* * Border *
|
||||
* **************************************
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Get visible area of element
|
||||
*/
|
||||
export function getVisibleArea(initArea, scrollerList) {
|
||||
const visibleArea = {
|
||||
...initArea
|
||||
};
|
||||
(scrollerList || []).forEach(ele => {
|
||||
if (ele instanceof HTMLBodyElement || ele instanceof HTMLHtmlElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if static position which will not affect visible area
|
||||
const {
|
||||
overflow,
|
||||
overflowClipMargin,
|
||||
borderTopWidth,
|
||||
borderBottomWidth,
|
||||
borderLeftWidth,
|
||||
borderRightWidth
|
||||
} = getWin(ele).getComputedStyle(ele);
|
||||
const eleRect = ele.getBoundingClientRect();
|
||||
const {
|
||||
offsetHeight: eleOutHeight,
|
||||
clientHeight: eleInnerHeight,
|
||||
offsetWidth: eleOutWidth,
|
||||
clientWidth: eleInnerWidth
|
||||
} = ele;
|
||||
const borderTopNum = getPxValue(borderTopWidth);
|
||||
const borderBottomNum = getPxValue(borderBottomWidth);
|
||||
const borderLeftNum = getPxValue(borderLeftWidth);
|
||||
const borderRightNum = getPxValue(borderRightWidth);
|
||||
const scaleX = toNum(Math.round(eleRect.width / eleOutWidth * 1000) / 1000);
|
||||
const scaleY = toNum(Math.round(eleRect.height / eleOutHeight * 1000) / 1000);
|
||||
|
||||
// Original visible area
|
||||
const eleScrollWidth = (eleOutWidth - eleInnerWidth - borderLeftNum - borderRightNum) * scaleX;
|
||||
const eleScrollHeight = (eleOutHeight - eleInnerHeight - borderTopNum - borderBottomNum) * scaleY;
|
||||
|
||||
// Cut border size
|
||||
const scaledBorderTopWidth = borderTopNum * scaleY;
|
||||
const scaledBorderBottomWidth = borderBottomNum * scaleY;
|
||||
const scaledBorderLeftWidth = borderLeftNum * scaleX;
|
||||
const scaledBorderRightWidth = borderRightNum * scaleX;
|
||||
|
||||
// Clip margin
|
||||
let clipMarginWidth = 0;
|
||||
let clipMarginHeight = 0;
|
||||
if (overflow === 'clip') {
|
||||
const clipNum = getPxValue(overflowClipMargin);
|
||||
clipMarginWidth = clipNum * scaleX;
|
||||
clipMarginHeight = clipNum * scaleY;
|
||||
}
|
||||
|
||||
// Region
|
||||
const eleLeft = eleRect.x + scaledBorderLeftWidth - clipMarginWidth;
|
||||
const eleTop = eleRect.y + scaledBorderTopWidth - clipMarginHeight;
|
||||
const eleRight = eleLeft + eleRect.width + 2 * clipMarginWidth - scaledBorderLeftWidth - scaledBorderRightWidth - eleScrollWidth;
|
||||
const eleBottom = eleTop + eleRect.height + 2 * clipMarginHeight - scaledBorderTopWidth - scaledBorderBottomWidth - eleScrollHeight;
|
||||
visibleArea.left = Math.max(visibleArea.left, eleLeft);
|
||||
visibleArea.top = Math.max(visibleArea.top, eleTop);
|
||||
visibleArea.right = Math.min(visibleArea.right, eleRight);
|
||||
visibleArea.bottom = Math.min(visibleArea.bottom, eleBottom);
|
||||
});
|
||||
return visibleArea;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
|
||||
export interface ArrowProps {
|
||||
prefixCls: string;
|
||||
align: AlignType;
|
||||
arrow: ArrowTypeOuter;
|
||||
arrowPos: ArrowPos;
|
||||
}
|
||||
export default function Arrow(props: ArrowProps): React.JSX.Element;
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = Arrow;
|
||||
var _clsx = require("clsx");
|
||||
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 Arrow(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
align,
|
||||
arrow,
|
||||
arrowPos
|
||||
} = props;
|
||||
const {
|
||||
className,
|
||||
content,
|
||||
style
|
||||
} = arrow || {};
|
||||
const {
|
||||
x = 0,
|
||||
y = 0
|
||||
} = arrowPos;
|
||||
const arrowRef = React.useRef(null);
|
||||
|
||||
// Skip if no align
|
||||
if (!align || !align.points) {
|
||||
return null;
|
||||
}
|
||||
const alignStyle = {
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
// Skip if no need to align
|
||||
if (align.autoArrow !== false) {
|
||||
const popupPoints = align.points[0];
|
||||
const targetPoints = align.points[1];
|
||||
const popupTB = popupPoints[0];
|
||||
const popupLR = popupPoints[1];
|
||||
const targetTB = targetPoints[0];
|
||||
const targetLR = targetPoints[1];
|
||||
|
||||
// Top & Bottom
|
||||
if (popupTB === targetTB || !['t', 'b'].includes(popupTB)) {
|
||||
alignStyle.top = y;
|
||||
} else if (popupTB === 't') {
|
||||
alignStyle.top = 0;
|
||||
} else {
|
||||
alignStyle.bottom = 0;
|
||||
}
|
||||
|
||||
// Left & Right
|
||||
if (popupLR === targetLR || !['l', 'r'].includes(popupLR)) {
|
||||
alignStyle.left = x;
|
||||
} else if (popupLR === 'l') {
|
||||
alignStyle.left = 0;
|
||||
} else {
|
||||
alignStyle.right = 0;
|
||||
}
|
||||
}
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
ref: arrowRef,
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-arrow`, className),
|
||||
style: {
|
||||
...alignStyle,
|
||||
...style
|
||||
}
|
||||
}, content);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import * as React from 'react';
|
||||
export interface MaskProps {
|
||||
prefixCls: string;
|
||||
open?: boolean;
|
||||
zIndex?: number;
|
||||
mask?: boolean;
|
||||
motion?: CSSMotionProps;
|
||||
mobile?: boolean;
|
||||
}
|
||||
export default function Mask(props: MaskProps): React.JSX.Element;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = Mask;
|
||||
var _clsx = require("clsx");
|
||||
var _motion = _interopRequireDefault(require("@rc-component/motion"));
|
||||
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 _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); }
|
||||
function Mask(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
open,
|
||||
zIndex,
|
||||
mask,
|
||||
motion,
|
||||
mobile
|
||||
} = props;
|
||||
if (!mask) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(_motion.default, _extends({}, motion, {
|
||||
motionAppear: true,
|
||||
visible: open,
|
||||
removeOnLeave: true
|
||||
}), ({
|
||||
className
|
||||
}) => /*#__PURE__*/React.createElement("div", {
|
||||
style: {
|
||||
zIndex
|
||||
},
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-mask`, mobile && `${prefixCls}-mobile-mask`, className)
|
||||
}));
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import * as React from 'react';
|
||||
export interface PopupContentProps {
|
||||
children?: React.ReactNode;
|
||||
cache?: boolean;
|
||||
}
|
||||
declare const PopupContent: React.MemoExoticComponent<({ children }: PopupContentProps) => React.ReactElement<any, string | React.JSXElementConstructor<any>>>;
|
||||
export default PopupContent;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
"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 PopupContent = /*#__PURE__*/React.memo(({
|
||||
children
|
||||
}) => children, (_, next) => next.cache);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
PopupContent.displayName = 'PopupContent';
|
||||
}
|
||||
var _default = exports.default = PopupContent;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import { type ResizeObserverProps } from '@rc-component/resize-observer';
|
||||
import * as React from 'react';
|
||||
import type { TriggerProps } from '../';
|
||||
import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
|
||||
import type { PortalProps } from '@rc-component/portal';
|
||||
export interface MobileConfig {
|
||||
mask?: boolean;
|
||||
/** Set popup motion. You can ref `rc-motion` for more info. */
|
||||
motion?: CSSMotionProps;
|
||||
/** Set mask motion. You can ref `rc-motion` for more info. */
|
||||
maskMotion?: CSSMotionProps;
|
||||
}
|
||||
export interface PopupProps {
|
||||
onEsc?: PortalProps['onEsc'];
|
||||
prefixCls: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
popup?: TriggerProps['popup'];
|
||||
target: HTMLElement;
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onPointerEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onPointerDownCapture?: React.MouseEventHandler<HTMLDivElement>;
|
||||
zIndex?: number;
|
||||
mask?: boolean;
|
||||
onVisibleChanged: (visible: boolean) => void;
|
||||
align?: AlignType;
|
||||
arrow?: ArrowTypeOuter;
|
||||
arrowPos: ArrowPos;
|
||||
open: boolean;
|
||||
/** Tell Portal that should keep in screen. e.g. should wait all motion end */
|
||||
keepDom: boolean;
|
||||
fresh?: boolean;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
motion?: CSSMotionProps;
|
||||
maskMotion?: CSSMotionProps;
|
||||
forceRender?: boolean;
|
||||
getPopupContainer?: TriggerProps['getPopupContainer'];
|
||||
autoDestroy?: boolean;
|
||||
portal: React.ComponentType<any>;
|
||||
children?: React.ReactElement;
|
||||
ready: boolean;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
offsetR: number;
|
||||
offsetB: number;
|
||||
onAlign: VoidFunction;
|
||||
onPrepare: () => Promise<void>;
|
||||
stretch?: string;
|
||||
targetWidth?: number;
|
||||
targetHeight?: number;
|
||||
onResize?: ResizeObserverProps['onResize'];
|
||||
mobile?: MobileConfig;
|
||||
}
|
||||
declare const Popup: React.ForwardRefExoticComponent<PopupProps & React.RefAttributes<HTMLDivElement>>;
|
||||
export default Popup;
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _clsx = require("clsx");
|
||||
var _motion = _interopRequireDefault(require("@rc-component/motion"));
|
||||
var _resizeObserver = _interopRequireDefault(require("@rc-component/resize-observer"));
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var _ref = require("@rc-component/util/lib/ref");
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _Arrow = _interopRequireDefault(require("./Arrow"));
|
||||
var _Mask = _interopRequireDefault(require("./Mask"));
|
||||
var _PopupContent = _interopRequireDefault(require("./PopupContent"));
|
||||
var _useOffsetStyle = _interopRequireDefault(require("../hooks/useOffsetStyle"));
|
||||
var _util = require("@rc-component/util");
|
||||
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 Popup = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
onEsc,
|
||||
popup,
|
||||
className,
|
||||
prefixCls,
|
||||
style,
|
||||
target,
|
||||
onVisibleChanged,
|
||||
// Open
|
||||
open,
|
||||
keepDom,
|
||||
fresh,
|
||||
// Click
|
||||
onClick,
|
||||
// Mask
|
||||
mask,
|
||||
// Arrow
|
||||
arrow,
|
||||
arrowPos,
|
||||
align,
|
||||
// Motion
|
||||
motion,
|
||||
maskMotion,
|
||||
// Mobile
|
||||
mobile,
|
||||
// Portal
|
||||
forceRender,
|
||||
getPopupContainer,
|
||||
autoDestroy,
|
||||
portal: Portal,
|
||||
children,
|
||||
zIndex,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onPointerEnter,
|
||||
onPointerDownCapture,
|
||||
ready,
|
||||
offsetX,
|
||||
offsetY,
|
||||
offsetR,
|
||||
offsetB,
|
||||
onAlign,
|
||||
onPrepare,
|
||||
// Resize
|
||||
onResize,
|
||||
stretch,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
} = props;
|
||||
const popupContent = typeof popup === 'function' ? popup() : popup;
|
||||
|
||||
// We can not remove holder only when motion finished.
|
||||
const isNodeVisible = open || keepDom;
|
||||
|
||||
// ========================= Mobile =========================
|
||||
const isMobile = !!mobile;
|
||||
|
||||
// ========================== Mask ==========================
|
||||
const [mergedMask, mergedMaskMotion, mergedPopupMotion] = React.useMemo(() => {
|
||||
if (mobile) {
|
||||
return [mobile.mask, mobile.maskMotion, mobile.motion];
|
||||
}
|
||||
return [mask, maskMotion, motion];
|
||||
}, [mobile, mask, maskMotion, motion]);
|
||||
|
||||
// ======================= Container ========================
|
||||
const getPopupContainerNeedParams = getPopupContainer?.length > 0;
|
||||
const [show, setShow] = React.useState(!getPopupContainer || !getPopupContainerNeedParams);
|
||||
|
||||
// Delay to show since `getPopupContainer` need target element
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (!show && getPopupContainerNeedParams && target) {
|
||||
setShow(true);
|
||||
}
|
||||
}, [show, getPopupContainerNeedParams, target]);
|
||||
|
||||
// ========================= Resize =========================
|
||||
const onInternalResize = (0, _util.useEvent)((size, ele) => {
|
||||
onResize?.(size, ele);
|
||||
onAlign();
|
||||
});
|
||||
|
||||
// ========================= Styles =========================
|
||||
const offsetStyle = (0, _useOffsetStyle.default)(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY);
|
||||
|
||||
// ========================= Render =========================
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// >>>>> Misc
|
||||
const miscStyle = {};
|
||||
if (stretch) {
|
||||
if (stretch.includes('height') && targetHeight) {
|
||||
miscStyle.height = targetHeight;
|
||||
} else if (stretch.includes('minHeight') && targetHeight) {
|
||||
miscStyle.minHeight = targetHeight;
|
||||
}
|
||||
if (stretch.includes('width') && targetWidth) {
|
||||
miscStyle.width = targetWidth;
|
||||
} else if (stretch.includes('minWidth') && targetWidth) {
|
||||
miscStyle.minWidth = targetWidth;
|
||||
}
|
||||
}
|
||||
if (!open) {
|
||||
miscStyle.pointerEvents = 'none';
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(Portal, {
|
||||
open: forceRender || isNodeVisible,
|
||||
getContainer: getPopupContainer && (() => getPopupContainer(target)),
|
||||
autoDestroy: autoDestroy,
|
||||
onEsc: onEsc
|
||||
}, /*#__PURE__*/React.createElement(_Mask.default, {
|
||||
prefixCls: prefixCls,
|
||||
open: open,
|
||||
zIndex: zIndex,
|
||||
mask: mergedMask,
|
||||
motion: mergedMaskMotion,
|
||||
mobile: isMobile
|
||||
}), /*#__PURE__*/React.createElement(_resizeObserver.default, {
|
||||
onResize: onInternalResize,
|
||||
disabled: !open
|
||||
}, resizeObserverRef => {
|
||||
return /*#__PURE__*/React.createElement(_motion.default, _extends({
|
||||
motionAppear: true,
|
||||
motionEnter: true,
|
||||
motionLeave: true,
|
||||
removeOnLeave: false,
|
||||
forceRender: forceRender,
|
||||
leavedClassName: `${prefixCls}-hidden`
|
||||
}, mergedPopupMotion, {
|
||||
onAppearPrepare: onPrepare,
|
||||
onEnterPrepare: onPrepare,
|
||||
visible: open,
|
||||
onVisibleChanged: nextVisible => {
|
||||
motion?.onVisibleChanged?.(nextVisible);
|
||||
onVisibleChanged(nextVisible);
|
||||
}
|
||||
}), ({
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}, motionRef) => {
|
||||
const cls = (0, _clsx.clsx)(prefixCls, motionClassName, className, {
|
||||
[`${prefixCls}-mobile`]: isMobile
|
||||
});
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
ref: (0, _ref.composeRef)(resizeObserverRef, ref, motionRef),
|
||||
className: cls,
|
||||
style: {
|
||||
'--arrow-x': `${arrowPos.x || 0}px`,
|
||||
'--arrow-y': `${arrowPos.y || 0}px`,
|
||||
...offsetStyle,
|
||||
...miscStyle,
|
||||
...motionStyle,
|
||||
boxSizing: 'border-box',
|
||||
zIndex,
|
||||
...style
|
||||
},
|
||||
onMouseEnter: onMouseEnter,
|
||||
onMouseLeave: onMouseLeave,
|
||||
onPointerEnter: onPointerEnter,
|
||||
onClick: onClick,
|
||||
onPointerDownCapture: onPointerDownCapture
|
||||
}, arrow && /*#__PURE__*/React.createElement(_Arrow.default, {
|
||||
prefixCls: prefixCls,
|
||||
arrow: arrow,
|
||||
arrowPos: arrowPos,
|
||||
align: align
|
||||
}), /*#__PURE__*/React.createElement(_PopupContent.default, {
|
||||
cache: !open && !fresh
|
||||
}, popupContent));
|
||||
});
|
||||
}), children);
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Popup.displayName = 'Popup';
|
||||
}
|
||||
var _default = exports.default = Popup;
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import type { AlignType, ArrowPos } from '../interface';
|
||||
export interface UniqueContainerProps {
|
||||
prefixCls: string;
|
||||
isMobile: boolean;
|
||||
ready: boolean;
|
||||
open: boolean;
|
||||
align: AlignType;
|
||||
offsetR: number;
|
||||
offsetB: number;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
arrowPos?: ArrowPos;
|
||||
popupSize?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
motion?: CSSMotionProps;
|
||||
uniqueContainerClassName?: string;
|
||||
uniqueContainerStyle?: React.CSSProperties;
|
||||
}
|
||||
declare const UniqueContainer: (props: UniqueContainerProps) => React.JSX.Element;
|
||||
export default UniqueContainer;
|
||||
Generated
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _react = _interopRequireDefault(require("react"));
|
||||
var _useOffsetStyle = _interopRequireDefault(require("../hooks/useOffsetStyle"));
|
||||
var _clsx = require("clsx");
|
||||
var _motion = _interopRequireDefault(require("@rc-component/motion"));
|
||||
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 UniqueContainer = props => {
|
||||
const {
|
||||
prefixCls,
|
||||
isMobile,
|
||||
ready,
|
||||
open,
|
||||
align,
|
||||
offsetR,
|
||||
offsetB,
|
||||
offsetX,
|
||||
offsetY,
|
||||
arrowPos,
|
||||
popupSize,
|
||||
motion,
|
||||
uniqueContainerClassName,
|
||||
uniqueContainerStyle
|
||||
} = props;
|
||||
const containerCls = `${prefixCls}-unique-container`;
|
||||
const [motionVisible, setMotionVisible] = _react.default.useState(false);
|
||||
|
||||
// ========================= Styles =========================
|
||||
const offsetStyle = (0, _useOffsetStyle.default)(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY);
|
||||
|
||||
// Cache for offsetStyle when ready is true
|
||||
const cachedOffsetStyleRef = _react.default.useRef(offsetStyle);
|
||||
|
||||
// Update cached offset style when ready is true
|
||||
if (ready) {
|
||||
cachedOffsetStyleRef.current = offsetStyle;
|
||||
}
|
||||
|
||||
// Apply popup size if available
|
||||
const sizeStyle = {};
|
||||
if (popupSize) {
|
||||
sizeStyle.width = popupSize.width;
|
||||
sizeStyle.height = popupSize.height;
|
||||
}
|
||||
|
||||
// ========================= Render =========================
|
||||
return /*#__PURE__*/_react.default.createElement(_motion.default, _extends({
|
||||
motionAppear: true,
|
||||
motionEnter: true,
|
||||
motionLeave: true,
|
||||
removeOnLeave: false,
|
||||
leavedClassName: `${containerCls}-hidden`
|
||||
}, motion, {
|
||||
visible: open,
|
||||
onVisibleChanged: nextVisible => {
|
||||
setMotionVisible(nextVisible);
|
||||
}
|
||||
}), ({
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}) => {
|
||||
const cls = (0, _clsx.clsx)(containerCls, motionClassName, uniqueContainerClassName, {
|
||||
[`${containerCls}-visible`]: motionVisible
|
||||
});
|
||||
return /*#__PURE__*/_react.default.createElement("div", {
|
||||
className: cls,
|
||||
style: {
|
||||
'--arrow-x': `${arrowPos?.x || 0}px`,
|
||||
'--arrow-y': `${arrowPos?.y || 0}px`,
|
||||
...cachedOffsetStyleRef.current,
|
||||
...sizeStyle,
|
||||
...motionStyle,
|
||||
...uniqueContainerStyle
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
var _default = exports.default = UniqueContainer;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { type UniqueShowOptions } from '../context';
|
||||
export interface UniqueProviderProps {
|
||||
children: React.ReactNode;
|
||||
/** Additional handle options data to do the customize info */
|
||||
postTriggerProps?: (options: UniqueShowOptions) => UniqueShowOptions;
|
||||
}
|
||||
declare const UniqueProvider: ({ children, postTriggerProps, }: UniqueProviderProps) => React.JSX.Element;
|
||||
export default UniqueProvider;
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _portal = _interopRequireDefault(require("@rc-component/portal"));
|
||||
var _context = _interopRequireWildcard(require("../context"));
|
||||
var _useDelay = _interopRequireDefault(require("../hooks/useDelay"));
|
||||
var _useAlign = _interopRequireDefault(require("../hooks/useAlign"));
|
||||
var _Popup = _interopRequireDefault(require("../Popup"));
|
||||
var _util = require("@rc-component/util");
|
||||
var _useTargetState = _interopRequireDefault(require("./useTargetState"));
|
||||
var _findDOMNode = require("@rc-component/util/lib/Dom/findDOMNode");
|
||||
var _UniqueContainer = _interopRequireDefault(require("./UniqueContainer"));
|
||||
var _clsx = require("clsx");
|
||||
var _util2 = require("../util");
|
||||
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 UniqueProvider = ({
|
||||
children,
|
||||
postTriggerProps
|
||||
}) => {
|
||||
const [trigger, open, options, onTargetVisibleChanged] = (0, _useTargetState.default)();
|
||||
|
||||
// ========================== Options ===========================
|
||||
const mergedOptions = React.useMemo(() => {
|
||||
if (!options || !postTriggerProps) {
|
||||
return options;
|
||||
}
|
||||
return postTriggerProps(options);
|
||||
}, [options, postTriggerProps]);
|
||||
|
||||
// =========================== Popup ============================
|
||||
const [popupEle, setPopupEle] = React.useState(null);
|
||||
const [popupSize, setPopupSize] = React.useState(null);
|
||||
|
||||
// Used for forwardRef popup. Not use internal
|
||||
const externalPopupRef = React.useRef(null);
|
||||
const setPopupRef = (0, _util.useEvent)(node => {
|
||||
externalPopupRef.current = node;
|
||||
if ((0, _findDOMNode.isDOM)(node) && popupEle !== node) {
|
||||
setPopupEle(node);
|
||||
}
|
||||
});
|
||||
|
||||
// ========================== Register ==========================
|
||||
// Store the isOpen function from the latest show call
|
||||
const isOpenRef = React.useRef(null);
|
||||
const delayInvoke = (0, _useDelay.default)();
|
||||
const show = (0, _util.useEvent)((showOptions, isOpen) => {
|
||||
// Store the isOpen function for later use in hide
|
||||
isOpenRef.current = isOpen;
|
||||
delayInvoke(() => {
|
||||
trigger(showOptions);
|
||||
}, showOptions.delay);
|
||||
});
|
||||
const hide = delay => {
|
||||
delayInvoke(() => {
|
||||
// Check if we should still hide by calling the isOpen function
|
||||
// If isOpen returns true, it means another trigger wants to keep it open
|
||||
if (isOpenRef.current?.()) {
|
||||
return; // Don't hide if something else wants it open
|
||||
}
|
||||
trigger(false);
|
||||
// Don't clear target, currentNode, options immediately, wait until animation completes
|
||||
}, delay);
|
||||
};
|
||||
|
||||
// Callback after animation completes
|
||||
const onVisibleChanged = (0, _util.useEvent)(visible => {
|
||||
// Call useTargetState callback to handle animation state
|
||||
onTargetVisibleChanged(visible);
|
||||
});
|
||||
|
||||
// =========================== Align ============================
|
||||
const [ready, offsetX, offsetY, offsetR, offsetB, arrowX, arrowY,
|
||||
|
||||
// scaleX - not used in UniqueProvider
|
||||
,,
|
||||
// scaleY - not used in UniqueProvider
|
||||
alignInfo, onAlign] = (0, _useAlign.default)(open, popupEle, mergedOptions?.target, mergedOptions?.popupPlacement, mergedOptions?.builtinPlacements || {}, mergedOptions?.popupAlign, undefined,
|
||||
// onPopupAlign
|
||||
false // isMobile
|
||||
);
|
||||
const alignedClassName = React.useMemo(() => {
|
||||
if (!mergedOptions) {
|
||||
return '';
|
||||
}
|
||||
const baseClassName = (0, _util2.getAlignPopupClassName)(mergedOptions.builtinPlacements || {}, mergedOptions.prefixCls || '', alignInfo, false // alignPoint is false for UniqueProvider
|
||||
);
|
||||
return (0, _clsx.clsx)(baseClassName, mergedOptions.getPopupClassNameFromAlign?.(alignInfo));
|
||||
}, [alignInfo, mergedOptions?.getPopupClassNameFromAlign, mergedOptions?.builtinPlacements, mergedOptions?.prefixCls]);
|
||||
const contextValue = React.useMemo(() => ({
|
||||
show,
|
||||
hide
|
||||
}), []);
|
||||
|
||||
// =========================== Align ============================
|
||||
React.useEffect(() => {
|
||||
onAlign();
|
||||
}, [mergedOptions?.target]);
|
||||
|
||||
// =========================== Motion ===========================
|
||||
const onPrepare = (0, _util.useEvent)(() => {
|
||||
onAlign();
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// ======================== Trigger Context =====================
|
||||
const subPopupElements = React.useRef({});
|
||||
const parentContext = React.useContext(_context.default);
|
||||
const triggerContextValue = React.useMemo(() => ({
|
||||
registerSubPopup: (id, subPopupEle) => {
|
||||
subPopupElements.current[id] = subPopupEle;
|
||||
parentContext?.registerSubPopup(id, subPopupEle);
|
||||
}
|
||||
}), [parentContext]);
|
||||
|
||||
// =========================== Render ===========================
|
||||
const prefixCls = mergedOptions?.prefixCls;
|
||||
return /*#__PURE__*/React.createElement(_context.UniqueContext.Provider, {
|
||||
value: contextValue
|
||||
}, children, mergedOptions && /*#__PURE__*/React.createElement(_context.default.Provider, {
|
||||
value: triggerContextValue
|
||||
}, /*#__PURE__*/React.createElement(_Popup.default, {
|
||||
ref: setPopupRef,
|
||||
portal: _portal.default,
|
||||
onEsc: mergedOptions.onEsc,
|
||||
prefixCls: prefixCls,
|
||||
popup: mergedOptions.popup,
|
||||
className: (0, _clsx.clsx)(mergedOptions.popupClassName, alignedClassName, `${prefixCls}-unique-controlled`),
|
||||
style: mergedOptions.popupStyle,
|
||||
target: mergedOptions.target,
|
||||
open: open,
|
||||
keepDom: true,
|
||||
fresh: true,
|
||||
autoDestroy: false,
|
||||
onVisibleChanged: onVisibleChanged,
|
||||
ready: ready,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
offsetR: offsetR,
|
||||
offsetB: offsetB,
|
||||
onAlign: onAlign,
|
||||
onPrepare: onPrepare,
|
||||
onResize: size => setPopupSize({
|
||||
width: size.offsetWidth,
|
||||
height: size.offsetHeight
|
||||
}),
|
||||
arrowPos: {
|
||||
x: arrowX,
|
||||
y: arrowY
|
||||
},
|
||||
align: alignInfo,
|
||||
zIndex: mergedOptions.zIndex,
|
||||
mask: mergedOptions.mask,
|
||||
arrow: mergedOptions.arrow,
|
||||
motion: mergedOptions.popupMotion,
|
||||
maskMotion: mergedOptions.maskMotion,
|
||||
getPopupContainer: mergedOptions.getPopupContainer
|
||||
}, /*#__PURE__*/React.createElement(_UniqueContainer.default, {
|
||||
prefixCls: prefixCls,
|
||||
isMobile: false,
|
||||
ready: ready,
|
||||
open: open,
|
||||
align: alignInfo,
|
||||
offsetR: offsetR,
|
||||
offsetB: offsetB,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
arrowPos: {
|
||||
x: arrowX,
|
||||
y: arrowY
|
||||
},
|
||||
popupSize: popupSize,
|
||||
motion: mergedOptions.popupMotion,
|
||||
uniqueContainerClassName: (0, _clsx.clsx)(mergedOptions.uniqueContainerClassName, alignedClassName),
|
||||
uniqueContainerStyle: mergedOptions.uniqueContainerStyle
|
||||
}))));
|
||||
};
|
||||
var _default = exports.default = UniqueProvider;
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
import type { UniqueShowOptions } from '../context';
|
||||
/**
|
||||
* Control the state of popup bind target:
|
||||
* 1. When set `target`. Do show the popup.
|
||||
* 2. When `target` is removed. Do hide the popup.
|
||||
* 3. When `target` change to another one:
|
||||
* a. We wait motion finish of previous popup.
|
||||
* b. Then we set new target and show the popup.
|
||||
* 4. During appear/enter animation, cache new options and apply after animation completes.
|
||||
*/
|
||||
export default function useTargetState(): [
|
||||
trigger: (options: UniqueShowOptions | false) => void,
|
||||
open: boolean,
|
||||
cacheOptions: UniqueShowOptions | null,
|
||||
onVisibleChanged: (visible: boolean) => void
|
||||
];
|
||||
Generated
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useTargetState;
|
||||
var _react = _interopRequireDefault(require("react"));
|
||||
var _util = require("@rc-component/util");
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
/**
|
||||
* Control the state of popup bind target:
|
||||
* 1. When set `target`. Do show the popup.
|
||||
* 2. When `target` is removed. Do hide the popup.
|
||||
* 3. When `target` change to another one:
|
||||
* a. We wait motion finish of previous popup.
|
||||
* b. Then we set new target and show the popup.
|
||||
* 4. During appear/enter animation, cache new options and apply after animation completes.
|
||||
*/
|
||||
function useTargetState() {
|
||||
const [options, setOptions] = _react.default.useState(null);
|
||||
const [open, setOpen] = _react.default.useState(false);
|
||||
const [isAnimating, setIsAnimating] = _react.default.useState(false);
|
||||
const pendingOptionsRef = _react.default.useRef(null);
|
||||
const trigger = (0, _util.useEvent)(nextOptions => {
|
||||
if (nextOptions === false) {
|
||||
// Clear pending options when hiding
|
||||
pendingOptionsRef.current = null;
|
||||
setOpen(false);
|
||||
} else {
|
||||
if (isAnimating && open) {
|
||||
// If animating (appear or enter), cache new options
|
||||
pendingOptionsRef.current = nextOptions;
|
||||
} else {
|
||||
setOpen(true);
|
||||
// Set new options
|
||||
setOptions(nextOptions);
|
||||
pendingOptionsRef.current = null;
|
||||
|
||||
// Only mark as animating when transitioning from closed to open
|
||||
if (!open) {
|
||||
setIsAnimating(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const onVisibleChanged = (0, _util.useEvent)(visible => {
|
||||
if (visible) {
|
||||
// Animation enter completed, check if there are pending options
|
||||
setIsAnimating(false);
|
||||
if (pendingOptionsRef.current) {
|
||||
// Apply pending options
|
||||
setOptions(pendingOptionsRef.current);
|
||||
pendingOptionsRef.current = null;
|
||||
}
|
||||
} else {
|
||||
// Animation leave completed
|
||||
setIsAnimating(false);
|
||||
pendingOptionsRef.current = null;
|
||||
}
|
||||
});
|
||||
return [trigger, open, options, onVisibleChanged];
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import * as React from 'react';
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import type { PortalProps } from '@rc-component/portal';
|
||||
import type { TriggerProps } from './index';
|
||||
import type { AlignType, ArrowTypeOuter, BuildInPlacements } from './interface';
|
||||
export interface TriggerContextProps {
|
||||
registerSubPopup: (id: string, node: HTMLElement) => void;
|
||||
}
|
||||
declare const TriggerContext: React.Context<TriggerContextProps>;
|
||||
export default TriggerContext;
|
||||
export interface UniqueShowOptions {
|
||||
id: string;
|
||||
popup: TriggerProps['popup'];
|
||||
target: HTMLElement;
|
||||
delay: number;
|
||||
prefixCls?: string;
|
||||
popupClassName?: string;
|
||||
uniqueContainerClassName?: string;
|
||||
uniqueContainerStyle?: React.CSSProperties;
|
||||
popupStyle?: React.CSSProperties;
|
||||
popupPlacement?: string;
|
||||
builtinPlacements?: BuildInPlacements;
|
||||
popupAlign?: AlignType;
|
||||
zIndex?: number;
|
||||
mask?: boolean;
|
||||
maskClosable?: boolean;
|
||||
popupMotion?: CSSMotionProps;
|
||||
maskMotion?: CSSMotionProps;
|
||||
arrow?: ArrowTypeOuter;
|
||||
getPopupContainer?: TriggerProps['getPopupContainer'];
|
||||
getPopupClassNameFromAlign?: (align: AlignType) => string;
|
||||
onEsc?: PortalProps['onEsc'];
|
||||
}
|
||||
export interface UniqueContextProps {
|
||||
show: (options: UniqueShowOptions, isOpen: () => boolean) => void;
|
||||
hide: (delay: number) => void;
|
||||
}
|
||||
export declare const UniqueContext: React.Context<UniqueContextProps>;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = exports.UniqueContext = 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; }
|
||||
// ===================== Nest =====================
|
||||
|
||||
const TriggerContext = /*#__PURE__*/React.createContext(null);
|
||||
var _default = exports.default = TriggerContext; // ==================== Unique ====================
|
||||
const UniqueContext = exports.UniqueContext = /*#__PURE__*/React.createContext(null);
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import type { ActionType } from '../interface';
|
||||
type InternalActionType = ActionType | 'touch';
|
||||
type ActionTypes = InternalActionType | InternalActionType[];
|
||||
export default function useAction(action: ActionTypes, showAction?: ActionTypes, hideAction?: ActionTypes): [showAction: Set<InternalActionType>, hideAction: Set<InternalActionType>];
|
||||
export {};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useAction;
|
||||
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 toArray(val) {
|
||||
return val ? Array.isArray(val) ? val : [val] : [];
|
||||
}
|
||||
function useAction(action, showAction, hideAction) {
|
||||
return React.useMemo(() => {
|
||||
const mergedShowAction = toArray(showAction ?? action);
|
||||
const mergedHideAction = toArray(hideAction ?? action);
|
||||
const showActionSet = new Set(mergedShowAction);
|
||||
const hideActionSet = new Set(mergedHideAction);
|
||||
if (showActionSet.has('hover') && !showActionSet.has('click')) {
|
||||
showActionSet.add('touch');
|
||||
}
|
||||
if (hideActionSet.has('hover') && !hideActionSet.has('click')) {
|
||||
hideActionSet.add('touch');
|
||||
}
|
||||
return [showActionSet, hideActionSet];
|
||||
}, [action, showAction, hideAction]);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import type { TriggerProps } from '..';
|
||||
import type { AlignType } from '../interface';
|
||||
export default function useAlign(open: boolean, popupEle: HTMLElement, target: HTMLElement | [x: number, y: number], placement: string, builtinPlacements: any, popupAlign?: AlignType, onPopupAlign?: TriggerProps['onPopupAlign'], mobile?: boolean): [
|
||||
ready: boolean,
|
||||
offsetX: number,
|
||||
offsetY: number,
|
||||
offsetR: number,
|
||||
offsetB: number,
|
||||
arrowX: number,
|
||||
arrowY: number,
|
||||
scaleX: number,
|
||||
scaleY: number,
|
||||
align: AlignType,
|
||||
onAlign: VoidFunction
|
||||
];
|
||||
+540
@@ -0,0 +1,540 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useAlign;
|
||||
var _findDOMNode = require("@rc-component/util/lib/Dom/findDOMNode");
|
||||
var _isVisible = _interopRequireDefault(require("@rc-component/util/lib/Dom/isVisible"));
|
||||
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _util = require("../util");
|
||||
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 getUnitOffset(size, offset = 0) {
|
||||
const offsetStr = `${offset}`;
|
||||
const cells = offsetStr.match(/^(.*)\%$/);
|
||||
if (cells) {
|
||||
return size * (parseFloat(cells[1]) / 100);
|
||||
}
|
||||
return parseFloat(offsetStr);
|
||||
}
|
||||
function getNumberOffset(rect, offset) {
|
||||
const [offsetX, offsetY] = offset || [];
|
||||
return [getUnitOffset(rect.width, offsetX), getUnitOffset(rect.height, offsetY)];
|
||||
}
|
||||
function splitPoints(points = '') {
|
||||
return [points[0], points[1]];
|
||||
}
|
||||
function getAlignPoint(rect, points) {
|
||||
const topBottom = points[0];
|
||||
const leftRight = points[1];
|
||||
let x;
|
||||
let y;
|
||||
|
||||
// Top & Bottom
|
||||
if (topBottom === 't') {
|
||||
y = rect.y;
|
||||
} else if (topBottom === 'b') {
|
||||
y = rect.y + rect.height;
|
||||
} else {
|
||||
y = rect.y + rect.height / 2;
|
||||
}
|
||||
|
||||
// Left & Right
|
||||
if (leftRight === 'l') {
|
||||
x = rect.x;
|
||||
} else if (leftRight === 'r') {
|
||||
x = rect.x + rect.width;
|
||||
} else {
|
||||
x = rect.x + rect.width / 2;
|
||||
}
|
||||
return {
|
||||
x,
|
||||
y
|
||||
};
|
||||
}
|
||||
function reversePoints(points, index) {
|
||||
const reverseMap = {
|
||||
t: 'b',
|
||||
b: 't',
|
||||
l: 'r',
|
||||
r: 'l'
|
||||
};
|
||||
const clone = [...points];
|
||||
clone[index] = reverseMap[points[index]] || 'c';
|
||||
return clone;
|
||||
}
|
||||
function flatPoints(points) {
|
||||
return points.join('');
|
||||
}
|
||||
function useAlign(open, popupEle, target, placement, builtinPlacements, popupAlign, onPopupAlign, mobile) {
|
||||
const [offsetInfo, setOffsetInfo] = React.useState({
|
||||
ready: false,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
offsetR: 0,
|
||||
offsetB: 0,
|
||||
arrowX: 0,
|
||||
arrowY: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
align: builtinPlacements[placement] || {}
|
||||
});
|
||||
const alignCountRef = React.useRef(0);
|
||||
const scrollerList = React.useMemo(() => {
|
||||
if (!popupEle || mobile) {
|
||||
return [];
|
||||
}
|
||||
return (0, _util.collectScroller)(popupEle);
|
||||
}, [popupEle]);
|
||||
|
||||
// ========================= Flip ==========================
|
||||
// We will memo flip info.
|
||||
// If size change to make flip, it will memo the flip info and use it in next align.
|
||||
const prevFlipRef = React.useRef({});
|
||||
const resetFlipCache = () => {
|
||||
prevFlipRef.current = {};
|
||||
};
|
||||
if (!open) {
|
||||
resetFlipCache();
|
||||
}
|
||||
|
||||
// ========================= Align =========================
|
||||
const onAlign = (0, _useEvent.default)(() => {
|
||||
if (popupEle && target && open && !mobile) {
|
||||
const popupElement = popupEle;
|
||||
const doc = popupElement.ownerDocument;
|
||||
const win = (0, _util.getWin)(popupElement);
|
||||
const {
|
||||
position: popupPosition
|
||||
} = win.getComputedStyle(popupElement);
|
||||
const originLeft = popupElement.style.left;
|
||||
const originTop = popupElement.style.top;
|
||||
const originRight = popupElement.style.right;
|
||||
const originBottom = popupElement.style.bottom;
|
||||
const originOverflow = popupElement.style.overflow;
|
||||
|
||||
// Placement
|
||||
const placementInfo = {
|
||||
...builtinPlacements[placement],
|
||||
...popupAlign
|
||||
};
|
||||
|
||||
// placeholder element
|
||||
const placeholderElement = doc.createElement('div');
|
||||
popupElement.parentElement?.appendChild(placeholderElement);
|
||||
placeholderElement.style.left = `${popupElement.offsetLeft}px`;
|
||||
placeholderElement.style.top = `${popupElement.offsetTop}px`;
|
||||
placeholderElement.style.position = popupPosition;
|
||||
placeholderElement.style.height = `${popupElement.offsetHeight}px`;
|
||||
placeholderElement.style.width = `${popupElement.offsetWidth}px`;
|
||||
|
||||
// Reset first
|
||||
popupElement.style.left = '0';
|
||||
popupElement.style.top = '0';
|
||||
popupElement.style.right = 'auto';
|
||||
popupElement.style.bottom = 'auto';
|
||||
popupElement.style.overflow = 'hidden';
|
||||
|
||||
// Calculate align style, we should consider `transform` case
|
||||
let targetRect;
|
||||
if (Array.isArray(target)) {
|
||||
targetRect = {
|
||||
x: target[0],
|
||||
y: target[1],
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
} else {
|
||||
const rect = target.getBoundingClientRect();
|
||||
rect.x = rect.x ?? rect.left;
|
||||
rect.y = rect.y ?? rect.top;
|
||||
targetRect = {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
};
|
||||
}
|
||||
const popupRect = popupElement.getBoundingClientRect();
|
||||
const {
|
||||
height,
|
||||
width
|
||||
} = win.getComputedStyle(popupElement);
|
||||
popupRect.x = popupRect.x ?? popupRect.left;
|
||||
popupRect.y = popupRect.y ?? popupRect.top;
|
||||
const {
|
||||
clientWidth,
|
||||
clientHeight,
|
||||
scrollWidth,
|
||||
scrollHeight,
|
||||
scrollTop,
|
||||
scrollLeft
|
||||
} = doc.documentElement;
|
||||
const popupHeight = popupRect.height;
|
||||
const popupWidth = popupRect.width;
|
||||
const targetHeight = targetRect.height;
|
||||
const targetWidth = targetRect.width;
|
||||
|
||||
// Get bounding of visible area
|
||||
const visibleRegion = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: clientWidth,
|
||||
bottom: clientHeight
|
||||
};
|
||||
const scrollRegion = {
|
||||
left: -scrollLeft,
|
||||
top: -scrollTop,
|
||||
right: scrollWidth - scrollLeft,
|
||||
bottom: scrollHeight - scrollTop
|
||||
};
|
||||
let {
|
||||
htmlRegion
|
||||
} = placementInfo;
|
||||
const VISIBLE = 'visible';
|
||||
const VISIBLE_FIRST = 'visibleFirst';
|
||||
if (htmlRegion !== 'scroll' && htmlRegion !== VISIBLE_FIRST) {
|
||||
htmlRegion = VISIBLE;
|
||||
}
|
||||
const isVisibleFirst = htmlRegion === VISIBLE_FIRST;
|
||||
const scrollRegionArea = (0, _util.getVisibleArea)(scrollRegion, scrollerList);
|
||||
const visibleRegionArea = (0, _util.getVisibleArea)(visibleRegion, scrollerList);
|
||||
const visibleArea = htmlRegion === VISIBLE ? visibleRegionArea : scrollRegionArea;
|
||||
|
||||
// When set to `visibleFirst`,
|
||||
// the check `adjust` logic will use `visibleRegion` for check first.
|
||||
const adjustCheckVisibleArea = isVisibleFirst ? visibleRegionArea : visibleArea;
|
||||
|
||||
// Record right & bottom align data
|
||||
popupElement.style.left = 'auto';
|
||||
popupElement.style.top = 'auto';
|
||||
popupElement.style.right = '0';
|
||||
popupElement.style.bottom = '0';
|
||||
const popupMirrorRect = popupElement.getBoundingClientRect();
|
||||
|
||||
// Reset back
|
||||
popupElement.style.left = originLeft;
|
||||
popupElement.style.top = originTop;
|
||||
popupElement.style.right = originRight;
|
||||
popupElement.style.bottom = originBottom;
|
||||
popupElement.style.overflow = originOverflow;
|
||||
popupElement.parentElement?.removeChild(placeholderElement);
|
||||
|
||||
// Calculate scale
|
||||
const scaleX = (0, _util.toNum)(Math.round(popupWidth / parseFloat(width) * 1000) / 1000);
|
||||
const scaleY = (0, _util.toNum)(Math.round(popupHeight / parseFloat(height) * 1000) / 1000);
|
||||
|
||||
// No need to align since it's not visible in view
|
||||
if (scaleX === 0 || scaleY === 0 || (0, _findDOMNode.isDOM)(target) && !(0, _isVisible.default)(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Offset
|
||||
const {
|
||||
offset,
|
||||
targetOffset
|
||||
} = placementInfo;
|
||||
let [popupOffsetX, popupOffsetY] = getNumberOffset(popupRect, offset);
|
||||
const [targetOffsetX, targetOffsetY] = getNumberOffset(targetRect, targetOffset);
|
||||
targetRect.x -= targetOffsetX;
|
||||
targetRect.y -= targetOffsetY;
|
||||
|
||||
// Points
|
||||
const [popupPoint, targetPoint] = placementInfo.points || [];
|
||||
const targetPoints = splitPoints(targetPoint);
|
||||
const popupPoints = splitPoints(popupPoint);
|
||||
const targetAlignPoint = getAlignPoint(targetRect, targetPoints);
|
||||
const popupAlignPoint = getAlignPoint(popupRect, popupPoints);
|
||||
|
||||
// Real align info may not same as origin one
|
||||
const nextAlignInfo = {
|
||||
...placementInfo
|
||||
};
|
||||
let nextPoints = [popupPoints, targetPoints];
|
||||
|
||||
// Next Offset
|
||||
let nextOffsetX = targetAlignPoint.x - popupAlignPoint.x + popupOffsetX;
|
||||
let nextOffsetY = targetAlignPoint.y - popupAlignPoint.y + popupOffsetY;
|
||||
|
||||
// ============== Intersection ===============
|
||||
// Get area by position. Used for check if flip area is better
|
||||
function getIntersectionVisibleArea(offsetX, offsetY, area = visibleArea) {
|
||||
const l = popupRect.x + offsetX;
|
||||
const t = popupRect.y + offsetY;
|
||||
const r = l + popupWidth;
|
||||
const b = t + popupHeight;
|
||||
const visibleL = Math.max(l, area.left);
|
||||
const visibleT = Math.max(t, area.top);
|
||||
const visibleR = Math.min(r, area.right);
|
||||
const visibleB = Math.min(b, area.bottom);
|
||||
return Math.max(0, (visibleR - visibleL) * (visibleB - visibleT));
|
||||
}
|
||||
const originIntersectionVisibleArea = getIntersectionVisibleArea(nextOffsetX, nextOffsetY);
|
||||
|
||||
// As `visibleFirst`, we prepare this for check
|
||||
const originIntersectionRecommendArea = getIntersectionVisibleArea(nextOffsetX, nextOffsetY, visibleRegionArea);
|
||||
|
||||
// ========================== Overflow ===========================
|
||||
const targetAlignPointTL = getAlignPoint(targetRect, ['t', 'l']);
|
||||
const popupAlignPointTL = getAlignPoint(popupRect, ['t', 'l']);
|
||||
const targetAlignPointBR = getAlignPoint(targetRect, ['b', 'r']);
|
||||
const popupAlignPointBR = getAlignPoint(popupRect, ['b', 'r']);
|
||||
const overflow = placementInfo.overflow || {};
|
||||
const {
|
||||
adjustX,
|
||||
adjustY,
|
||||
shiftX,
|
||||
shiftY
|
||||
} = overflow;
|
||||
const supportAdjust = val => {
|
||||
if (typeof val === 'boolean') {
|
||||
return val;
|
||||
}
|
||||
return val >= 0;
|
||||
};
|
||||
|
||||
// Prepare position
|
||||
let nextPopupY;
|
||||
let nextPopupBottom;
|
||||
let nextPopupX;
|
||||
let nextPopupRight;
|
||||
function syncNextPopupPosition() {
|
||||
nextPopupY = popupRect.y + nextOffsetY;
|
||||
nextPopupBottom = nextPopupY + popupHeight;
|
||||
nextPopupX = popupRect.x + nextOffsetX;
|
||||
nextPopupRight = nextPopupX + popupWidth;
|
||||
}
|
||||
syncNextPopupPosition();
|
||||
|
||||
// >>>>>>>>>> Top & Bottom
|
||||
const needAdjustY = supportAdjust(adjustY);
|
||||
const sameTB = popupPoints[0] === targetPoints[0];
|
||||
|
||||
// Bottom to Top
|
||||
if (needAdjustY && popupPoints[0] === 't' && (nextPopupBottom > adjustCheckVisibleArea.bottom || prevFlipRef.current.bt)) {
|
||||
let tmpNextOffsetY = nextOffsetY;
|
||||
if (sameTB) {
|
||||
tmpNextOffsetY -= popupHeight - targetHeight;
|
||||
} else {
|
||||
tmpNextOffsetY = targetAlignPointTL.y - popupAlignPointBR.y - popupOffsetY;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.bt = true;
|
||||
nextOffsetY = tmpNextOffsetY;
|
||||
popupOffsetY = -popupOffsetY;
|
||||
nextPoints = [reversePoints(nextPoints[0], 0), reversePoints(nextPoints[1], 0)];
|
||||
} else {
|
||||
prevFlipRef.current.bt = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Top to Bottom
|
||||
if (needAdjustY && popupPoints[0] === 'b' && (nextPopupY < adjustCheckVisibleArea.top || prevFlipRef.current.tb)) {
|
||||
let tmpNextOffsetY = nextOffsetY;
|
||||
if (sameTB) {
|
||||
tmpNextOffsetY += popupHeight - targetHeight;
|
||||
} else {
|
||||
tmpNextOffsetY = targetAlignPointBR.y - popupAlignPointTL.y - popupOffsetY;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.tb = true;
|
||||
nextOffsetY = tmpNextOffsetY;
|
||||
popupOffsetY = -popupOffsetY;
|
||||
nextPoints = [reversePoints(nextPoints[0], 0), reversePoints(nextPoints[1], 0)];
|
||||
} else {
|
||||
prevFlipRef.current.tb = false;
|
||||
}
|
||||
}
|
||||
|
||||
// >>>>>>>>>> Left & Right
|
||||
const needAdjustX = supportAdjust(adjustX);
|
||||
|
||||
// >>>>> Flip
|
||||
const sameLR = popupPoints[1] === targetPoints[1];
|
||||
|
||||
// Right to Left
|
||||
if (needAdjustX && popupPoints[1] === 'l' && (nextPopupRight > adjustCheckVisibleArea.right || prevFlipRef.current.rl)) {
|
||||
let tmpNextOffsetX = nextOffsetX;
|
||||
if (sameLR) {
|
||||
tmpNextOffsetX -= popupWidth - targetWidth;
|
||||
} else {
|
||||
tmpNextOffsetX = targetAlignPointTL.x - popupAlignPointBR.x - popupOffsetX;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.rl = true;
|
||||
nextOffsetX = tmpNextOffsetX;
|
||||
popupOffsetX = -popupOffsetX;
|
||||
nextPoints = [reversePoints(nextPoints[0], 1), reversePoints(nextPoints[1], 1)];
|
||||
} else {
|
||||
prevFlipRef.current.rl = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Left to Right
|
||||
if (needAdjustX && popupPoints[1] === 'r' && (nextPopupX < adjustCheckVisibleArea.left || prevFlipRef.current.lr)) {
|
||||
let tmpNextOffsetX = nextOffsetX;
|
||||
if (sameLR) {
|
||||
tmpNextOffsetX += popupWidth - targetWidth;
|
||||
} else {
|
||||
tmpNextOffsetX = targetAlignPointBR.x - popupAlignPointTL.x - popupOffsetX;
|
||||
}
|
||||
const newVisibleArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY);
|
||||
const newVisibleRecommendArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY, visibleRegionArea);
|
||||
if (
|
||||
// Of course use larger one
|
||||
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
|
||||
// Choose recommend one
|
||||
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
|
||||
prevFlipRef.current.lr = true;
|
||||
nextOffsetX = tmpNextOffsetX;
|
||||
popupOffsetX = -popupOffsetX;
|
||||
nextPoints = [reversePoints(nextPoints[0], 1), reversePoints(nextPoints[1], 1)];
|
||||
} else {
|
||||
prevFlipRef.current.lr = false;
|
||||
}
|
||||
}
|
||||
nextAlignInfo.points = [flatPoints(nextPoints[0]), flatPoints(nextPoints[1])];
|
||||
|
||||
// ============================ Shift ============================
|
||||
syncNextPopupPosition();
|
||||
const numShiftX = shiftX === true ? 0 : shiftX;
|
||||
if (typeof numShiftX === 'number') {
|
||||
// Left
|
||||
if (nextPopupX < visibleRegionArea.left) {
|
||||
nextOffsetX -= nextPopupX - visibleRegionArea.left - popupOffsetX;
|
||||
if (targetRect.x + targetWidth < visibleRegionArea.left + numShiftX) {
|
||||
nextOffsetX += targetRect.x - visibleRegionArea.left + targetWidth - numShiftX;
|
||||
}
|
||||
}
|
||||
|
||||
// Right
|
||||
if (nextPopupRight > visibleRegionArea.right) {
|
||||
nextOffsetX -= nextPopupRight - visibleRegionArea.right - popupOffsetX;
|
||||
if (targetRect.x > visibleRegionArea.right - numShiftX) {
|
||||
nextOffsetX += targetRect.x - visibleRegionArea.right + numShiftX;
|
||||
}
|
||||
}
|
||||
}
|
||||
const numShiftY = shiftY === true ? 0 : shiftY;
|
||||
if (typeof numShiftY === 'number') {
|
||||
// Top
|
||||
if (nextPopupY < visibleRegionArea.top) {
|
||||
nextOffsetY -= nextPopupY - visibleRegionArea.top - popupOffsetY;
|
||||
|
||||
// When target if far away from visible area
|
||||
// Stop shift
|
||||
if (targetRect.y + targetHeight < visibleRegionArea.top + numShiftY) {
|
||||
nextOffsetY += targetRect.y - visibleRegionArea.top + targetHeight - numShiftY;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom
|
||||
if (nextPopupBottom > visibleRegionArea.bottom) {
|
||||
nextOffsetY -= nextPopupBottom - visibleRegionArea.bottom - popupOffsetY;
|
||||
if (targetRect.y > visibleRegionArea.bottom - numShiftY) {
|
||||
nextOffsetY += targetRect.y - visibleRegionArea.bottom + numShiftY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ Arrow ============================
|
||||
// Arrow center align
|
||||
const popupLeft = popupRect.x + nextOffsetX;
|
||||
const popupRight = popupLeft + popupWidth;
|
||||
const popupTop = popupRect.y + nextOffsetY;
|
||||
const popupBottom = popupTop + popupHeight;
|
||||
const targetLeft = targetRect.x;
|
||||
const targetRight = targetLeft + targetWidth;
|
||||
const targetTop = targetRect.y;
|
||||
const targetBottom = targetTop + targetHeight;
|
||||
|
||||
/** Max left of the popup and target element */
|
||||
const maxLeft = Math.max(popupLeft, targetLeft);
|
||||
/** Min right of the popup and target element */
|
||||
const minRight = Math.min(popupRight, targetRight);
|
||||
|
||||
/** The center X of popup & target cross area */
|
||||
const xCenter = (maxLeft + minRight) / 2;
|
||||
/** Arrow X of popup offset */
|
||||
const nextArrowX = xCenter - popupLeft;
|
||||
const maxTop = Math.max(popupTop, targetTop);
|
||||
const minBottom = Math.min(popupBottom, targetBottom);
|
||||
const yCenter = (maxTop + minBottom) / 2;
|
||||
const nextArrowY = yCenter - popupTop;
|
||||
onPopupAlign?.(popupEle, nextAlignInfo);
|
||||
|
||||
// Additional calculate right & bottom position
|
||||
let offsetX4Right = popupMirrorRect.right - popupRect.x - (nextOffsetX + popupRect.width);
|
||||
let offsetY4Bottom = popupMirrorRect.bottom - popupRect.y - (nextOffsetY + popupRect.height);
|
||||
if (scaleX === 1) {
|
||||
nextOffsetX = Math.floor(nextOffsetX);
|
||||
offsetX4Right = Math.floor(offsetX4Right);
|
||||
}
|
||||
if (scaleY === 1) {
|
||||
nextOffsetY = Math.floor(nextOffsetY);
|
||||
offsetY4Bottom = Math.floor(offsetY4Bottom);
|
||||
}
|
||||
const nextOffsetInfo = {
|
||||
ready: true,
|
||||
offsetX: nextOffsetX / scaleX,
|
||||
offsetY: nextOffsetY / scaleY,
|
||||
offsetR: offsetX4Right / scaleX,
|
||||
offsetB: offsetY4Bottom / scaleY,
|
||||
arrowX: nextArrowX / scaleX,
|
||||
arrowY: nextArrowY / scaleY,
|
||||
scaleX,
|
||||
scaleY,
|
||||
align: nextAlignInfo
|
||||
};
|
||||
setOffsetInfo(nextOffsetInfo);
|
||||
}
|
||||
});
|
||||
const triggerAlign = () => {
|
||||
alignCountRef.current += 1;
|
||||
const id = alignCountRef.current;
|
||||
|
||||
// Merge all align requirement into one frame
|
||||
Promise.resolve().then(() => {
|
||||
if (alignCountRef.current === id) {
|
||||
onAlign();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Reset ready status when placement & open changed
|
||||
const resetReady = () => {
|
||||
setOffsetInfo(ori => ({
|
||||
...ori,
|
||||
ready: false
|
||||
}));
|
||||
};
|
||||
(0, _useLayoutEffect.default)(resetReady, [placement]);
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (!open) {
|
||||
resetReady();
|
||||
}
|
||||
}, [open]);
|
||||
return [offsetInfo.ready, offsetInfo.offsetX, offsetInfo.offsetY, offsetInfo.offsetR, offsetInfo.offsetB, offsetInfo.arrowX, offsetInfo.arrowY, offsetInfo.scaleX, offsetInfo.scaleY, offsetInfo.align, triggerAlign];
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function useDelay(): (callback: VoidFunction, delay: number) => void;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useDelay;
|
||||
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 useDelay() {
|
||||
const delayRef = React.useRef(null);
|
||||
const clearDelay = () => {
|
||||
if (delayRef.current) {
|
||||
clearTimeout(delayRef.current);
|
||||
delayRef.current = null;
|
||||
}
|
||||
};
|
||||
const delayInvoke = (callback, delay) => {
|
||||
clearDelay();
|
||||
if (delay === 0) {
|
||||
callback();
|
||||
} else {
|
||||
delayRef.current = setTimeout(() => {
|
||||
callback();
|
||||
}, delay * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// Clean up on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
clearDelay();
|
||||
};
|
||||
}, []);
|
||||
return delayInvoke;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type * as React from 'react';
|
||||
import type { AlignType } from '../interface';
|
||||
export default function useOffsetStyle(isMobile: boolean, ready: boolean, open: boolean, align: AlignType, offsetR: number, offsetB: number, offsetX: number, offsetY: number): React.CSSProperties;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useOffsetStyle;
|
||||
function useOffsetStyle(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY) {
|
||||
// >>>>> Offset
|
||||
const AUTO = 'auto';
|
||||
const offsetStyle = isMobile ? {} : {
|
||||
left: '-1000vw',
|
||||
top: '-1000vh',
|
||||
right: AUTO,
|
||||
bottom: AUTO
|
||||
};
|
||||
|
||||
// Set align style
|
||||
if (!isMobile && (ready || !open)) {
|
||||
const {
|
||||
points
|
||||
} = align;
|
||||
const dynamicInset = align.dynamicInset || align._experimental?.dynamicInset;
|
||||
const alignRight = dynamicInset && points[0][1] === 'r';
|
||||
const alignBottom = dynamicInset && points[0][0] === 'b';
|
||||
if (alignRight) {
|
||||
offsetStyle.right = offsetR;
|
||||
offsetStyle.left = AUTO;
|
||||
} else {
|
||||
offsetStyle.left = offsetX;
|
||||
offsetStyle.right = AUTO;
|
||||
}
|
||||
if (alignBottom) {
|
||||
offsetStyle.bottom = offsetB;
|
||||
offsetStyle.top = AUTO;
|
||||
} else {
|
||||
offsetStyle.top = offsetY;
|
||||
offsetStyle.bottom = AUTO;
|
||||
}
|
||||
}
|
||||
return offsetStyle;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function useWatch(open: boolean, target: HTMLElement, popup: HTMLElement, onAlign: VoidFunction, onScroll: VoidFunction): void;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useWatch;
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var _util = require("../util");
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function useWatch(open, target, popup, onAlign, onScroll) {
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (open && target && popup) {
|
||||
const targetElement = target;
|
||||
const popupElement = popup;
|
||||
const targetScrollList = (0, _util.collectScroller)(targetElement);
|
||||
const popupScrollList = (0, _util.collectScroller)(popupElement);
|
||||
const win = (0, _util.getWin)(popupElement);
|
||||
const mergedList = new Set([win, ...targetScrollList, ...popupScrollList]);
|
||||
function notifyScroll() {
|
||||
onAlign();
|
||||
onScroll();
|
||||
}
|
||||
mergedList.forEach(scroller => {
|
||||
scroller.addEventListener('scroll', notifyScroll, {
|
||||
passive: true
|
||||
});
|
||||
});
|
||||
win.addEventListener('resize', notifyScroll, {
|
||||
passive: true
|
||||
});
|
||||
|
||||
// First time always do align
|
||||
onAlign();
|
||||
return () => {
|
||||
mergedList.forEach(scroller => {
|
||||
scroller.removeEventListener('scroll', notifyScroll);
|
||||
win.removeEventListener('resize', notifyScroll);
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [open, target, popup]);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Close if click on the window.
|
||||
* Return the function that click on the Popup element.
|
||||
*/
|
||||
export default function useWinClick(open: boolean, clickToHide: boolean, targetEle: HTMLElement, popupEle: HTMLElement, mask: boolean, maskClosable: boolean, inPopupOrChild: (target: EventTarget) => boolean, triggerOpen: (open: boolean) => void): () => void;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useWinClick;
|
||||
var _shadow = require("@rc-component/util/lib/Dom/shadow");
|
||||
var _warning = require("@rc-component/util/lib/warning");
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _util = require("../util");
|
||||
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; }
|
||||
/**
|
||||
* Close if click on the window.
|
||||
* Return the function that click on the Popup element.
|
||||
*/
|
||||
function useWinClick(open, clickToHide, targetEle, popupEle, mask, maskClosable, inPopupOrChild, triggerOpen) {
|
||||
const openRef = React.useRef(open);
|
||||
openRef.current = open;
|
||||
const popupPointerDownRef = React.useRef(false);
|
||||
|
||||
// Click to hide is special action since click popup element should not hide
|
||||
React.useEffect(() => {
|
||||
if (clickToHide && popupEle && (!mask || maskClosable)) {
|
||||
const onPointerDown = () => {
|
||||
popupPointerDownRef.current = false;
|
||||
};
|
||||
const onTriggerClose = e => {
|
||||
if (openRef.current && !inPopupOrChild(e.composedPath?.()?.[0] || e.target) && !popupPointerDownRef.current) {
|
||||
triggerOpen(false);
|
||||
}
|
||||
};
|
||||
const win = (0, _util.getWin)(popupEle);
|
||||
win.addEventListener('pointerdown', onPointerDown, true);
|
||||
win.addEventListener('mousedown', onTriggerClose, true);
|
||||
win.addEventListener('contextmenu', onTriggerClose, true);
|
||||
|
||||
// shadow root
|
||||
const targetShadowRoot = (0, _shadow.getShadowRoot)(targetEle);
|
||||
if (targetShadowRoot) {
|
||||
targetShadowRoot.addEventListener('mousedown', onTriggerClose, true);
|
||||
targetShadowRoot.addEventListener('contextmenu', onTriggerClose, true);
|
||||
}
|
||||
|
||||
// Warning if target and popup not in same root
|
||||
if (process.env.NODE_ENV !== 'production' && targetEle) {
|
||||
const targetRoot = targetEle.getRootNode?.();
|
||||
const popupRoot = popupEle.getRootNode?.();
|
||||
(0, _warning.warning)(targetRoot === popupRoot, `trigger element and popup element should in same shadow root.`);
|
||||
}
|
||||
return () => {
|
||||
win.removeEventListener('pointerdown', onPointerDown, true);
|
||||
win.removeEventListener('mousedown', onTriggerClose, true);
|
||||
win.removeEventListener('contextmenu', onTriggerClose, true);
|
||||
if (targetShadowRoot) {
|
||||
targetShadowRoot.removeEventListener('mousedown', onTriggerClose, true);
|
||||
targetShadowRoot.removeEventListener('contextmenu', onTriggerClose, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [clickToHide, targetEle, popupEle, mask, maskClosable]);
|
||||
function onPopupPointerDown() {
|
||||
popupPointerDownRef.current = true;
|
||||
}
|
||||
return onPopupPointerDown;
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import type { CSSMotionProps } from '@rc-component/motion';
|
||||
import * as React from 'react';
|
||||
import { type MobileConfig } from './Popup';
|
||||
import type { ActionType, AlignType, ArrowTypeOuter, BuildInPlacements } from './interface';
|
||||
export type { ActionType, AlignType, ArrowTypeOuter as ArrowType, BuildInPlacements, };
|
||||
import UniqueProvider, { type UniqueProviderProps } from './UniqueProvider';
|
||||
export { UniqueProvider };
|
||||
export type { UniqueProviderProps };
|
||||
export interface TriggerRef {
|
||||
nativeElement: HTMLElement;
|
||||
popupElement: HTMLDivElement;
|
||||
forceAlign: VoidFunction;
|
||||
}
|
||||
export interface TriggerProps {
|
||||
children: React.ReactElement<any> | ((info: {
|
||||
open: boolean;
|
||||
}) => React.ReactElement<any>);
|
||||
action?: ActionType | ActionType[];
|
||||
showAction?: ActionType[];
|
||||
hideAction?: ActionType[];
|
||||
prefixCls?: string;
|
||||
zIndex?: number;
|
||||
onPopupAlign?: (element: HTMLElement, align: AlignType) => void;
|
||||
stretch?: string;
|
||||
popupVisible?: boolean;
|
||||
defaultPopupVisible?: boolean;
|
||||
onOpenChange?: (visible: boolean) => void;
|
||||
afterOpenChange?: (visible: boolean) => void;
|
||||
/** @deprecated Use `onOpenChange` instead */
|
||||
onPopupVisibleChange?: (visible: boolean) => void;
|
||||
/** @deprecated Use `afterOpenChange` instead */
|
||||
afterPopupVisibleChange?: (visible: boolean) => void;
|
||||
getPopupContainer?: (node: HTMLElement) => HTMLElement;
|
||||
forceRender?: boolean;
|
||||
autoDestroy?: boolean;
|
||||
mask?: boolean;
|
||||
maskClosable?: boolean;
|
||||
/** Set popup motion. You can ref `rc-motion` for more info. */
|
||||
popupMotion?: CSSMotionProps;
|
||||
/** Set mask motion. You can ref `rc-motion` for more info. */
|
||||
maskMotion?: CSSMotionProps;
|
||||
mouseEnterDelay?: number;
|
||||
mouseLeaveDelay?: number;
|
||||
focusDelay?: number;
|
||||
blurDelay?: number;
|
||||
popup: React.ReactNode | (() => React.ReactNode);
|
||||
popupPlacement?: string;
|
||||
builtinPlacements?: BuildInPlacements;
|
||||
popupAlign?: AlignType;
|
||||
popupClassName?: string;
|
||||
/** Pass to `UniqueProvider` UniqueContainer */
|
||||
uniqueContainerClassName?: string;
|
||||
/** Pass to `UniqueProvider` UniqueContainer */
|
||||
uniqueContainerStyle?: React.CSSProperties;
|
||||
popupStyle?: React.CSSProperties;
|
||||
getPopupClassNameFromAlign?: (align: AlignType) => string;
|
||||
onPopupClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
alignPoint?: boolean;
|
||||
/**
|
||||
* Trigger will memo content when close.
|
||||
* This may affect the case if want to keep content update.
|
||||
* Set `fresh` to `false` will always keep update.
|
||||
*/
|
||||
fresh?: boolean;
|
||||
/**
|
||||
* Config with UniqueProvider to shared the floating popup.
|
||||
*/
|
||||
unique?: boolean;
|
||||
arrow?: boolean | ArrowTypeOuter;
|
||||
/**
|
||||
* @private Bump fixed position at bottom in mobile.
|
||||
* Will replace the config of root props.
|
||||
* This will directly trade as mobile view which will not check what real is.
|
||||
* This is internal usage currently, do not use in your prod.
|
||||
*/
|
||||
mobile?: MobileConfig;
|
||||
}
|
||||
export declare function generateTrigger(PortalComponent?: React.ComponentType<any>): React.ForwardRefExoticComponent<TriggerProps & React.RefAttributes<TriggerRef>>;
|
||||
declare const _default: React.ForwardRefExoticComponent<TriggerProps & React.RefAttributes<TriggerRef>>;
|
||||
export default _default;
|
||||
+576
@@ -0,0 +1,576 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
Object.defineProperty(exports, "UniqueProvider", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _UniqueProvider.default;
|
||||
}
|
||||
});
|
||||
exports.default = void 0;
|
||||
exports.generateTrigger = generateTrigger;
|
||||
var _portal = _interopRequireDefault(require("@rc-component/portal"));
|
||||
var _clsx = require("clsx");
|
||||
var _resizeObserver = require("@rc-component/resize-observer");
|
||||
var _findDOMNode = require("@rc-component/util/lib/Dom/findDOMNode");
|
||||
var _shadow = require("@rc-component/util/lib/Dom/shadow");
|
||||
var _ref = require("@rc-component/util/lib/ref");
|
||||
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
|
||||
var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId"));
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _Popup = _interopRequireDefault(require("./Popup"));
|
||||
var _context = _interopRequireWildcard(require("./context"));
|
||||
var _useAction = _interopRequireDefault(require("./hooks/useAction"));
|
||||
var _useAlign = _interopRequireDefault(require("./hooks/useAlign"));
|
||||
var _useDelay = _interopRequireDefault(require("./hooks/useDelay"));
|
||||
var _useWatch = _interopRequireDefault(require("./hooks/useWatch"));
|
||||
var _useWinClick = _interopRequireDefault(require("./hooks/useWinClick"));
|
||||
var _util = require("./util");
|
||||
var _UniqueProvider = _interopRequireDefault(require("./UniqueProvider"));
|
||||
var _util2 = require("@rc-component/util");
|
||||
var _reactDom = require("react-dom");
|
||||
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 }; }
|
||||
// Removed Props List
|
||||
// Seems this can be auto
|
||||
// getDocument?: (element?: HTMLElement) => Document;
|
||||
|
||||
// New version will not wrap popup with `rc-trigger-popup-content` when multiple children
|
||||
|
||||
function generateTrigger(PortalComponent = _portal.default) {
|
||||
const Trigger = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
prefixCls = 'rc-trigger-popup',
|
||||
children,
|
||||
// Action
|
||||
action = 'hover',
|
||||
showAction,
|
||||
hideAction,
|
||||
// Open
|
||||
popupVisible,
|
||||
defaultPopupVisible,
|
||||
onOpenChange,
|
||||
afterOpenChange,
|
||||
onPopupVisibleChange,
|
||||
afterPopupVisibleChange,
|
||||
// Delay
|
||||
mouseEnterDelay,
|
||||
mouseLeaveDelay = 0.1,
|
||||
focusDelay,
|
||||
blurDelay,
|
||||
// Mask
|
||||
mask,
|
||||
maskClosable = true,
|
||||
// Portal
|
||||
getPopupContainer,
|
||||
forceRender,
|
||||
autoDestroy,
|
||||
// Popup
|
||||
popup,
|
||||
popupClassName,
|
||||
uniqueContainerClassName,
|
||||
uniqueContainerStyle,
|
||||
popupStyle,
|
||||
popupPlacement,
|
||||
builtinPlacements = {},
|
||||
popupAlign,
|
||||
zIndex,
|
||||
stretch,
|
||||
getPopupClassNameFromAlign,
|
||||
fresh,
|
||||
unique,
|
||||
alignPoint,
|
||||
onPopupClick,
|
||||
onPopupAlign,
|
||||
// Arrow
|
||||
arrow,
|
||||
// Motion
|
||||
popupMotion,
|
||||
maskMotion,
|
||||
// Private
|
||||
mobile,
|
||||
...restProps
|
||||
} = props;
|
||||
const mergedAutoDestroy = autoDestroy || false;
|
||||
const openUncontrolled = popupVisible === undefined;
|
||||
|
||||
// =========================== Mobile ===========================
|
||||
const isMobile = !!mobile;
|
||||
|
||||
// ========================== Context ===========================
|
||||
const subPopupElements = React.useRef({});
|
||||
const parentContext = React.useContext(_context.default);
|
||||
const context = React.useMemo(() => {
|
||||
return {
|
||||
registerSubPopup: (id, subPopupEle) => {
|
||||
subPopupElements.current[id] = subPopupEle;
|
||||
parentContext?.registerSubPopup(id, subPopupEle);
|
||||
}
|
||||
};
|
||||
}, [parentContext]);
|
||||
|
||||
// ======================== UniqueContext =========================
|
||||
const uniqueContext = React.useContext(_context.UniqueContext);
|
||||
|
||||
// =========================== Popup ============================
|
||||
const id = (0, _useId.default)();
|
||||
const [popupEle, setPopupEle] = React.useState(null);
|
||||
|
||||
// Used for forwardRef popup. Not use internal
|
||||
const externalPopupRef = React.useRef(null);
|
||||
const setPopupRef = (0, _useEvent.default)(node => {
|
||||
externalPopupRef.current = node;
|
||||
if ((0, _findDOMNode.isDOM)(node) && popupEle !== node) {
|
||||
setPopupEle(node);
|
||||
}
|
||||
parentContext?.registerSubPopup(id, node);
|
||||
});
|
||||
|
||||
// =========================== Target ===========================
|
||||
// Use state to control here since `useRef` update not trigger render
|
||||
const [targetEle, setTargetEle] = React.useState(null);
|
||||
|
||||
// Used for forwardRef target. Not use internal
|
||||
const externalForwardRef = React.useRef(null);
|
||||
const setTargetRef = (0, _useEvent.default)(node => {
|
||||
const domNode = (0, _findDOMNode.getDOM)(node);
|
||||
if ((0, _findDOMNode.isDOM)(domNode) && targetEle !== domNode) {
|
||||
setTargetEle(domNode);
|
||||
externalForwardRef.current = domNode;
|
||||
}
|
||||
});
|
||||
const cloneProps = {};
|
||||
const inPopupOrChild = (0, _useEvent.default)(ele => {
|
||||
const childDOM = targetEle;
|
||||
return childDOM?.contains(ele) || (0, _shadow.getShadowRoot)(childDOM)?.host === ele || ele === childDOM || popupEle?.contains(ele) || (0, _shadow.getShadowRoot)(popupEle)?.host === ele || ele === popupEle || Object.values(subPopupElements.current).some(subPopupEle => subPopupEle?.contains(ele) || ele === subPopupEle);
|
||||
});
|
||||
|
||||
// =========================== Arrow ============================
|
||||
const innerArrow = arrow ? {
|
||||
// true and Object likely
|
||||
...(arrow !== true ? arrow : {})
|
||||
} : null;
|
||||
|
||||
// ============================ Open ============================
|
||||
const [internalOpen, setInternalOpen] = (0, _util2.useControlledState)(defaultPopupVisible || false, popupVisible);
|
||||
const mergedOpen = internalOpen || false;
|
||||
|
||||
// ========================== Children ==========================
|
||||
const child = React.useMemo(() => {
|
||||
const nextChild = typeof children === 'function' ? children({
|
||||
open: mergedOpen
|
||||
}) : children;
|
||||
return React.Children.only(nextChild);
|
||||
}, [children, mergedOpen]);
|
||||
const originChildProps = child?.props || {};
|
||||
|
||||
// Support ref
|
||||
const isOpen = (0, _useEvent.default)(() => mergedOpen);
|
||||
|
||||
// Extract common options for UniqueProvider
|
||||
const getUniqueOptions = (0, _useEvent.default)((delay = 0) => ({
|
||||
popup,
|
||||
target: targetEle,
|
||||
delay,
|
||||
prefixCls,
|
||||
popupClassName,
|
||||
uniqueContainerClassName,
|
||||
uniqueContainerStyle,
|
||||
popupStyle,
|
||||
popupPlacement,
|
||||
builtinPlacements,
|
||||
popupAlign,
|
||||
zIndex,
|
||||
mask,
|
||||
maskClosable,
|
||||
popupMotion,
|
||||
maskMotion,
|
||||
arrow: innerArrow,
|
||||
getPopupContainer,
|
||||
getPopupClassNameFromAlign,
|
||||
id,
|
||||
onEsc
|
||||
}));
|
||||
|
||||
// Handle controlled state changes for UniqueProvider
|
||||
// Only sync to UniqueProvider when it's controlled mode
|
||||
// If there is a parentContext, don't call uniqueContext methods
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (uniqueContext && unique && targetEle && !openUncontrolled && !parentContext) {
|
||||
if (mergedOpen) {
|
||||
uniqueContext.show(getUniqueOptions(mouseEnterDelay), isOpen);
|
||||
} else {
|
||||
uniqueContext.hide(mouseLeaveDelay);
|
||||
}
|
||||
}
|
||||
}, [mergedOpen, targetEle]);
|
||||
const openRef = React.useRef(mergedOpen);
|
||||
openRef.current = mergedOpen;
|
||||
const internalTriggerOpen = (0, _useEvent.default)(nextOpen => {
|
||||
(0, _reactDom.flushSync)(() => {
|
||||
if (mergedOpen !== nextOpen) {
|
||||
setInternalOpen(nextOpen);
|
||||
onOpenChange?.(nextOpen);
|
||||
onPopupVisibleChange?.(nextOpen);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger for delay
|
||||
const delayInvoke = (0, _useDelay.default)();
|
||||
const triggerOpen = (nextOpen, delay = 0) => {
|
||||
// If it's controlled mode, always use internal trigger logic
|
||||
// UniqueProvider will be synced through useLayoutEffect
|
||||
if (popupVisible !== undefined) {
|
||||
delayInvoke(() => {
|
||||
internalTriggerOpen(nextOpen);
|
||||
}, delay);
|
||||
return;
|
||||
}
|
||||
|
||||
// If UniqueContext exists and not controlled, pass delay to Provider instead of handling it internally
|
||||
// If there is a parentContext, don't call uniqueContext methods
|
||||
if (uniqueContext && unique && openUncontrolled && !parentContext) {
|
||||
if (nextOpen) {
|
||||
uniqueContext.show(getUniqueOptions(delay), isOpen);
|
||||
} else {
|
||||
uniqueContext.hide(delay);
|
||||
}
|
||||
return;
|
||||
}
|
||||
delayInvoke(() => {
|
||||
internalTriggerOpen(nextOpen);
|
||||
}, delay);
|
||||
};
|
||||
function onEsc({
|
||||
top
|
||||
}) {
|
||||
if (top) {
|
||||
triggerOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================== Motion ============================
|
||||
const [inMotion, setInMotion] = React.useState(false);
|
||||
(0, _useLayoutEffect.default)(firstMount => {
|
||||
if (!firstMount || mergedOpen) {
|
||||
setInMotion(true);
|
||||
}
|
||||
}, [mergedOpen]);
|
||||
const [motionPrepareResolve, setMotionPrepareResolve] = React.useState(null);
|
||||
|
||||
// =========================== Align ============================
|
||||
const [mousePos, setMousePos] = React.useState(null);
|
||||
const setMousePosByEvent = event => {
|
||||
setMousePos([event.clientX, event.clientY]);
|
||||
};
|
||||
const [ready, offsetX, offsetY, offsetR, offsetB, arrowX, arrowY, scaleX, scaleY, alignInfo, onAlign] = (0, _useAlign.default)(mergedOpen, popupEle, alignPoint && mousePos !== null ? mousePos : targetEle, popupPlacement, builtinPlacements, popupAlign, onPopupAlign, isMobile);
|
||||
const [showActions, hideActions] = (0, _useAction.default)(action, showAction, hideAction);
|
||||
const clickToShow = showActions.has('click');
|
||||
const clickToHide = hideActions.has('click') || hideActions.has('contextMenu');
|
||||
const triggerAlign = (0, _useEvent.default)(() => {
|
||||
if (!inMotion) {
|
||||
onAlign();
|
||||
}
|
||||
});
|
||||
const onScroll = () => {
|
||||
if (openRef.current && alignPoint && clickToHide) {
|
||||
triggerOpen(false);
|
||||
}
|
||||
};
|
||||
(0, _useWatch.default)(mergedOpen, targetEle, popupEle, triggerAlign, onScroll);
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
triggerAlign();
|
||||
}, [mousePos, popupPlacement]);
|
||||
|
||||
// When no builtinPlacements and popupAlign changed
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (mergedOpen && !builtinPlacements?.[popupPlacement]) {
|
||||
triggerAlign();
|
||||
}
|
||||
}, [JSON.stringify(popupAlign)]);
|
||||
const alignedClassName = React.useMemo(() => {
|
||||
const baseClassName = (0, _util.getAlignPopupClassName)(builtinPlacements, prefixCls, alignInfo, alignPoint);
|
||||
return (0, _clsx.clsx)(baseClassName, getPopupClassNameFromAlign?.(alignInfo));
|
||||
}, [alignInfo, getPopupClassNameFromAlign, builtinPlacements, prefixCls, alignPoint]);
|
||||
|
||||
// ============================ Refs ============================
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
nativeElement: externalForwardRef.current,
|
||||
popupElement: externalPopupRef.current,
|
||||
forceAlign: triggerAlign
|
||||
}));
|
||||
|
||||
// ========================== Stretch ===========================
|
||||
const [targetWidth, setTargetWidth] = React.useState(0);
|
||||
const [targetHeight, setTargetHeight] = React.useState(0);
|
||||
const syncTargetSize = () => {
|
||||
if (stretch && targetEle) {
|
||||
const rect = targetEle.getBoundingClientRect();
|
||||
setTargetWidth(rect.width);
|
||||
setTargetHeight(rect.height);
|
||||
}
|
||||
};
|
||||
const onTargetResize = () => {
|
||||
syncTargetSize();
|
||||
triggerAlign();
|
||||
};
|
||||
|
||||
// ========================== Motion ============================
|
||||
const onVisibleChanged = visible => {
|
||||
setInMotion(false);
|
||||
onAlign();
|
||||
afterOpenChange?.(visible);
|
||||
afterPopupVisibleChange?.(visible);
|
||||
};
|
||||
|
||||
// We will trigger align when motion is in prepare
|
||||
const onPrepare = () => new Promise(resolve => {
|
||||
syncTargetSize();
|
||||
setMotionPrepareResolve(() => resolve);
|
||||
});
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (motionPrepareResolve) {
|
||||
onAlign();
|
||||
motionPrepareResolve();
|
||||
setMotionPrepareResolve(null);
|
||||
}
|
||||
}, [motionPrepareResolve]);
|
||||
|
||||
// =========================== Action ===========================
|
||||
/**
|
||||
* Util wrapper for trigger action
|
||||
* @param eventName Listen event name
|
||||
* @param nextOpen Next open state after trigger
|
||||
* @param delay Delay to trigger open change
|
||||
* @param callback Callback if current event need additional action
|
||||
* @param ignoreCheck Ignore current event if check return true
|
||||
*/
|
||||
function wrapperAction(eventName, nextOpen, delay, callback, ignoreCheck) {
|
||||
cloneProps[eventName] = (event, ...args) => {
|
||||
if (!ignoreCheck || !ignoreCheck()) {
|
||||
callback?.(event);
|
||||
triggerOpen(nextOpen, delay);
|
||||
}
|
||||
|
||||
// Pass to origin
|
||||
originChildProps[eventName]?.(event, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
// ======================= Action: Touch ========================
|
||||
const touchToShow = showActions.has('touch');
|
||||
const touchToHide = hideActions.has('touch');
|
||||
|
||||
/** Used for prevent `hover` event conflict with mobile env */
|
||||
const touchedRef = React.useRef(false);
|
||||
if (touchToShow || touchToHide) {
|
||||
cloneProps.onTouchStart = (...args) => {
|
||||
touchedRef.current = true;
|
||||
if (openRef.current && touchToHide) {
|
||||
triggerOpen(false);
|
||||
} else if (!openRef.current && touchToShow) {
|
||||
triggerOpen(true);
|
||||
}
|
||||
|
||||
// Pass to origin
|
||||
originChildProps.onTouchStart?.(...args);
|
||||
};
|
||||
}
|
||||
|
||||
// ======================= Action: Click ========================
|
||||
if (clickToShow || clickToHide) {
|
||||
cloneProps.onClick = (event, ...args) => {
|
||||
if (openRef.current && clickToHide) {
|
||||
triggerOpen(false);
|
||||
} else if (!openRef.current && clickToShow) {
|
||||
setMousePosByEvent(event);
|
||||
triggerOpen(true);
|
||||
}
|
||||
|
||||
// Pass to origin
|
||||
originChildProps.onClick?.(event, ...args);
|
||||
touchedRef.current = false;
|
||||
};
|
||||
}
|
||||
|
||||
// Click to hide is special action since click popup element should not hide
|
||||
const onPopupPointerDown = (0, _useWinClick.default)(mergedOpen, clickToHide || touchToHide, targetEle, popupEle, mask, maskClosable, inPopupOrChild, triggerOpen);
|
||||
|
||||
// ======================= Action: Hover ========================
|
||||
const hoverToShow = showActions.has('hover');
|
||||
const hoverToHide = hideActions.has('hover');
|
||||
let onPopupMouseEnter;
|
||||
let onPopupMouseLeave;
|
||||
const ignoreMouseTrigger = () => {
|
||||
return touchedRef.current;
|
||||
};
|
||||
if (hoverToShow) {
|
||||
const onMouseEnterCallback = event => {
|
||||
setMousePosByEvent(event);
|
||||
};
|
||||
|
||||
// Compatible with old browser which not support pointer event
|
||||
wrapperAction('onMouseEnter', true, mouseEnterDelay, onMouseEnterCallback, ignoreMouseTrigger);
|
||||
wrapperAction('onPointerEnter', true, mouseEnterDelay, onMouseEnterCallback, ignoreMouseTrigger);
|
||||
onPopupMouseEnter = event => {
|
||||
// Only trigger re-open when popup is visible
|
||||
if ((mergedOpen || inMotion) && popupEle?.contains(event.target)) {
|
||||
triggerOpen(true, mouseEnterDelay);
|
||||
}
|
||||
};
|
||||
|
||||
// Align Point
|
||||
if (alignPoint) {
|
||||
cloneProps.onMouseMove = event => {
|
||||
originChildProps.onMouseMove?.(event);
|
||||
};
|
||||
}
|
||||
}
|
||||
if (hoverToHide) {
|
||||
wrapperAction('onMouseLeave', false, mouseLeaveDelay, undefined, ignoreMouseTrigger);
|
||||
wrapperAction('onPointerLeave', false, mouseLeaveDelay, undefined, ignoreMouseTrigger);
|
||||
onPopupMouseLeave = () => {
|
||||
triggerOpen(false, mouseLeaveDelay);
|
||||
};
|
||||
}
|
||||
|
||||
// ======================= Action: Focus ========================
|
||||
if (showActions.has('focus')) {
|
||||
wrapperAction('onFocus', true, focusDelay);
|
||||
}
|
||||
if (hideActions.has('focus')) {
|
||||
wrapperAction('onBlur', false, blurDelay);
|
||||
}
|
||||
|
||||
// ==================== Action: ContextMenu =====================
|
||||
if (showActions.has('contextMenu')) {
|
||||
cloneProps.onContextMenu = (event, ...args) => {
|
||||
if (openRef.current && hideActions.has('contextMenu')) {
|
||||
triggerOpen(false);
|
||||
} else {
|
||||
setMousePosByEvent(event);
|
||||
triggerOpen(true);
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
// Pass to origin
|
||||
originChildProps.onContextMenu?.(event, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
// ============================ Perf ============================
|
||||
const rendedRef = React.useRef(false);
|
||||
rendedRef.current ||= forceRender || mergedOpen || inMotion;
|
||||
|
||||
// =========================== Render ===========================
|
||||
const mergedChildrenProps = {
|
||||
...originChildProps,
|
||||
...cloneProps
|
||||
};
|
||||
|
||||
// Pass props into cloneProps for nest usage
|
||||
const passedProps = {};
|
||||
const passedEventList = ['onContextMenu', 'onClick', 'onMouseDown', 'onTouchStart', 'onMouseEnter', 'onMouseLeave', 'onFocus', 'onBlur'];
|
||||
passedEventList.forEach(eventName => {
|
||||
if (restProps[eventName]) {
|
||||
passedProps[eventName] = (...args) => {
|
||||
mergedChildrenProps[eventName]?.(...args);
|
||||
restProps[eventName](...args);
|
||||
};
|
||||
}
|
||||
});
|
||||
const arrowPos = {
|
||||
x: arrowX,
|
||||
y: arrowY
|
||||
};
|
||||
|
||||
// =================== Resize Observer ===================
|
||||
// Use hook to observe target element resize
|
||||
// Pass targetEle directly instead of a function so the hook will re-observe when target changes
|
||||
(0, _resizeObserver.useResizeObserver)(mergedOpen, targetEle, onTargetResize);
|
||||
|
||||
// Compose refs
|
||||
const mergedRef = (0, _ref.useComposeRef)(setTargetRef, (0, _ref.getNodeRef)(child));
|
||||
|
||||
// Child Node
|
||||
const triggerNode = /*#__PURE__*/React.cloneElement(child, {
|
||||
...mergedChildrenProps,
|
||||
...passedProps,
|
||||
ref: mergedRef
|
||||
});
|
||||
|
||||
// Render
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, triggerNode, rendedRef.current && (!uniqueContext || !unique) && /*#__PURE__*/React.createElement(_context.default.Provider, {
|
||||
value: context
|
||||
}, /*#__PURE__*/React.createElement(_Popup.default, {
|
||||
portal: PortalComponent,
|
||||
ref: setPopupRef,
|
||||
prefixCls: prefixCls,
|
||||
popup: popup,
|
||||
className: (0, _clsx.clsx)(popupClassName, !isMobile && alignedClassName),
|
||||
style: popupStyle,
|
||||
target: targetEle,
|
||||
onMouseEnter: onPopupMouseEnter,
|
||||
onMouseLeave: onPopupMouseLeave
|
||||
// https://github.com/ant-design/ant-design/issues/43924
|
||||
,
|
||||
onPointerEnter: onPopupMouseEnter,
|
||||
zIndex: zIndex
|
||||
// Open
|
||||
,
|
||||
open: mergedOpen,
|
||||
keepDom: inMotion,
|
||||
fresh: fresh
|
||||
// Click
|
||||
,
|
||||
onClick: onPopupClick,
|
||||
onPointerDownCapture: onPopupPointerDown
|
||||
// Mask
|
||||
,
|
||||
mask: mask
|
||||
// Motion
|
||||
,
|
||||
motion: popupMotion,
|
||||
maskMotion: maskMotion,
|
||||
onVisibleChanged: onVisibleChanged,
|
||||
onPrepare: onPrepare
|
||||
// Portal
|
||||
,
|
||||
forceRender: forceRender,
|
||||
autoDestroy: mergedAutoDestroy,
|
||||
getPopupContainer: getPopupContainer,
|
||||
onEsc: onEsc
|
||||
// Arrow
|
||||
,
|
||||
align: alignInfo,
|
||||
arrow: innerArrow,
|
||||
arrowPos: arrowPos
|
||||
// Align
|
||||
,
|
||||
ready: ready,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
offsetR: offsetR,
|
||||
offsetB: offsetB,
|
||||
onAlign: triggerAlign
|
||||
// Stretch
|
||||
,
|
||||
stretch: stretch,
|
||||
targetWidth: targetWidth / scaleX,
|
||||
targetHeight: targetHeight / scaleY
|
||||
// Mobile
|
||||
,
|
||||
mobile: mobile
|
||||
})));
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Trigger.displayName = 'Trigger';
|
||||
}
|
||||
return Trigger;
|
||||
}
|
||||
var _default = exports.default = generateTrigger(_portal.default);
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/// <reference types="react" />
|
||||
export type Placement = 'top' | 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
|
||||
export type AlignPointTopBottom = 't' | 'b' | 'c';
|
||||
export type AlignPointLeftRight = 'l' | 'r' | 'c';
|
||||
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
|
||||
export type AlignPoint = `${AlignPointTopBottom}${AlignPointLeftRight}`;
|
||||
export type OffsetType = number | `${number}%`;
|
||||
export interface AlignType {
|
||||
/**
|
||||
* move point of source node to align with point of target node.
|
||||
* Such as ['tr','cc'], align top right point of source node with center point of target node.
|
||||
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
|
||||
points?: (string | AlignPoint)[];
|
||||
/**
|
||||
* @private Do not use in your production code
|
||||
*/
|
||||
_experimental?: Record<string, any>;
|
||||
/**
|
||||
* offset source node by offset[0] in x and offset[1] in y.
|
||||
* If offset contains percentage string value, it is relative to sourceNode region.
|
||||
*/
|
||||
offset?: OffsetType[];
|
||||
/**
|
||||
* offset target node by offset[0] in x and offset[1] in y.
|
||||
* If targetOffset contains percentage string value, it is relative to targetNode region.
|
||||
*/
|
||||
targetOffset?: OffsetType[];
|
||||
/**
|
||||
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
|
||||
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
|
||||
*/
|
||||
overflow?: {
|
||||
adjustX?: boolean | number;
|
||||
adjustY?: boolean | number;
|
||||
shiftX?: boolean | number;
|
||||
shiftY?: boolean | number;
|
||||
};
|
||||
/** Auto adjust arrow position */
|
||||
autoArrow?: boolean;
|
||||
/**
|
||||
* Config visible region check of html node. Default `visible`:
|
||||
* - `visible`:
|
||||
* The visible region of user browser window.
|
||||
* Use `clientHeight` for check.
|
||||
* If `visible` region not satisfy, fallback to `scroll`.
|
||||
* - `scroll`:
|
||||
* The whole region of the html scroll area.
|
||||
* Use `scrollHeight` for check.
|
||||
* - `visibleFirst`:
|
||||
* Similar to `visible`, but if `visible` region not satisfy, fallback to `scroll`.
|
||||
*/
|
||||
htmlRegion?: 'visible' | 'scroll' | 'visibleFirst';
|
||||
/**
|
||||
* Auto chose position with `top` or `bottom` by the align result
|
||||
*/
|
||||
dynamicInset?: boolean;
|
||||
/**
|
||||
* Whether use css right instead of left to position
|
||||
*/
|
||||
useCssRight?: boolean;
|
||||
/**
|
||||
* Whether use css bottom instead of top to position
|
||||
*/
|
||||
useCssBottom?: boolean;
|
||||
/**
|
||||
* Whether use css transform instead of left/top/right/bottom to position if browser supports.
|
||||
* Defaults to false.
|
||||
*/
|
||||
useCssTransform?: boolean;
|
||||
ignoreShake?: boolean;
|
||||
}
|
||||
export interface ArrowTypeOuter {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
content?: React.ReactNode;
|
||||
}
|
||||
export type ArrowPos = {
|
||||
x?: number;
|
||||
y?: number;
|
||||
};
|
||||
export type BuildInPlacements = Record<string, AlignType>;
|
||||
export type StretchType = string;
|
||||
export type ActionType = 'hover' | 'focus' | 'click' | 'contextMenu';
|
||||
export type AnimationType = string;
|
||||
export type TransitionNameType = string;
|
||||
export interface Point {
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
}
|
||||
export interface CommonEventHandler {
|
||||
remove: () => void;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { UniqueProvider } from './index';
|
||||
declare const _default: React.ForwardRefExoticComponent<import("./index").TriggerProps & React.RefAttributes<import("./index").TriggerRef>>;
|
||||
export default _default;
|
||||
export { UniqueProvider };
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
Object.defineProperty(exports, "UniqueProvider", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _index.UniqueProvider;
|
||||
}
|
||||
});
|
||||
exports.default = void 0;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _index = require("./index");
|
||||
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 MockPortal = ({
|
||||
open,
|
||||
autoDestroy,
|
||||
children,
|
||||
getContainer
|
||||
}) => {
|
||||
const [visible, setVisible] = React.useState(open);
|
||||
React.useEffect(() => {
|
||||
getContainer?.();
|
||||
});
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
setVisible(true);
|
||||
} else if (autoDestroy) {
|
||||
setVisible(false);
|
||||
}
|
||||
}, [open, autoDestroy]);
|
||||
return visible ? children : null;
|
||||
};
|
||||
var _default = exports.default = (0, _index.generateTrigger)(MockPortal);
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import type { AlignType, BuildInPlacements } from './interface';
|
||||
export declare function getAlignPopupClassName(builtinPlacements: BuildInPlacements, prefixCls: string, align: AlignType, isAlignPoint: boolean): string;
|
||||
export declare function getWin(ele: HTMLElement): Window & typeof globalThis;
|
||||
/**
|
||||
* Get all the scrollable parent elements of the element
|
||||
* @param ele The element to be detected
|
||||
* @param areaOnly Only return the parent which will cut visible area
|
||||
*/
|
||||
export declare function collectScroller(ele: HTMLElement): HTMLElement[];
|
||||
export declare function toNum(num: number, defaultValue?: number): number;
|
||||
export interface VisibleArea {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* **************************************
|
||||
* * Border *
|
||||
* * ************************** *
|
||||
* * * * * *
|
||||
* * B * * S * B *
|
||||
* * o * * c * o *
|
||||
* * r * Content * r * r *
|
||||
* * d * * o * d *
|
||||
* * e * * l * e *
|
||||
* * r ******************** l * r *
|
||||
* * * Scroll * *
|
||||
* * ************************** *
|
||||
* * Border *
|
||||
* **************************************
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Get visible area of element
|
||||
*/
|
||||
export declare function getVisibleArea(initArea: VisibleArea, scrollerList?: HTMLElement[]): {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
};
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.collectScroller = collectScroller;
|
||||
exports.getAlignPopupClassName = getAlignPopupClassName;
|
||||
exports.getVisibleArea = getVisibleArea;
|
||||
exports.getWin = getWin;
|
||||
exports.toNum = toNum;
|
||||
function isPointsEq(a1 = [], a2 = [], isAlignPoint) {
|
||||
const getVal = (a, index) => a[index] || '';
|
||||
if (isAlignPoint) {
|
||||
return getVal(a1, 0) === getVal(a2, 0);
|
||||
}
|
||||
return getVal(a1, 0) === getVal(a2, 0) && getVal(a1, 1) === getVal(a2, 1);
|
||||
}
|
||||
function getAlignPopupClassName(builtinPlacements, prefixCls, align, isAlignPoint) {
|
||||
const {
|
||||
points
|
||||
} = align;
|
||||
const placements = Object.keys(builtinPlacements);
|
||||
for (let i = 0; i < placements.length; i += 1) {
|
||||
const placement = placements[i];
|
||||
if (isPointsEq(builtinPlacements[placement]?.points, points, isAlignPoint)) {
|
||||
return `${prefixCls}-placement-${placement}`;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
function getWin(ele) {
|
||||
return ele.ownerDocument.defaultView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the scrollable parent elements of the element
|
||||
* @param ele The element to be detected
|
||||
* @param areaOnly Only return the parent which will cut visible area
|
||||
*/
|
||||
function collectScroller(ele) {
|
||||
const scrollerList = [];
|
||||
let current = ele?.parentElement;
|
||||
const scrollStyle = ['hidden', 'scroll', 'clip', 'auto'];
|
||||
while (current) {
|
||||
const {
|
||||
overflowX,
|
||||
overflowY,
|
||||
overflow
|
||||
} = getWin(current).getComputedStyle(current);
|
||||
if ([overflowX, overflowY, overflow].some(o => scrollStyle.includes(o))) {
|
||||
scrollerList.push(current);
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
return scrollerList;
|
||||
}
|
||||
function toNum(num, defaultValue = 1) {
|
||||
return Number.isNaN(num) ? defaultValue : num;
|
||||
}
|
||||
function getPxValue(val) {
|
||||
return toNum(parseFloat(val), 0);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* **************************************
|
||||
* * Border *
|
||||
* * ************************** *
|
||||
* * * * * *
|
||||
* * B * * S * B *
|
||||
* * o * * c * o *
|
||||
* * r * Content * r * r *
|
||||
* * d * * o * d *
|
||||
* * e * * l * e *
|
||||
* * r ******************** l * r *
|
||||
* * * Scroll * *
|
||||
* * ************************** *
|
||||
* * Border *
|
||||
* **************************************
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Get visible area of element
|
||||
*/
|
||||
function getVisibleArea(initArea, scrollerList) {
|
||||
const visibleArea = {
|
||||
...initArea
|
||||
};
|
||||
(scrollerList || []).forEach(ele => {
|
||||
if (ele instanceof HTMLBodyElement || ele instanceof HTMLHtmlElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if static position which will not affect visible area
|
||||
const {
|
||||
overflow,
|
||||
overflowClipMargin,
|
||||
borderTopWidth,
|
||||
borderBottomWidth,
|
||||
borderLeftWidth,
|
||||
borderRightWidth
|
||||
} = getWin(ele).getComputedStyle(ele);
|
||||
const eleRect = ele.getBoundingClientRect();
|
||||
const {
|
||||
offsetHeight: eleOutHeight,
|
||||
clientHeight: eleInnerHeight,
|
||||
offsetWidth: eleOutWidth,
|
||||
clientWidth: eleInnerWidth
|
||||
} = ele;
|
||||
const borderTopNum = getPxValue(borderTopWidth);
|
||||
const borderBottomNum = getPxValue(borderBottomWidth);
|
||||
const borderLeftNum = getPxValue(borderLeftWidth);
|
||||
const borderRightNum = getPxValue(borderRightWidth);
|
||||
const scaleX = toNum(Math.round(eleRect.width / eleOutWidth * 1000) / 1000);
|
||||
const scaleY = toNum(Math.round(eleRect.height / eleOutHeight * 1000) / 1000);
|
||||
|
||||
// Original visible area
|
||||
const eleScrollWidth = (eleOutWidth - eleInnerWidth - borderLeftNum - borderRightNum) * scaleX;
|
||||
const eleScrollHeight = (eleOutHeight - eleInnerHeight - borderTopNum - borderBottomNum) * scaleY;
|
||||
|
||||
// Cut border size
|
||||
const scaledBorderTopWidth = borderTopNum * scaleY;
|
||||
const scaledBorderBottomWidth = borderBottomNum * scaleY;
|
||||
const scaledBorderLeftWidth = borderLeftNum * scaleX;
|
||||
const scaledBorderRightWidth = borderRightNum * scaleX;
|
||||
|
||||
// Clip margin
|
||||
let clipMarginWidth = 0;
|
||||
let clipMarginHeight = 0;
|
||||
if (overflow === 'clip') {
|
||||
const clipNum = getPxValue(overflowClipMargin);
|
||||
clipMarginWidth = clipNum * scaleX;
|
||||
clipMarginHeight = clipNum * scaleY;
|
||||
}
|
||||
|
||||
// Region
|
||||
const eleLeft = eleRect.x + scaledBorderLeftWidth - clipMarginWidth;
|
||||
const eleTop = eleRect.y + scaledBorderTopWidth - clipMarginHeight;
|
||||
const eleRight = eleLeft + eleRect.width + 2 * clipMarginWidth - scaledBorderLeftWidth - scaledBorderRightWidth - eleScrollWidth;
|
||||
const eleBottom = eleTop + eleRect.height + 2 * clipMarginHeight - scaledBorderTopWidth - scaledBorderBottomWidth - eleScrollHeight;
|
||||
visibleArea.left = Math.max(visibleArea.left, eleLeft);
|
||||
visibleArea.top = Math.max(visibleArea.top, eleTop);
|
||||
visibleArea.right = Math.min(visibleArea.right, eleRight);
|
||||
visibleArea.bottom = Math.min(visibleArea.bottom, eleBottom);
|
||||
});
|
||||
return visibleArea;
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "@rc-component/trigger",
|
||||
"version": "3.9.0",
|
||||
"description": "base abstract trigger component for react",
|
||||
"engines": {
|
||||
"node": ">=8.x"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-component",
|
||||
"react-trigger",
|
||||
"trigger"
|
||||
],
|
||||
"homepage": "https://github.com/react-component/trigger",
|
||||
"author": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/react-component/trigger.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/react-component/trigger/issues"
|
||||
},
|
||||
"files": [
|
||||
"es",
|
||||
"lib",
|
||||
"assets/**/*.css",
|
||||
"assets/**/*.less"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "./lib/index",
|
||||
"module": "./es/index",
|
||||
"scripts": {
|
||||
"start": "dumi dev",
|
||||
"build": "dumi build",
|
||||
"compile": "father build && lessc assets/index.less assets/index.css",
|
||||
"prepublishOnly": "npm run compile && rc-np",
|
||||
"lint": "eslint src/ docs/examples/ --ext .tsx,.ts,.jsx,.js",
|
||||
"test": "rc-test",
|
||||
"prettier": "prettier --write .",
|
||||
"coverage": "rc-test --coverage",
|
||||
"now-build": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.1.4",
|
||||
"@rc-component/portal": "^2.2.0",
|
||||
"@rc-component/resize-observer": "^1.1.1",
|
||||
"@rc-component/util": "^1.2.1",
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rc-component/father-plugin": "^2.0.0",
|
||||
"@rc-component/np": "^1.0.3",
|
||||
"@testing-library/jest-dom": "^6.1.4",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@umijs/fabric": "^4.0.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"dumi": "^2.1.0",
|
||||
"eslint": "^8.51.0",
|
||||
"father": "^4.0.0",
|
||||
"less": "^4.2.0",
|
||||
"prettier": "^3.3.3",
|
||||
"rc-test": "^7.0.13",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"regenerator-runtime": "^0.14.0",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user