From 6f3b576f3f10ae5633643a0f440a0f09b8fd4491 Mon Sep 17 00:00:00 2001
From: aojunhao123 <1844749591@qq.com>
Date: Sun, 17 Aug 2025 21:22:48 +0800
Subject: [PATCH 01/24] clean code
---
.dumirc.ts | 6 +-
.gitignore | 1 +
HISTORY.md | 59 --
README.md | 36 +-
assets/index.less | 79 --
assets/index/Mask.less | 65 --
assets/index/Mobile.less | 55 --
docs/demos/body-overflow.md | 8 -
docs/demos/case.md | 8 -
docs/demos/click-nested.md | 8 -
docs/demos/clip.md | 8 -
docs/demos/container.md | 8 -
docs/demos/inside.md | 8 -
docs/demos/large-popup.md | 8 -
docs/demos/mobile.md | 8 -
docs/demos/nested.md | 8 -
docs/demos/point.md | 8 -
docs/demos/portal.md | 8 -
docs/demos/shadow.md | 8 -
docs/demos/simple.md | 8 -
docs/demos/static-scroll.md | 8 -
docs/demos/visible-fallback.md | 8 -
docs/examples/body-overflow.tsx | 254 ------
docs/examples/case.less | 109 ---
docs/examples/case.tsx | 245 ------
docs/examples/click-nested.tsx | 94 ---
docs/examples/clip.tsx | 108 ---
docs/examples/container.tsx | 200 -----
docs/examples/inside.tsx | 180 -----
docs/examples/large-popup.tsx | 103 ---
docs/examples/mobile.tsx | 68 --
docs/examples/nested.tsx | 126 ---
docs/examples/point.less | 3 -
docs/examples/point.tsx | 89 ---
docs/examples/portal.tsx | 110 ---
docs/examples/shadow.tsx | 83 --
docs/examples/simple.tsx | 385 ---------
docs/examples/static-scroll.tsx | 64 --
docs/examples/visible-fallback.tsx | 109 ---
docs/index.md | 4 +-
now.json | 11 -
package.json | 16 +-
src/Popup/Arrow.tsx | 66 --
src/Popup/Mask.tsx | 48 --
src/Popup/PopupContent.tsx | 17 -
src/Popup/index.tsx | 325 --------
src/context.ts | 9 -
src/hooks/useAction.ts | 34 -
src/hooks/useAlign.ts | 762 ------------------
src/hooks/useWatch.ts | 48 --
src/hooks/useWinClick.ts | 92 ---
src/index.tsx | 790 -------------------
src/interface.ts | 126 +--
src/mock.tsx | 34 -
src/util.ts | 193 -----
tests/align.test.tsx | 299 -------
tests/arrow.test.jsx | 214 -----
tests/basic.test.jsx | 1180 ----------------------------
tests/flip-visibleFirst.test.tsx | 321 --------
tests/flip.test.tsx | 561 -------------
tests/flipShift.test.tsx | 229 ------
tests/mask.test.jsx | 40 -
tests/mobile.test.tsx | 116 ---
tests/motion.test.jsx | 161 ----
tests/perf.test.tsx | 102 ---
tests/point.test.jsx | 190 -----
tests/portal.test.jsx | 73 --
tests/rect.test.tsx | 77 --
tests/ref.test.tsx | 53 --
tests/shadow.test.tsx | 200 -----
tests/util.tsx | 104 ---
71 files changed, 46 insertions(+), 9200 deletions(-)
delete mode 100644 HISTORY.md
delete mode 100644 assets/index/Mask.less
delete mode 100644 assets/index/Mobile.less
delete mode 100644 docs/demos/body-overflow.md
delete mode 100644 docs/demos/case.md
delete mode 100644 docs/demos/click-nested.md
delete mode 100644 docs/demos/clip.md
delete mode 100644 docs/demos/container.md
delete mode 100644 docs/demos/inside.md
delete mode 100644 docs/demos/large-popup.md
delete mode 100644 docs/demos/mobile.md
delete mode 100644 docs/demos/nested.md
delete mode 100644 docs/demos/point.md
delete mode 100644 docs/demos/portal.md
delete mode 100644 docs/demos/shadow.md
delete mode 100644 docs/demos/simple.md
delete mode 100644 docs/demos/static-scroll.md
delete mode 100644 docs/demos/visible-fallback.md
delete mode 100644 docs/examples/body-overflow.tsx
delete mode 100644 docs/examples/case.less
delete mode 100644 docs/examples/case.tsx
delete mode 100644 docs/examples/click-nested.tsx
delete mode 100644 docs/examples/clip.tsx
delete mode 100644 docs/examples/container.tsx
delete mode 100644 docs/examples/inside.tsx
delete mode 100644 docs/examples/large-popup.tsx
delete mode 100644 docs/examples/mobile.tsx
delete mode 100644 docs/examples/nested.tsx
delete mode 100644 docs/examples/point.less
delete mode 100644 docs/examples/point.tsx
delete mode 100644 docs/examples/portal.tsx
delete mode 100644 docs/examples/shadow.tsx
delete mode 100644 docs/examples/simple.tsx
delete mode 100644 docs/examples/static-scroll.tsx
delete mode 100644 docs/examples/visible-fallback.tsx
delete mode 100644 now.json
delete mode 100644 src/Popup/Arrow.tsx
delete mode 100644 src/Popup/Mask.tsx
delete mode 100644 src/Popup/PopupContent.tsx
delete mode 100644 src/Popup/index.tsx
delete mode 100644 src/hooks/useAction.ts
delete mode 100644 src/hooks/useAlign.ts
delete mode 100644 src/hooks/useWatch.ts
delete mode 100644 src/hooks/useWinClick.ts
delete mode 100644 src/mock.tsx
delete mode 100644 tests/align.test.tsx
delete mode 100644 tests/arrow.test.jsx
delete mode 100644 tests/basic.test.jsx
delete mode 100644 tests/flip-visibleFirst.test.tsx
delete mode 100644 tests/flip.test.tsx
delete mode 100644 tests/flipShift.test.tsx
delete mode 100644 tests/mask.test.jsx
delete mode 100644 tests/mobile.test.tsx
delete mode 100644 tests/motion.test.jsx
delete mode 100644 tests/perf.test.tsx
delete mode 100644 tests/point.test.jsx
delete mode 100644 tests/portal.test.jsx
delete mode 100644 tests/rect.test.tsx
delete mode 100644 tests/ref.test.tsx
delete mode 100644 tests/shadow.test.tsx
delete mode 100644 tests/util.tsx
diff --git a/.dumirc.ts b/.dumirc.ts
index efcef9c..6d575e3 100644
--- a/.dumirc.ts
+++ b/.dumirc.ts
@@ -3,13 +3,13 @@ import path from 'path';
export default defineConfig({
alias: {
- 'rc-trigger$': path.resolve('src'),
- 'rc-trigger/es': path.resolve('src'),
+ 'rc-listy$': path.resolve('src'),
+ 'rc-listy/es': path.resolve('src'),
},
mfsu: false,
favicons: ['https://avatars0.githubusercontent.com/u/9441414?s=200&v=4'],
themeConfig: {
- name: 'Trigger',
+ name: 'Listy',
logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
},
styles: [
diff --git a/.gitignore b/.gitignore
index 02e6333..d048bce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,7 @@ es
coverage
yarn.lock
package-lock.json
+pnpm-lock.yaml
bun.lockb
.vscode
diff --git a/HISTORY.md b/HISTORY.md
deleted file mode 100644
index c159b6b..0000000
--- a/HISTORY.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# History
-
----
-
-## 4.1.0 / 2020-05-08
-
-- upgrade rc-animate to `3.x`
-
-## 2.5.0 / 2018-06-05
-
-- support `alignPoint`
-
-## 2.1.0 / 2017-10-16
-
-- add action `contextMenu`
-
-## 2.0.0 / 2017-09-25
-
-- support React 16
-
-## 1.11.0 / 2017-06-07
-
-- add es
-
-## 1.9.0 / 2017-02-27
-
-- add getDocument prop
-
-## 1.8.2 / 2017-02-24
-
-- change default container to absolute to fix scrollbar change problem
-
-## 1.7.0 / 2016-07-18
-
-- use getContainerRenderMixin from 'rc-util'
-
-## 1.6.0 / 2016-05-26
-
-- support popup as function
-
-## 1.5.0 / 2016-05-26
-
-- add forcePopupAlign method
-
-## 1.4.0 / 2016-04-06
-
-- support onPopupAlign
-
-## 1.3.0 / 2016-03-25
-
-- support mask/maskTransitionName/zIndex
-
-## 1.2.0 / 2016-03-01
-
-- add showAction/hideAction
-
-## 1.1.0 / 2016-01-06
-
-- add root trigger node as parameter of getPopupContainer
diff --git a/README.md b/README.md
index 6f8662e..199d8cd 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# @rc-component/trigger
+# @rc-component/Listy
-React Trigger Component
+React Listy Component
[![NPM version][npm-image]][npm-url]
[![npm download][download-image]][download-url]
@@ -9,30 +9,30 @@ React Trigger Component
[![bundle size][bundlephobia-image]][bundlephobia-url]
[![dumi][dumi-image]][dumi-url]
-[npm-image]: http://img.shields.io/npm/v/@rc-component/trigger.svg?style=flat-square
-[npm-url]: http://npmjs.org/package/@rc-component/trigger
-[github-actions-image]: https://github.com/react-component/trigger/workflows/CI/badge.svg
-[github-actions-url]: https://github.com/react-component/trigger/actions
-[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
+[npm-image]: http://img.shields.io/npm/v/@rc-component/listy.svg?style=flat-square
+[npm-url]: http://npmjs.org/package/@rc-component/listy
+[github-actions-image]: https://github.com/react-component/listy/workflows/CI/badge.svg
+[github-actions-url]: https://github.com/react-component/listy/actions
+[codecov-image]: https://img.shields.io/codecov/c/github/react-component/listy/master.svg?style=flat-square
+[codecov-url]: https://codecov.io/gh/react-component/listy/branch/master
+[david-url]: https://david-dm.org/react-component/listy
+[david-image]: https://david-dm.org/react-component/listy/status.svg?style=flat-square
+[david-dev-url]: https://david-dm.org/react-component/listy?type=dev
+[david-dev-image]: https://david-dm.org/react-component/listy/dev-status.svg?style=flat-square
+[download-image]: https://img.shields.io/npm/dm/@rc-component/listy.svg?style=flat-square
+[download-url]: https://npmjs.org/package/@rc-component/listy
+[bundlephobia-url]: https://bundlephobia.com/result?p=@rc-component/listy
+[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/listy
[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)
+[](https://npmjs.org/package/@rc-component/listy)
## Usage
-Include the default [styling](https://github.com/react-component/trigger/blob/master/assets/index.less#L4:L11) and then:
+Include the default [styling](https://github.com/react-component/listy/blob/master/assets/index.less#L4:L11) and then:
```js
import React from 'react';
diff --git a/assets/index.less b/assets/index.less
index e13a125..e69de29 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -1,79 +0,0 @@
-@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;
- }
- }
-}
-
-@import './index/Mask';
-@import './index/Mobile';
diff --git a/assets/index/Mask.less b/assets/index/Mask.less
deleted file mode 100644
index a7b40bf..0000000
--- a/assets/index/Mask.less
+++ /dev/null
@@ -1,65 +0,0 @@
-.@{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;
- }
- }
-}
diff --git a/assets/index/Mobile.less b/assets/index/Mobile.less
deleted file mode 100644
index 81cdd3b..0000000
--- a/assets/index/Mobile.less
+++ /dev/null
@@ -1,55 +0,0 @@
-.@{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;
- }
- }
- }
- }
- }
-}
diff --git a/docs/demos/body-overflow.md b/docs/demos/body-overflow.md
deleted file mode 100644
index b8fe6fa..0000000
--- a/docs/demos/body-overflow.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Body Overflow
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/case.md b/docs/demos/case.md
deleted file mode 100644
index 7a7c3cf..0000000
--- a/docs/demos/case.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Case
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/click-nested.md b/docs/demos/click-nested.md
deleted file mode 100644
index d1fa6cc..0000000
--- a/docs/demos/click-nested.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Click Nested
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/clip.md b/docs/demos/clip.md
deleted file mode 100644
index 017d539..0000000
--- a/docs/demos/clip.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Clip
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/container.md b/docs/demos/container.md
deleted file mode 100644
index d607f16..0000000
--- a/docs/demos/container.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Container
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/inside.md b/docs/demos/inside.md
deleted file mode 100644
index 6a1ccb0..0000000
--- a/docs/demos/inside.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Inside
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/large-popup.md b/docs/demos/large-popup.md
deleted file mode 100644
index 24cf8b1..0000000
--- a/docs/demos/large-popup.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Large Popup
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/mobile.md b/docs/demos/mobile.md
deleted file mode 100644
index 298eb12..0000000
--- a/docs/demos/mobile.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Mobile
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/nested.md b/docs/demos/nested.md
deleted file mode 100644
index bfd0c6b..0000000
--- a/docs/demos/nested.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Nested
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/point.md b/docs/demos/point.md
deleted file mode 100644
index 1d31f1c..0000000
--- a/docs/demos/point.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Point
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/portal.md b/docs/demos/portal.md
deleted file mode 100644
index 5611bd1..0000000
--- a/docs/demos/portal.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Portal
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/shadow.md b/docs/demos/shadow.md
deleted file mode 100644
index 029c60c..0000000
--- a/docs/demos/shadow.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Shadow
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/simple.md b/docs/demos/simple.md
deleted file mode 100644
index 6ac4ca8..0000000
--- a/docs/demos/simple.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Simple
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/static-scroll.md b/docs/demos/static-scroll.md
deleted file mode 100644
index 14a1846..0000000
--- a/docs/demos/static-scroll.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Static Scroll
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/demos/visible-fallback.md b/docs/demos/visible-fallback.md
deleted file mode 100644
index 71d0d91..0000000
--- a/docs/demos/visible-fallback.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Visible Fallback
-nav:
- title: Demo
- path: /demo
----
-
-
diff --git a/docs/examples/body-overflow.tsx b/docs/examples/body-overflow.tsx
deleted file mode 100644
index 924de83..0000000
--- a/docs/examples/body-overflow.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-/* eslint no-console:0 */
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import { createPortal } from 'react-dom';
-import '../../assets/index.less';
-
-const PortalDemo = () => {
- return createPortal(
-
- PortalNode
-
,
- document.body,
- );
-};
-
-export default () => {
- const [open, setOpen] = React.useState(false);
- const [open1, setOpen1] = React.useState(false);
- const [open2, setOpen2] = React.useState(false);
- const [open3, setOpen3] = React.useState(false);
- return (
-
-
-
- {
- console.log('Visible Change:', next);
- setOpen(next);
- }}
- popupMotion={{
- motionName: 'rc-trigger-popup-zoom',
- }}
- popup={
-
-
-
-
-
- }
- // popupVisible
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupAlign={{
- points: ['tc', 'bc'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- htmlRegion: 'scroll',
- }}
- >
-
-
-
- {
- console.log('Visible Change:', next);
- setOpen1(next);
- }}
- popupMotion={{
- motionName: 'rc-trigger-popup-zoom',
- }}
- popup={
-
-
-
- }
- // popupVisible
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupAlign={{
- points: ['tc', 'bc'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- htmlRegion: 'scroll',
- }}
- >
-
- Target Click
-
-
-
- {
- console.log('Visible Change:', next);
- setOpen2(next);
- }}
- popupMotion={{
- motionName: 'rc-trigger-popup-zoom',
- }}
- popup={
-
- Target ContextMenu1
-
- }
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupAlign={{
- points: ['tc', 'bc'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- htmlRegion: 'scroll',
- }}
- >
-
- Target ContextMenu1
-
-
-
- {
- console.log('Visible Change:', next);
- setOpen3(next);
- }}
- popupMotion={{
- motionName: 'rc-trigger-popup-zoom',
- }}
- popup={
-
- Target ContextMenu2
-
- }
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupAlign={{
- points: ['tc', 'bc'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- htmlRegion: 'scroll',
- }}
- >
-
- Target ContextMenu2
-
-
-
- );
-};
diff --git a/docs/examples/case.less b/docs/examples/case.less
deleted file mode 100644
index a1012d6..0000000
--- a/docs/examples/case.less
+++ /dev/null
@@ -1,109 +0,0 @@
-// .rc-trigger-popup-placement-right {
-// border-width: 10px!important;
-// }
-
-// ======================= Popup =======================
-.case-motion {
- transform-origin: 50% 50%;
-
- animation-duration: 0.3s;
- animation-timing-function: cubic-bezier(0.18, 0.89, 0.32, 1.28);
- animation-fill-mode: both;
-
- &::after {
- content: 'Animating...';
- position: absolute;
- bottom: -3em;
- }
-
- &-appear,
- &-enter {
- animation-play-state: paused;
-
- &-active {
- animation-name: case-zoom-in;
- animation-play-state: running;
- }
- }
-
- &-leave {
- animation-play-state: paused;
-
- &-active {
- animation-name: case-zoom-out;
- animation-play-state: running;
- }
- }
-}
-
-@keyframes case-zoom-in {
- 0% {
- opacity: 0;
- transform: scale(0);
- }
- 100% {
- opacity: 1;
- transform: scale(1);
- }
-}
-
-@keyframes case-zoom-out {
- 0% {
- opacity: 1;
- transform: scale(1);
- }
- 100% {
- opacity: 0;
- transform: scale(1.2);
- }
-}
-
-// ======================= Mask =======================
-.mask-motion {
- animation-duration: 0.3s;
- animation-fill-mode: both;
- position: fixed;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.3);
-
- &-appear,
- &-enter {
- animation-play-state: paused;
- opacity: 0;
-
- &-active {
- animation-name: mask-zoom-in;
- animation-play-state: running;
- }
- }
-
- &-leave {
- animation-play-state: paused;
-
- &-active {
- animation-name: mask-zoom-out;
- animation-play-state: running;
- }
- }
-}
-
-@keyframes mask-zoom-in {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
-
-@keyframes mask-zoom-out {
- 0% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
-}
diff --git a/docs/examples/case.tsx b/docs/examples/case.tsx
deleted file mode 100644
index ca8c785..0000000
--- a/docs/examples/case.tsx
+++ /dev/null
@@ -1,245 +0,0 @@
-/* eslint no-console:0 */
-
-import React from 'react';
-import type { CSSMotionProps } from '@rc-component/motion';
-import type { ActionType, BuildInPlacements } from '@rc-component/trigger';
-import Trigger from '@rc-component/trigger';
-import './case.less';
-
-const builtinPlacements: BuildInPlacements = {
- left: {
- points: ['cr', 'cl'],
- },
- right: {
- points: ['cl', 'cr'],
- },
- top: {
- points: ['bc', 'tc'],
- },
- bottom: {
- points: ['tc', 'bc'],
- },
- topLeft: {
- points: ['bl', 'tl'],
- },
- topRight: {
- points: ['br', 'tr'],
- },
- bottomRight: {
- points: ['tr', 'br'],
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- },
-};
-
-const Motion: CSSMotionProps = {
- motionName: 'case-motion',
-};
-
-const MaskMotion: CSSMotionProps = {
- motionName: 'mask-motion',
-};
-
-function useControl(valuePropName: string, defaultValue: T): [T, any] {
- const [value, setValue] = React.useState(defaultValue);
-
- return [
- value,
- {
- value,
- checked: value,
- onChange({ target }) {
- setValue(target[valuePropName]);
- },
- },
- ];
-}
-
-const LabelItem: React.FC<{
- title: React.ReactNode;
- children: React.ReactElement;
- [prop: string]: any;
-}> = ({ title, children, ...rest }) => {
- const { type } = children;
-
- const style = {
- display: 'inline-flex',
- padding: '0 8px',
- alignItems: 'center',
- };
-
- const spacing = ;
-
- if (type === 'input' && children.props.type === 'checkbox') {
- return (
-
- );
- }
-
- return (
-
- );
-};
-
-const Demo = () => {
- const [hover, hoverProps] = useControl('checked', true);
- const [focus, focusProps] = useControl('checked', false);
- const [click, clickProps] = useControl('checked', false);
- const [contextMenu, contextMenuProps] = useControl('checked', false);
-
- const [placement, placementProps] = useControl('value', 'right');
- const [stretch, stretchProps] = useControl('value', '');
- const [motion, motionProps] = useControl('checked', true);
- const [destroyPopupOnHide, destroyPopupOnHideProps] = useControl(
- 'checked',
- false,
- );
- const [mask, maskProps] = useControl('checked', false);
- const [maskClosable, maskClosableProps] = useControl('checked', true);
- const [forceRender, forceRenderProps] = useControl('checked', false);
- const [offsetX, offsetXProps] = useControl('value', 0);
- const [offsetY, offsetYProps] = useControl('value', 0);
-
- const actions = {
- hover,
- focus,
- click,
- contextMenu,
- };
-
- const actionsKeys = Object.keys(actions).filter(
- (action) => actions[action],
- ) as ActionType[];
-
- return (
-
-
-
-
- );
-};
-
-export default Demo;
diff --git a/docs/examples/click-nested.tsx b/docs/examples/click-nested.tsx
deleted file mode 100644
index e76c9fe..0000000
--- a/docs/examples/click-nested.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-/* eslint no-console:0 */
-
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- left: {
- points: ['cr', 'cl'],
- },
- right: {
- points: ['cl', 'cr'],
- },
- top: {
- points: ['bc', 'tc'],
- },
- bottom: {
- points: ['tc', 'bc'],
- },
- topLeft: {
- points: ['bl', 'tl'],
- },
- topRight: {
- points: ['br', 'tr'],
- },
- bottomRight: {
- points: ['tr', 'br'],
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- },
-};
-
-const popupBorderStyle = {
- border: '1px solid red',
- padding: 10,
- background: 'rgba(255, 0, 0, 0.1)',
-};
-
-const NestPopup = ({ open, setOpen }) => {
- return (
- i am a click popup}
- popupVisible={open}
- onOpenChange={setOpen}
- >
-
- i am a click popup{' '}
-
-
-
- );
-};
-
-NestPopup.displayName = '🐞 NestPopup';
-
-const Test = () => {
- const [open1, setOpen1] = React.useState(false);
- const [open2, setOpen2] = React.useState(false);
-
- return (
-
-
-
- }
- fresh
- >
- Click Me
-
-
-
- );
-};
-
-export default Test;
diff --git a/docs/examples/clip.tsx b/docs/examples/clip.tsx
deleted file mode 100644
index 7ba1986..0000000
--- a/docs/examples/clip.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-/* eslint no-console:0 */
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- top: {
- points: ['bc', 'tc'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- offset: [0, 0],
- },
- bottom: {
- points: ['tc', 'bc'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- offset: [0, 0],
- },
-};
-
-const popupPlacement = 'top';
-
-export default () => {
- const [scale, setScale] = React.useState('1');
-
- return (
-
-
-
- setScale(e.target.value)}
- />
-
-
-
-
- Popup
-
- }
- getPopupContainer={(n) => n.parentNode as any}
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupPlacement={popupPlacement}
- builtinPlacements={builtinPlacements}
- stretch="minWidth"
- >
-
- Target
-
-
-
-
-
- {/* */}
-
- );
-};
diff --git a/docs/examples/container.tsx b/docs/examples/container.tsx
deleted file mode 100644
index b3f3892..0000000
--- a/docs/examples/container.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-/* eslint no-console:0 */
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- topLeft: {
- points: ['bl', 'tl'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- offset: [0, 0],
- targetOffset: [10, 0],
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- },
- top: {
- points: ['bc', 'tc'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- offset: [0, -10],
- },
- bottom: {
- points: ['tc', 'bc'],
- overflow: {
- shiftX: true,
- adjustY: true,
- },
- offset: [0, 10],
- htmlRegion: 'scroll' as const,
- },
- left: {
- points: ['cr', 'cl'],
- overflow: {
- adjustX: true,
- shiftY: true,
- },
- offset: [-10, 0],
- },
- right: {
- points: ['cl', 'cr'],
- overflow: {
- adjustX: true,
- shiftY: 24,
- },
- offset: [10, 0],
- },
-};
-
-const popupPlacement = 'top';
-
-export default () => {
- console.log('Demo Render!');
-
- const [visible, setVisible] = React.useState(false);
- const [scale, setScale] = React.useState('1');
- const [targetVisible, setTargetVisible] = React.useState(true);
-
- const rootRef = React.useRef(null);
- const popHolderRef = React.useRef(null);
- const scrollRef = React.useRef(null);
-
- React.useEffect(() => {
- scrollRef.current.scrollLeft = window.innerWidth;
- scrollRef.current.scrollTop = window.innerHeight / 2;
- }, []);
-
- return (
-
-
-
- setScale(e.target.value)}
- />
-
-
-
-
-
-
-
- Popup
-
- }
- popupMotion={{
- motionName: 'rc-trigger-popup-zoom',
- }}
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupVisible={visible}
- onOpenChange={(nextVisible) => {
- setVisible(nextVisible);
- }}
- // getPopupContainer={() => popHolderRef.current}
- popupPlacement={popupPlacement}
- builtinPlacements={builtinPlacements}
- stretch="minWidth"
- onPopupAlign={(domNode, align) => {
- console.log('onPopupAlign:', domNode, align);
- }}
- >
-
- Target
-
-
-
-
-
-
- {/* */}
-
- );
-};
diff --git a/docs/examples/inside.tsx b/docs/examples/inside.tsx
deleted file mode 100644
index 9efea42..0000000
--- a/docs/examples/inside.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-/* eslint no-console:0 */
-import React from 'react';
-import '../../assets/index.less';
-import Trigger, { type BuildInPlacements } from '../../src';
-
-const experimentalConfig = {
- _experimental: {
- dynamicInset: true,
- },
-};
-
-export const builtinPlacements: BuildInPlacements = {
- top: {
- points: ['bc', 'tc'],
- overflow: {
- shiftX: 0,
- adjustY: true,
- },
- offset: [0, 0],
- ...experimentalConfig,
- },
- topLeft: {
- points: ['bl', 'tl'],
- overflow: {
- adjustX: true,
- adjustY: false,
- shiftY: true,
- },
- offset: [0, -20],
- ...experimentalConfig,
- },
- topRight: {
- points: ['br', 'tr'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- offset: [0, 0],
- ...experimentalConfig,
- },
- left: {
- points: ['cr', 'cl'],
- overflow: {
- adjustX: true,
- shiftY: true,
- },
- offset: [0, 0],
- ...experimentalConfig,
- },
- leftTop: {
- points: ['tr', 'tl'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- offset: [0, 0],
- ...experimentalConfig,
- },
- leftBottom: {
- points: ['br', 'bl'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- offset: [0, 0],
- ...experimentalConfig,
- },
- right: {
- points: ['cl', 'cr'],
- overflow: {
- adjustX: true,
- shiftY: true,
- },
- offset: [0, 0],
- ...experimentalConfig,
- },
- bottom: {
- points: ['tc', 'bc'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- offset: [0, 0],
- ...experimentalConfig,
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- shiftY: true,
- },
- offset: [0, 20],
- ...experimentalConfig,
- },
-};
-
-const popupPlacement = 'bottomLeft';
-
-export default () => {
- const [popupHeight, setPopupHeight] = React.useState(60);
-
- const containerRef = React.useRef(null);
-
- React.useEffect(() => {
- containerRef.current.scrollLeft = document.defaultView.innerWidth;
- containerRef.current.scrollTop = document.defaultView.innerHeight;
- }, []);
-
- return (
- <>
-
-
-
-
-
-
- Popup
-
- }
- popupVisible
- getPopupContainer={() => containerRef.current}
- popupPlacement={popupPlacement}
- builtinPlacements={builtinPlacements}
- >
-
- Target
-
-
-
-
- >
- );
-};
diff --git a/docs/examples/large-popup.tsx b/docs/examples/large-popup.tsx
deleted file mode 100644
index 687db80..0000000
--- a/docs/examples/large-popup.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-/* eslint no-console:0 */
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- top: {
- points: ['bc', 'tc'],
- overflow: {
- shiftY: true,
- adjustY: true,
- },
- offset: [0, -10],
- },
- bottom: {
- points: ['tc', 'bc'],
- overflow: {
- shiftY: true,
- adjustY: true,
- },
- offset: [0, 10],
- htmlRegion: 'scroll' as const,
- },
-};
-
-export default () => {
- const containerRef = React.useRef(null);
-
- React.useEffect(() => {
- console.clear();
- containerRef.current.scrollTop = document.defaultView.innerHeight * 0.75;
- }, []);
-
- return (
-
-
-
-
-
- Popup 75vh
-
- }
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupVisible
- popupPlacement="top"
- builtinPlacements={builtinPlacements}
- >
-
- Target
-
-
-
-
-
-
- {/* */}
-
- );
-};
diff --git a/docs/examples/mobile.tsx b/docs/examples/mobile.tsx
deleted file mode 100644
index 37d50f3..0000000
--- a/docs/examples/mobile.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- left: {
- points: ['cr', 'cl'],
- },
- right: {
- points: ['cl', 'cr'],
- },
- top: {
- points: ['bc', 'tc'],
- },
- bottom: {
- points: ['tc', 'bc'],
- },
- topLeft: {
- points: ['bl', 'tl'],
- },
- topRight: {
- points: ['br', 'tr'],
- },
- bottomRight: {
- points: ['tr', 'br'],
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- },
-};
-
-const Test = () => {
- const [open1, setOpen1] = React.useState(false);
-
- return (
-
-
-
- Hello World
-
- }
- mobile={{
- mask: true,
- motion: { motionName: 'raise' },
- maskMotion: { motionName: 'fade' },
- }}
- >
-
Click Me
-
-
-
- );
-};
-
-export default Test;
diff --git a/docs/examples/nested.tsx b/docs/examples/nested.tsx
deleted file mode 100644
index 0040744..0000000
--- a/docs/examples/nested.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-/* eslint no-console:0 */
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Trigger from '@rc-component/trigger';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- left: {
- points: ['cr', 'cl'],
- },
- right: {
- points: ['cl', 'cr'],
- },
- top: {
- points: ['bc', 'tc'],
- },
- bottom: {
- points: ['tc', 'bc'],
- },
- topLeft: {
- points: ['bl', 'tl'],
- },
- topRight: {
- points: ['br', 'tr'],
- },
- bottomRight: {
- points: ['tr', 'br'],
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- },
-};
-
-const popupBorderStyle = {
- border: '1px solid red',
- padding: 10,
-};
-
-const OuterContent = ({ getContainer }) => {
- return ReactDOM.createPortal(
-
- I am outer content
-
-
,
- getContainer(),
- );
-};
-
-const Test = () => {
- const containerRef = React.useRef(null);
- const outerDivRef = React.useRef(null);
-
- const innerTrigger = (
-
-
-
containerRef.current}
- popup={I am inner Trigger Popup
}
- >
- clickToShowInnerTrigger
-
-
- );
- return (
-
-
-
- i am a click popup
- outerDivRef.current} />
-
- }
- >
-
- i am a hover popup }
- >
- trigger
-
-
-
-
-
-
- trigger
-
-
-
-
-
- );
-};
-
-export default Test;
diff --git a/docs/examples/point.less b/docs/examples/point.less
deleted file mode 100644
index 3ea05e5..0000000
--- a/docs/examples/point.less
+++ /dev/null
@@ -1,3 +0,0 @@
-.point-popup {
- pointer-events: none;
-}
diff --git a/docs/examples/point.tsx b/docs/examples/point.tsx
deleted file mode 100644
index 839092d..0000000
--- a/docs/examples/point.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-/* eslint no-console:0 */
-
-import React from 'react';
-import Trigger, { ActionType } from '@rc-component/trigger';
-import '../../assets/index.less';
-import './point.less';
-
-const builtinPlacements = {
- topLeft: {
- points: ['tl', 'tl'],
- },
-};
-
-const innerTrigger = (
-
- This is popup
-
-);
-
-class Test extends React.Component {
- state = {
- action: 'click' as ActionType,
- mouseEnterDelay: 0,
- };
-
- onActionChange = ({ target: { value } }) => {
- this.setState({ action: value });
- };
-
- onDelayChange = ({ target: { value } }) => {
- this.setState({ mouseEnterDelay: Number(value) || 0 });
- };
-
- render() {
- const { action, mouseEnterDelay } = this.state;
-
- return (
-
-
{' '}
- {action === 'hover' && (
-
- )}
-
-
-
- Interactive region
-
-
-
-
- );
- }
-}
-
-export default Test;
diff --git a/docs/examples/portal.tsx b/docs/examples/portal.tsx
deleted file mode 100644
index 3c2f650..0000000
--- a/docs/examples/portal.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-/* eslint no-console:0 */
-
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import { createPortal } from 'react-dom';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- left: {
- points: ['cr', 'cl'],
- },
- right: {
- points: ['cl', 'cr'],
- },
- top: {
- points: ['bc', 'tc'],
- },
- bottom: {
- points: ['tc', 'bc'],
- },
- topLeft: {
- points: ['bl', 'tl'],
- },
- topRight: {
- points: ['br', 'tr'],
- },
- bottomRight: {
- points: ['tr', 'br'],
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- },
-};
-
-const popupBorderStyle = {
- border: '1px solid red',
- padding: 10,
- background: 'rgba(255, 0, 0, 0.1)',
-};
-
-const PortalPopup = () =>
- createPortal(
- {
- console.log('Portal Down', e);
- e.stopPropagation();
- e.preventDefault();
- }}
- >
- i am a portal element
-
,
- document.body,
- );
-
-const Test = () => {
- const buttonRef = React.useRef(null);
- React.useEffect(() => {
- const button = buttonRef.current;
- if (button) {
- button.addEventListener('mousedown', (e) => {
- console.log('button natives down');
- e.stopPropagation();
- e.preventDefault();
- });
- }
- }, []);
-
- return (
-
-
- i am a click popup
-
-
- }
- onOpenChange={(visible) => {
- console.log('visible change:', visible);
- }}
- >
-
-
-
-
-
-
- );
-};
-
-export default Test;
diff --git a/docs/examples/shadow.tsx b/docs/examples/shadow.tsx
deleted file mode 100644
index 1d4e969..0000000
--- a/docs/examples/shadow.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-/* eslint no-console:0 */
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-import '../../assets/index.less';
-
-const Demo = () => {
- return (
-
-
- Popup
-
- }
- popupStyle={{ boxShadow: '0 0 5px red', position: 'absolute' }}
- getPopupContainer={(item) => item.parentElement!}
- popupAlign={{
- points: ['bc', 'tc'],
- overflow: {
- shiftX: 50,
- adjustY: true,
- },
- offset: [0, -10],
- }}
- stretch="minWidth"
- autoDestroy
- >
-
- Target
-
-
-
- );
-};
-
-export default () => {
- React.useEffect(() => {
- const wrapperHost = document.createElement('div');
- const wrapperShadowRoot = wrapperHost.attachShadow({
- mode: 'open',
- delegatesFocus: false,
- });
- document.body.appendChild(wrapperHost);
-
- const host = document.createElement('div');
- wrapperShadowRoot.appendChild(host);
- host.style.background = 'rgba(255,0,0,0.1)';
- const shadowRoot = host.attachShadow({
- mode: 'open',
- delegatesFocus: false,
- });
-
- const container = document.createElement('div');
- shadowRoot.appendChild(container);
-
- createRoot(container).render();
- }, []);
-
- return null;
-};
diff --git a/docs/examples/simple.tsx b/docs/examples/simple.tsx
deleted file mode 100644
index fbfefea..0000000
--- a/docs/examples/simple.tsx
+++ /dev/null
@@ -1,385 +0,0 @@
-/* eslint no-console:0 */
-
-import Trigger, { type ActionType } from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-
-const builtinPlacements = {
- left: {
- points: ['cr', 'cl'],
- offset: [-10, 0],
- },
- right: {
- points: ['cl', 'cr'],
- offset: [10, 0],
- },
- top: {
- points: ['bc', 'tc'],
- offset: [0, -10],
- },
- bottom: {
- points: ['tc', 'bc'],
- offset: [0, 10],
- },
- topLeft: {
- points: ['bl', 'tl'],
- offset: [0, -10],
- },
- topRight: {
- points: ['br', 'tr'],
- offset: [0, -10],
- },
- bottomRight: {
- points: ['tr', 'br'],
- offset: [0, 10],
- },
- bottomLeft: {
- points: ['tl', 'bl'],
- offset: [0, 10],
- },
-};
-
-function getPopupContainer(trigger) {
- return trigger.parentNode;
-}
-
-const InnerTarget = React.forwardRef(
- (props: any, ref: React.Ref) => (
-
-
This is a example of trigger usage.
-
You can adjust the value above
-
which will also change the behavior of popup.
-
- ),
-);
-
-interface TestState {
- mask: boolean;
- maskClosable: boolean;
- placement: string;
- trigger: {
- click?: boolean;
- focus?: boolean;
- hover?: boolean;
- contextMenu?: boolean;
- };
- offsetX: number;
- offsetY: number;
- stretch: string;
- transitionName: string;
- destroyed?: boolean;
- destroyPopupOnHide?: boolean;
- autoDestroy?: boolean;
- mobile?: boolean;
-}
-
-class Test extends React.Component {
- state: TestState = {
- mask: false,
- maskClosable: true,
- placement: 'bottom',
- trigger: {
- click: true,
- },
- offsetX: undefined,
- offsetY: undefined,
- stretch: 'minWidth',
- transitionName: 'rc-trigger-popup-zoom',
- };
-
- onPlacementChange = (e) => {
- this.setState({
- placement: e.target.value,
- });
- };
-
- onStretch = (e) => {
- this.setState({
- stretch: e.target.value,
- });
- };
-
- onTransitionChange = (e) => {
- this.setState({
- transitionName: e.target.checked ? e.target.value : '',
- });
- };
-
- onTriggerChange = ({ target: { checked, value } }) => {
- this.setState(({ trigger }) => {
- const clone = { ...trigger };
-
- if (checked) {
- clone[value] = 1;
- } else {
- delete clone[value];
- }
-
- return {
- trigger: clone,
- };
- });
- };
-
- onOffsetXChange = (e) => {
- const targetValue = e.target.value;
- this.setState({
- offsetX: targetValue || undefined,
- });
- };
-
- onOffsetYChange = (e) => {
- const targetValue = e.target.value;
- this.setState({
- offsetY: targetValue || undefined,
- });
- };
-
- onVisibleChange = (visible) => {
- console.log('tooltip', visible);
- };
-
- onMask = (e) => {
- this.setState({
- mask: e.target.checked,
- });
- };
-
- onMaskClosable = (e) => {
- this.setState({
- maskClosable: e.target.checked,
- });
- };
-
- getPopupAlign = () => {
- const { offsetX, offsetY } = this.state;
- return {
- offset: [offsetX, offsetY],
- overflow: {
- adjustX: 1,
- adjustY: 1,
- },
- };
- };
-
- destroy = () => {
- this.setState({
- destroyed: true,
- });
- };
-
- destroyPopupOnHide = (e) => {
- this.setState({
- destroyPopupOnHide: e.target.checked,
- });
- };
-
- autoDestroy = (e) => {
- this.setState({
- autoDestroy: e.target.checked,
- });
- };
-
- render() {
- const { state } = this;
- const { trigger } = state;
- if (state.destroyed) {
- return null;
- }
-
- const actions = Object.keys(state.trigger) as ActionType[];
-
- return (
-
-
- );
- }
-}
-
-export default Test;
diff --git a/docs/examples/static-scroll.tsx b/docs/examples/static-scroll.tsx
deleted file mode 100644
index 79979f3..0000000
--- a/docs/examples/static-scroll.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-/* eslint no-console:0 */
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-import { builtinPlacements } from './inside';
-
-export default () => {
- return (
-
-
-
- Popup
-
- }
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupVisible
- builtinPlacements={builtinPlacements}
- popupPlacement="top"
- stretch="minWidth"
- getPopupContainer={(e) => e.parentElement!}
- >
-
- Target
-
-
- {new Array(20).fill(null).map((_, index) => (
-
- Placeholder Line {index}
-
- ))}
-
-
- );
-};
diff --git a/docs/examples/visible-fallback.tsx b/docs/examples/visible-fallback.tsx
deleted file mode 100644
index 8c59d88..0000000
--- a/docs/examples/visible-fallback.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-/* eslint no-console:0 */
-import type { AlignType, TriggerRef } from '@rc-component/trigger';
-import Trigger from '@rc-component/trigger';
-import React from 'react';
-import '../../assets/index.less';
-
-const builtinPlacements: Record = {
- top: {
- points: ['bc', 'tc'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- offset: [0, 0],
- htmlRegion: 'visibleFirst',
- },
- bottom: {
- points: ['tc', 'bc'],
- overflow: {
- adjustX: true,
- adjustY: true,
- },
- offset: [0, 0],
- htmlRegion: 'visibleFirst',
- },
-};
-
-export default () => {
- const [enoughTop, setEnoughTop] = React.useState(true);
-
- const triggerRef = React.useRef(null);
-
- React.useEffect(() => {
- triggerRef.current?.forceAlign();
- }, [enoughTop]);
-
- return (
-
- `visibleFirst` should not show in hidden region if still scrollable
-
-
-
-
-
- Should Always place bottom
-
- }
- getPopupContainer={(n) => n.parentNode as any}
- popupStyle={{ boxShadow: '0 0 5px red' }}
- popupPlacement={enoughTop ? 'bottom' : 'top'}
- builtinPlacements={builtinPlacements}
- stretch="minWidth"
- >
-
- Target
-
-
-
-
- );
-};
diff --git a/docs/index.md b/docs/index.md
index 6e9e0db..d518949 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,7 +1,7 @@
---
hero:
- title: rc-trigger
- description: React Trigger Component
+ title: rc-listy
+ description: React Listy Component
---
diff --git a/now.json b/now.json
deleted file mode 100644
index 716cff0..0000000
--- a/now.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "version": 2,
- "name": "rc-trigger",
- "builds": [
- {
- "src": "package.json",
- "use": "@now/static-build",
- "config": { "distDir": ".doc" }
- }
- ]
-}
diff --git a/package.json b/package.json
index d977ae5..598368e 100644
--- a/package.json
+++ b/package.json
@@ -1,24 +1,25 @@
{
- "name": "@rc-component/trigger",
- "version": "3.5.2",
- "description": "base abstract trigger component for react",
+ "name": "@rc-component/listy",
+ "version": "1.0.0",
+ "description": "React Listy Component",
"engines": {
"node": ">=8.x"
},
"keywords": [
"react",
"react-component",
- "react-trigger",
- "trigger"
+ "list",
+ "high performance",
+ "headless ui"
],
"homepage": "https://github.com/react-component/trigger",
"author": "",
"repository": {
"type": "git",
- "url": "https://github.com/react-component/trigger.git"
+ "url": "https://github.com/react-component/listy.git"
},
"bugs": {
- "url": "https://github.com/react-component/trigger/issues"
+ "url": "https://github.com/react-component/listy/issues"
},
"files": [
"es",
@@ -45,6 +46,7 @@
"@rc-component/portal": "^2.0.0",
"@rc-component/resize-observer": "^1.0.0",
"@rc-component/util": "^1.2.1",
+ "rc-virtual-list": "^3.0.0",
"classnames": "^2.3.2"
},
"devDependencies": {
diff --git a/src/Popup/Arrow.tsx b/src/Popup/Arrow.tsx
deleted file mode 100644
index d6b8229..0000000
--- a/src/Popup/Arrow.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import classNames from 'classnames';
-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) {
- const { prefixCls, align, arrow, arrowPos } = props;
-
- const { className, content } = arrow || {};
- const { x = 0, y = 0 } = arrowPos;
-
- const arrowRef = React.useRef(null);
-
- // Skip if no align
- if (!align || !align.points) {
- return null;
- }
-
- const alignStyle: React.CSSProperties = {
- 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 (
-
- {content}
-
- );
-}
diff --git a/src/Popup/Mask.tsx b/src/Popup/Mask.tsx
deleted file mode 100644
index 4c376da..0000000
--- a/src/Popup/Mask.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import classNames from 'classnames';
-import type { CSSMotionProps } from '@rc-component/motion';
-import CSSMotion from '@rc-component/motion';
-import * as React from 'react';
-
-export interface MaskProps {
- prefixCls: string;
- open?: boolean;
- zIndex?: number;
- mask?: boolean;
-
- // Motion
- motion?: CSSMotionProps;
-
- mobile?: boolean;
-}
-
-export default function Mask(props: MaskProps) {
- const {
- prefixCls,
- open,
- zIndex,
-
- mask,
- motion,
-
- mobile,
- } = props;
-
- if (!mask) {
- return null;
- }
-
- return (
-
- {({ className }) => (
-
- )}
-
- );
-}
diff --git a/src/Popup/PopupContent.tsx b/src/Popup/PopupContent.tsx
deleted file mode 100644
index 12e9723..0000000
--- a/src/Popup/PopupContent.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as React from 'react';
-
-export interface PopupContentProps {
- children?: React.ReactNode;
- cache?: boolean;
-}
-
-const PopupContent = React.memo(
- ({ children }: PopupContentProps) => children as React.ReactElement,
- (_, next) => next.cache,
-);
-
-if (process.env.NODE_ENV !== 'production') {
- PopupContent.displayName = 'PopupContent';
-}
-
-export default PopupContent;
diff --git a/src/Popup/index.tsx b/src/Popup/index.tsx
deleted file mode 100644
index 37e43ce..0000000
--- a/src/Popup/index.tsx
+++ /dev/null
@@ -1,325 +0,0 @@
-import classNames from 'classnames';
-import type { CSSMotionProps } from '@rc-component/motion';
-import CSSMotion from '@rc-component/motion';
-import ResizeObserver from '@rc-component/resize-observer';
-import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
-import { composeRef } from '@rc-component/util/lib/ref';
-import * as React from 'react';
-import type { TriggerProps } from '../';
-import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
-import Arrow from './Arrow';
-import Mask from './Mask';
-import PopupContent from './PopupContent';
-
-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 {
- prefixCls: string;
- className?: string;
- style?: React.CSSProperties;
- popup?: TriggerProps['popup'];
- target: HTMLElement;
- onMouseEnter?: React.MouseEventHandler;
- onMouseLeave?: React.MouseEventHandler;
- onPointerEnter?: React.MouseEventHandler;
- onPointerDownCapture?: React.MouseEventHandler;
- zIndex?: number;
-
- mask?: boolean;
- onVisibleChanged: (visible: boolean) => void;
-
- // Arrow
- align?: AlignType;
- arrow?: ArrowTypeOuter;
- arrowPos: ArrowPos;
-
- // Open
- open: boolean;
- /** Tell Portal that should keep in screen. e.g. should wait all motion end */
- keepDom: boolean;
- fresh?: boolean;
-
- // Click
- onClick?: React.MouseEventHandler;
-
- // Motion
- motion?: CSSMotionProps;
- maskMotion?: CSSMotionProps;
-
- // Portal
- forceRender?: boolean;
- getPopupContainer?: TriggerProps['getPopupContainer'];
- autoDestroy?: boolean;
- portal: React.ComponentType;
-
- // Align
- ready: boolean;
- offsetX: number;
- offsetY: number;
- offsetR: number;
- offsetB: number;
- onAlign: VoidFunction;
- onPrepare: () => Promise;
-
- // stretch
- stretch?: string;
- targetWidth?: number;
- targetHeight?: number;
-
- // Mobile
- mobile?: MobileConfig;
-}
-
-const Popup = React.forwardRef((props, ref) => {
- const {
- 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,
-
- zIndex,
-
- onMouseEnter,
- onMouseLeave,
- onPointerEnter,
- onPointerDownCapture,
-
- ready,
- offsetX,
- offsetY,
- offsetR,
- offsetB,
- onAlign,
- onPrepare,
-
- stretch,
- targetWidth,
- targetHeight,
- } = props;
-
- const childNode = 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<
- [
- mask: boolean,
- maskMotion: CSSMotionProps | undefined,
- popupMotion: CSSMotionProps | undefined,
- ]
- >(() => {
- 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]);
-
- // ========================= Render =========================
- if (!show) {
- return null;
- }
-
- // >>>>> Offset
- const AUTO = 'auto' as const;
-
- const offsetStyle: React.CSSProperties = isMobile
- ? {}
- : {
- left: '-1000vw',
- top: '-1000vh',
- right: AUTO,
- bottom: AUTO,
- };
-
- // Set align style
- if (!isMobile && (ready || !open)) {
- const { points } = align;
- const dynamicInset =
- align.dynamicInset || (align as any)._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;
- }
- }
-
- // >>>>> Misc
- const miscStyle: React.CSSProperties = {};
- 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 (
- getPopupContainer(target))}
- autoDestroy={autoDestroy}
- >
-
-
- {(resizeObserverRef) => {
- return (
- {
- motion?.onVisibleChanged?.(nextVisible);
- onVisibleChanged(nextVisible);
- }}
- >
- {(
- { className: motionClassName, style: motionStyle },
- motionRef,
- ) => {
- const cls = classNames(prefixCls, motionClassName, className, {
- [`${prefixCls}-mobile`]: isMobile,
- });
-
- return (
-
- {arrow && (
-
- )}
-
- {childNode}
-
-
- );
- }}
-
- );
- }}
-
-
- );
-});
-
-if (process.env.NODE_ENV !== 'production') {
- Popup.displayName = 'Popup';
-}
-
-export default Popup;
diff --git a/src/context.ts b/src/context.ts
index 429b350..e69de29 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -1,9 +0,0 @@
-import * as React from 'react';
-
-export interface TriggerContextProps {
- registerSubPopup: (id: string, node: HTMLElement) => void;
-}
-
-const TriggerContext = React.createContext(null);
-
-export default TriggerContext;
diff --git a/src/hooks/useAction.ts b/src/hooks/useAction.ts
deleted file mode 100644
index 87c6abd..0000000
--- a/src/hooks/useAction.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import * as React from 'react';
-import type { ActionType } from '../interface';
-
-type InternalActionType = ActionType | 'touch';
-
-type ActionTypes = InternalActionType | InternalActionType[];
-
-function toArray(val?: T | T[]) {
- return val ? (Array.isArray(val) ? val : [val]) : [];
-}
-
-export default function useAction(
- action: ActionTypes,
- showAction?: ActionTypes,
- hideAction?: ActionTypes,
-): [showAction: Set, hideAction: Set] {
- 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]);
-}
diff --git a/src/hooks/useAlign.ts b/src/hooks/useAlign.ts
deleted file mode 100644
index 1bb3749..0000000
--- a/src/hooks/useAlign.ts
+++ /dev/null
@@ -1,762 +0,0 @@
-import { isDOM } from '@rc-component/util/lib/Dom/findDOMNode';
-import isVisible from '@rc-component/util/lib/Dom/isVisible';
-import useEvent from '@rc-component/util/lib/hooks/useEvent';
-import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
-import * as React from 'react';
-import type { TriggerProps } from '..';
-import type {
- AlignPointLeftRight,
- AlignPointTopBottom,
- AlignType,
- OffsetType,
-} from '../interface';
-import { collectScroller, getVisibleArea, getWin, toNum } from '../util';
-
-type Rect = Record<'x' | 'y' | 'width' | 'height', number>;
-
-type Points = [topBottom: AlignPointTopBottom, leftRight: AlignPointLeftRight];
-
-function getUnitOffset(size: number, offset: OffsetType = 0) {
- const offsetStr = `${offset}`;
- const cells = offsetStr.match(/^(.*)\%$/);
- if (cells) {
- return size * (parseFloat(cells[1]) / 100);
- }
- return parseFloat(offsetStr);
-}
-
-function getNumberOffset(
- rect: { width: number; height: number },
- offset?: OffsetType[],
-) {
- const [offsetX, offsetY] = offset || [];
-
- return [
- getUnitOffset(rect.width, offsetX),
- getUnitOffset(rect.height, offsetY),
- ];
-}
-
-function splitPoints(points: string = ''): Points {
- return [points[0] as any, points[1] as any];
-}
-
-function getAlignPoint(rect: Rect, points: Points) {
- const topBottom = points[0];
- const leftRight = points[1];
-
- let x: number;
- let y: number;
-
- // 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: Points, index: number): string {
- const reverseMap = {
- t: 'b',
- b: 't',
- l: 'r',
- r: 'l',
- };
-
- return points
- .map((point, i) => {
- if (i === index) {
- return reverseMap[point] || 'c';
- }
- return point;
- })
- .join('');
-}
-
-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,
-] {
- const [offsetInfo, setOffsetInfo] = React.useState<{
- /** Align finished */
- ready: boolean;
- /** Offset Left of current Left */
- offsetX: number;
- /** Offset Top of current Top */
- offsetY: number;
- /** Offset Right of current Left */
- offsetR: number;
- /** Offset Bottom of current Top */
- offsetB: number;
- /** Arrow X offset related with popup */
- arrowX: number;
- /** Arrow Y offset related with popup */
- arrowY: number;
- /** Scale X of popup */
- scaleX: number;
- /** Scale Y of popup */
- scaleY: number;
- /** Calculated align info */
- align: AlignType;
- }>({
- 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<{
- tb?: boolean;
- bt?: boolean;
- lr?: boolean;
- rl?: boolean;
- }>({});
-
- 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: AlignType = {
- ...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: Rect;
- 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' as const;
- const VISIBLE_FIRST = 'visibleFirst' as const;
- 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,
- };
-
- // 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: number,
- offsetY: number,
- 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: boolean | number) => {
- if (typeof val === 'boolean') {
- return val;
- }
- return val >= 0;
- };
-
- // Prepare position
- let nextPopupY: number;
- let nextPopupBottom: number;
- let nextPopupX: number;
- let nextPopupRight: number;
-
- 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: number = 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;
-
- nextAlignInfo.points = [
- reversePoints(popupPoints, 0),
- reversePoints(targetPoints, 0),
- ];
- } else {
- prevFlipRef.current.bt = false;
- }
- }
-
- // Top to Bottom
- if (
- needAdjustY &&
- popupPoints[0] === 'b' &&
- (nextPopupY < adjustCheckVisibleArea.top || prevFlipRef.current.tb)
- ) {
- let tmpNextOffsetY: number = 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;
-
- nextAlignInfo.points = [
- reversePoints(popupPoints, 0),
- reversePoints(targetPoints, 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: number = 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;
-
- nextAlignInfo.points = [
- reversePoints(popupPoints, 1),
- reversePoints(targetPoints, 1),
- ];
- } else {
- prevFlipRef.current.rl = false;
- }
- }
-
- // Left to Right
- if (
- needAdjustX &&
- popupPoints[1] === 'r' &&
- (nextPopupX < adjustCheckVisibleArea.left || prevFlipRef.current.lr)
- ) {
- let tmpNextOffsetX: number = 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;
-
- nextAlignInfo.points = [
- reversePoints(popupPoints, 1),
- reversePoints(targetPoints, 1),
- ];
- } else {
- prevFlipRef.current.lr = false;
- }
- }
-
- // ============================ 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.round(nextOffsetX);
- offsetX4Right = Math.round(offsetX4Right);
- }
-
- if (scaleY === 1) {
- nextOffsetY = Math.round(nextOffsetY);
- offsetY4Bottom = Math.round(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,
- ];
-}
diff --git a/src/hooks/useWatch.ts b/src/hooks/useWatch.ts
deleted file mode 100644
index d5d3fd6..0000000
--- a/src/hooks/useWatch.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
-import { collectScroller, getWin } from '../util';
-
-export default function useWatch(
- open: boolean,
- target: HTMLElement,
- popup: HTMLElement,
- onAlign: VoidFunction,
- onScroll: VoidFunction,
-) {
- 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]);
-}
diff --git a/src/hooks/useWinClick.ts b/src/hooks/useWinClick.ts
deleted file mode 100644
index 7c208d4..0000000
--- a/src/hooks/useWinClick.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { getShadowRoot } from '@rc-component/util/lib/Dom/shadow';
-import { warning } from '@rc-component/util/lib/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: boolean,
- clickToHide: boolean,
- targetEle: HTMLElement,
- popupEle: HTMLElement,
- mask: boolean,
- maskClosable: boolean,
- inPopupOrChild: (target: EventTarget) => boolean,
- triggerOpen: (open: boolean) => void,
-) {
- 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: MouseEvent) => {
- 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;
-}
diff --git a/src/index.tsx b/src/index.tsx
index dafeee7..e69de29 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,790 +0,0 @@
-import Portal from '@rc-component/portal';
-import classNames from 'classnames';
-import type { CSSMotionProps } from '@rc-component/motion';
-import ResizeObserver from '@rc-component/resize-observer';
-import { isDOM } from '@rc-component/util/lib/Dom/findDOMNode';
-import { getShadowRoot } from '@rc-component/util/lib/Dom/shadow';
-import useEvent from '@rc-component/util/lib/hooks/useEvent';
-import useId from '@rc-component/util/lib/hooks/useId';
-import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
-import * as React from 'react';
-import Popup, { type MobileConfig } from './Popup';
-import type { TriggerContextProps } from './context';
-import TriggerContext from './context';
-import useAction from './hooks/useAction';
-import useAlign from './hooks/useAlign';
-import useWatch from './hooks/useWatch';
-import useWinClick from './hooks/useWinClick';
-import type {
- ActionType,
- AlignType,
- ArrowPos,
- ArrowTypeOuter,
- BuildInPlacements,
-} from './interface';
-import { getAlignPopupClassName } from './util';
-
-export type {
- ActionType,
- AlignType,
- ArrowTypeOuter as ArrowType,
- BuildInPlacements,
-};
-
-export interface TriggerRef {
- nativeElement: HTMLElement;
- popupElement: HTMLDivElement;
- forceAlign: VoidFunction;
-}
-
-// 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 interface TriggerProps {
- children: React.ReactElement;
- action?: ActionType | ActionType[];
- showAction?: ActionType[];
- hideAction?: ActionType[];
-
- prefixCls?: string;
-
- zIndex?: number;
-
- onPopupAlign?: (element: HTMLElement, align: AlignType) => void;
-
- stretch?: string;
-
- // ==================== Open =====================
- 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;
-
- // =================== Portal ====================
- getPopupContainer?: (node: HTMLElement) => HTMLElement;
- forceRender?: boolean;
- autoDestroy?: boolean;
-
- // ==================== Mask =====================
- mask?: boolean;
- maskClosable?: boolean;
-
- // =================== Motion ====================
- /** 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;
-
- // ==================== Delay ====================
- mouseEnterDelay?: number;
- mouseLeaveDelay?: number;
-
- focusDelay?: number;
- blurDelay?: number;
-
- // ==================== Popup ====================
- popup: React.ReactNode | (() => React.ReactNode);
- popupPlacement?: string;
- builtinPlacements?: BuildInPlacements;
- popupAlign?: AlignType;
- popupClassName?: string;
- popupStyle?: React.CSSProperties;
- getPopupClassNameFromAlign?: (align: AlignType) => string;
- onPopupClick?: React.MouseEventHandler;
-
- alignPoint?: boolean; // Maybe we can support user pass position in the future
-
- /**
- * 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;
-
- // ==================== Arrow ====================
- arrow?: boolean | ArrowTypeOuter;
-
- // // ========================== Mobile ==========================
- /**
- * @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 function generateTrigger(
- PortalComponent: React.ComponentType = Portal,
-) {
- const Trigger = 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,
- popupStyle,
-
- popupPlacement,
- builtinPlacements = {},
- popupAlign,
- zIndex,
- stretch,
- getPopupClassNameFromAlign,
- fresh,
-
- alignPoint,
-
- onPopupClick,
- onPopupAlign,
-
- // Arrow
- arrow,
-
- // Motion
- popupMotion,
- maskMotion,
-
- // Private
- mobile,
-
- ...restProps
- } = props;
-
- const mergedAutoDestroy = autoDestroy || false;
-
- // =========================== 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]);
-
- // =========================== 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: HTMLDivElement) => {
- 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: HTMLElement) => {
- if (isDOM(node) && targetEle !== node) {
- setTargetEle(node);
- externalForwardRef.current = node;
- }
- });
-
- // ========================== Children ==========================
- const child = React.Children.only(children);
- const originChildProps = child?.props || {};
- const cloneProps: Pick<
- React.HTMLAttributes,
- | 'onClick'
- | 'onTouchStart'
- | 'onMouseEnter'
- | 'onMouseLeave'
- | 'onMouseMove'
- | 'onPointerEnter'
- | 'onPointerLeave'
- | 'onFocus'
- | 'onBlur'
- | 'onContextMenu'
- > = {};
-
- const inPopupOrChild = useEvent((ele: EventTarget) => {
- const childDOM = targetEle;
-
- return (
- childDOM?.contains(ele as HTMLElement) ||
- getShadowRoot(childDOM)?.host === ele ||
- ele === childDOM ||
- popupEle?.contains(ele as HTMLElement) ||
- getShadowRoot(popupEle)?.host === ele ||
- ele === popupEle ||
- Object.values(subPopupElements.current).some(
- (subPopupEle) =>
- subPopupEle?.contains(ele as HTMLElement) || ele === subPopupEle,
- )
- );
- });
-
- // ============================ Open ============================
- const [internalOpen, setInternalOpen] = React.useState(
- defaultPopupVisible || false,
- );
-
- // Render still use props as first priority
- const mergedOpen = popupVisible ?? internalOpen;
-
- // We use effect sync here in case `popupVisible` back to `undefined`
- const setMergedOpen = useEvent((nextOpen: boolean) => {
- if (popupVisible === undefined) {
- setInternalOpen(nextOpen);
- }
- });
-
- useLayoutEffect(() => {
- setInternalOpen(popupVisible || false);
- }, [popupVisible]);
-
- const openRef = React.useRef(mergedOpen);
- openRef.current = mergedOpen;
-
- const lastTriggerRef = React.useRef([]);
- lastTriggerRef.current = [];
-
- const internalTriggerOpen = useEvent((nextOpen: boolean) => {
- setMergedOpen(nextOpen);
-
- // Enter or Pointer will both trigger open state change
- // We only need take one to avoid duplicated change event trigger
- // Use `lastTriggerRef` to record last open type
- if (
- (lastTriggerRef.current[lastTriggerRef.current.length - 1] ??
- mergedOpen) !== nextOpen
- ) {
- lastTriggerRef.current.push(nextOpen);
- onOpenChange?.(nextOpen);
- onPopupVisibleChange?.(nextOpen);
- }
- });
-
- // Trigger for delay
- const delayRef = React.useRef>(null);
-
- const clearDelay = () => {
- clearTimeout(delayRef.current);
- };
-
- const triggerOpen = (nextOpen: boolean, delay = 0) => {
- clearDelay();
-
- if (delay === 0) {
- internalTriggerOpen(nextOpen);
- } else {
- delayRef.current = setTimeout(() => {
- internalTriggerOpen(nextOpen);
- }, delay * 1000);
- }
- };
-
- React.useEffect(() => clearDelay, []);
-
- // ========================== 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<
- [x: number, y: number] | null
- >(null);
-
- const setMousePosByEvent = (
- event: Pick,
- ) => {
- 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 classNames(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: boolean) => {
- 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: string,
- nextOpen: boolean,
- delay?: number,
- callback?: (event: Event) => void,
- ignoreCheck?: () => boolean,
- ) {
- cloneProps[eventName] = (event: any, ...args: any[]) => {
- 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: any[]) => {
- 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: React.MouseEvent,
- ...args: any[]
- ) => {
- 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: React.MouseEventHandler;
- let onPopupMouseLeave: VoidFunction;
-
- const ignoreMouseTrigger = () => {
- return touchedRef.current;
- };
-
- if (hoverToShow) {
- const onMouseEnterCallback = (event: React.MouseEvent) => {
- 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 as HTMLElement)
- ) {
- triggerOpen(true, mouseEnterDelay);
- }
- };
-
- // Align Point
- if (alignPoint) {
- cloneProps.onMouseMove = (event: React.MouseEvent) => {
- 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: React.MouseEvent, ...args: any[]) => {
- 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: Record = {};
- const passedEventList = [
- 'onContextMenu',
- 'onClick',
- 'onMouseDown',
- 'onTouchStart',
- 'onMouseEnter',
- 'onMouseLeave',
- 'onFocus',
- 'onBlur',
- ];
-
- passedEventList.forEach((eventName) => {
- if (restProps[eventName]) {
- passedProps[eventName] = (...args: any[]) => {
- mergedChildrenProps[eventName]?.(...args);
- restProps[eventName](...args);
- };
- }
- });
-
- const arrowPos: ArrowPos = {
- x: arrowX,
- y: arrowY,
- };
-
- const innerArrow: ArrowTypeOuter = arrow
- ? {
- // true and Object likely
- ...(arrow !== true ? arrow : {}),
- }
- : null;
-
- // Child Node
- const triggerNode = React.cloneElement(child, {
- ...mergedChildrenProps,
- ...passedProps,
- });
-
- // Render
- return (
- <>
-
- {triggerNode}
-
- {rendedRef.current && (
-
-
-
- )}
- >
- );
- });
-
- if (process.env.NODE_ENV !== 'production') {
- Trigger.displayName = 'Trigger';
- }
-
- return Trigger;
-}
-
-export default generateTrigger(Portal);
diff --git a/src/interface.ts b/src/interface.ts
index 0ff8cfd..379f5d5 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -1,118 +1,18 @@
-export type Placement =
- | 'top'
- | 'left'
- | 'right'
- | 'bottom'
- | 'topLeft'
- | 'topRight'
- | 'bottomLeft'
- | 'bottomRight'
- | 'leftTop'
- | 'leftBottom'
- | 'rightTop'
- | 'rightBottom';
+import * as React from "react";
-export type AlignPointTopBottom = 't' | 'b' | 'c';
-export type AlignPointLeftRight = 'l' | 'r' | 'c';
+export type ListyProps = {
+ items: T[];
+ sticky?: boolean;
+ height: number;
+ rowKey: string | ((item: T) => string);
+ itemRender: (item: T) => React.ReactNode;
+ groupRender: {
-/** 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;
-
- /**
- * 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 {
- className?: string;
- content?: React.ReactNode;
-}
-
-export type ArrowPos = {
- x?: number;
- y?: number;
+ onScrollEnd: (lastIndex: number) => void;
};
-export type BuildInPlacements = Record;
-
-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;
-}
+export type ListyItemProps = {
+ item: T;
+ index: number;
+};
\ No newline at end of file
diff --git a/src/mock.tsx b/src/mock.tsx
deleted file mode 100644
index 8a8c0a1..0000000
--- a/src/mock.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import * as React from 'react';
-import { generateTrigger } from './index';
-
-interface MockPortalProps {
- open?: boolean;
- autoDestroy?: boolean;
- children: React.ReactElement;
- getContainer?: () => HTMLElement;
-}
-
-const MockPortal: React.FC = ({
- 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);
diff --git a/src/util.ts b/src/util.ts
index cc13eb6..e69de29 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,193 +0,0 @@
-import type {
- AlignType,
- BuildInPlacements,
-} from './interface';
-
-function isPointsEq(
- a1: string[] = [],
- a2: string[] = [],
- isAlignPoint: boolean,
-): boolean {
- if (isAlignPoint) {
- return a1[0] === a2[0];
- }
- return a1[0] === a2[0] && a1[1] === a2[1];
-}
-
-export function getAlignPopupClassName(
- builtinPlacements: BuildInPlacements,
- prefixCls: string,
- align: AlignType,
- isAlignPoint: boolean,
-): string {
- 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: HTMLElement) {
- 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: HTMLElement) {
- const scrollerList: HTMLElement[] = [];
- 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: number, defaultValue = 1) {
- return Number.isNaN(num) ? defaultValue : num;
-}
-
-function getPxValue(val: string) {
- return toNum(parseFloat(val), 0);
-}
-
-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 function getVisibleArea(
- initArea: VisibleArea,
- scrollerList?: HTMLElement[],
-) {
- 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;
-}
diff --git a/tests/align.test.tsx b/tests/align.test.tsx
deleted file mode 100644
index 1ef62f0..0000000
--- a/tests/align.test.tsx
+++ /dev/null
@@ -1,299 +0,0 @@
-import { act, cleanup, fireEvent, render } from '@testing-library/react';
-import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook';
-import React from 'react';
-import type { TriggerProps, TriggerRef } from '../src';
-import Trigger from '../src';
-import { awaitFakeTimer } from './util';
-
-import { _rs } from '@rc-component/resize-observer';
-
-export const triggerResize = (target: Element) => {
- act(() => {
- _rs([{ target } as ResizeObserverEntry]);
- });
-};
-
-describe('Trigger.Align', () => {
- let targetVisible = true;
-
- let rectX = 100;
- let rectY = 100;
- let rectWidth = 100;
- let rectHeight = 100;
-
- beforeAll(() => {
- spyElementPrototypes(HTMLDivElement, {
- getBoundingClientRect: () => ({
- x: rectX,
- y: rectY,
- left: rectX,
- top: rectY,
- width: rectWidth,
- height: rectHeight,
- right: 200,
- bottom: 200,
- }),
- });
-
- spyElementPrototypes(HTMLElement, {
- offsetParent: {
- get: () => (targetVisible ? document.body : null),
- },
- });
- spyElementPrototypes(SVGElement, {
- offsetParent: {
- get: () => (targetVisible ? document.body : null),
- },
- });
- });
-
- beforeEach(() => {
- targetVisible = true;
-
- rectX = 100;
- rectY = 100;
- rectWidth = 100;
- rectHeight = 100;
-
- jest.useFakeTimers();
- });
-
- afterEach(() => {
- cleanup();
- jest.useRealTimers();
- });
-
- it('not show', async () => {
- const onAlign = jest.fn();
-
- const Demo = (props: Partial) => {
- const scrollRef = React.useRef(null);
-
- return (
- <>
-
- trigger}
- getPopupContainer={() => scrollRef.current!}
- {...props}
- >
-
-
- >
- );
- };
-
- const { rerender, container } = render();
- const scrollDiv = container.querySelector('.scroll')!;
-
- const mockAddEvent = jest.spyOn(scrollDiv, 'addEventListener');
-
- expect(mockAddEvent).not.toHaveBeenCalled();
-
- // Visible
- rerender();
- expect(mockAddEvent).toHaveBeenCalled();
-
- // Scroll
- onAlign.mockReset();
- fireEvent.scroll(scrollDiv);
-
- await awaitFakeTimer();
- expect(onAlign).toHaveBeenCalled();
- });
-
- it('resize align', async () => {
- const onAlign = jest.fn();
-
- const { container } = render(
- trigger}
- >
-
- ,
- );
-
- await Promise.resolve();
- onAlign.mockReset();
-
- // Resize
- const target = container.querySelector('.target')!;
- triggerResize(target);
-
- await awaitFakeTimer();
- expect(onAlign).toHaveBeenCalled();
- });
-
- it('placement is higher than popupAlign', async () => {
- render(
- }
- builtinPlacements={{
- top: {},
- }}
- popupPlacement="top"
- popupAlign={{}}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- expect(
- document.querySelector('.rc-trigger-popup-placement-top'),
- ).toBeTruthy();
- });
-
- it('invisible should not align', async () => {
- const onPopupAlign = jest.fn();
- const triggerRef = React.createRef();
-
- render(
- }
- popupAlign={{}}
- onPopupAlign={onPopupAlign}
- ref={triggerRef}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- expect(onPopupAlign).toHaveBeenCalled();
- onPopupAlign.mockReset();
-
- for (let i = 0; i < 10; i += 1) {
- triggerRef.current!.forceAlign();
-
- await awaitFakeTimer();
- expect(onPopupAlign).toHaveBeenCalled();
- onPopupAlign.mockReset();
- }
-
- // Make invisible
- targetVisible = false;
-
- triggerRef.current!.forceAlign();
- await awaitFakeTimer();
- expect(onPopupAlign).not.toHaveBeenCalled();
- });
-
- it('align should merge into placement', async () => {
- render(
- }
- builtinPlacements={{
- top: {
- targetOffset: [0, 0],
- },
- }}
- popupPlacement="top"
- popupAlign={{
- targetOffset: [-903, -1128],
- }}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- expect(
- document.querySelector('.rc-trigger-popup-placement-top'),
- ).toHaveStyle({
- left: `753px`,
- top: `978px`,
- });
- });
-
- it('targetOffset support ptg', async () => {
- render(
- }
- popupAlign={{
- targetOffset: ['50%', '-50%'],
- }}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- // Correct this if I miss understand the value calculation
- // from https://github.com/yiminghe/dom-align/blob/master/src/getElFuturePos.js
- expect(document.querySelector('.rc-trigger-popup')).toHaveStyle({
- left: `-50px`,
- top: `50px`,
- });
- });
-
- it('support dynamicInset', async () => {
- render(
- }
- popupAlign={{
- points: ['bc', 'tc'],
- _experimental: {
- dynamicInset: true,
- },
- }}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- expect(document.querySelector('.rc-trigger-popup')).toHaveStyle({
- bottom: `100px`,
- });
- });
-
- it('round when decimal precision', async () => {
- rectX = 22.6;
- rectY = 33.4;
- rectWidth = 33.7;
- rectHeight = 55.9;
-
- render(
- }
- popupAlign={{
- points: ['tl', 'bl'],
- }}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- expect(document.querySelector('.rc-trigger-popup')).toHaveStyle({
- top: `56px`,
- });
- });
-});
diff --git a/tests/arrow.test.jsx b/tests/arrow.test.jsx
deleted file mode 100644
index 19c53b9..0000000
--- a/tests/arrow.test.jsx
+++ /dev/null
@@ -1,214 +0,0 @@
-/* eslint-disable max-classes-per-file */
-
-import { act, cleanup, render } from '@testing-library/react';
-import {
- spyElementPrototype,
- spyElementPrototypes,
-} from '@rc-component/util/lib/test/domHook';
-import Trigger from '../src';
-
-describe('Trigger.Arrow', () => {
- beforeAll(() => {
- spyElementPrototypes(HTMLElement, {
- offsetParent: {
- get: () => document.body,
- },
- });
- });
-
- beforeEach(() => {
- jest.useFakeTimers();
- });
-
- afterEach(() => {
- cleanup();
- jest.useRealTimers();
- });
-
- async function awaitFakeTimer() {
- for (let i = 0; i < 10; i += 1) {
- await act(async () => {
- jest.advanceTimersByTime(100);
- await Promise.resolve();
- });
- }
- }
-
- it('not show', () => {
- render(
- trigger} arrow>
-
- ,
- );
- });
-
- describe('direction', () => {
- let divSpy;
- let windowSpy;
-
- beforeAll(() => {
- divSpy = spyElementPrototype(
- HTMLDivElement,
- 'getBoundingClientRect',
- () => ({
- x: 200,
- y: 200,
- left: 200,
- top: 200,
- width: 100,
- height: 50,
- }),
- );
-
- windowSpy = spyElementPrototypes(Window, {
- clientWidth: {
- get: () => 1000,
- },
- clientHeight: {
- get: () => 1000,
- },
- });
- });
-
- afterAll(() => {
- divSpy.mockRestore();
- windowSpy.mockRestore();
- });
-
- function test(name, align, style) {
- it(name, async () => {
- render(
- trigger}
- arrow
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- expect(document.querySelector('.rc-trigger-popup-arrow')).toHaveStyle(
- style,
- );
- });
- }
-
- // Top
- test(
- 'top',
- {
- points: ['bl', 'tl'],
- },
- {
- bottom: 0,
- },
- );
-
- // Bottom
- test(
- 'bottom',
- {
- points: ['tc', 'bc'],
- },
- {
- top: 0,
- },
- );
-
- // Left
- test(
- 'left',
- {
- points: ['cr', 'cl'],
- },
- {
- right: 0,
- },
- );
-
- // Right
- test(
- 'right',
- {
- points: ['cl', 'cr'],
- },
- {
- left: 0,
- },
- );
-
- it('not aligned', async () => {
- render(
- trigger}
- arrow
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- // Not have other align style
- const { style } = document.querySelector('.rc-trigger-popup-arrow');
- expect(style.position).toBeTruthy();
- expect(style.left).toBeFalsy();
- expect(style.right).toBeFalsy();
- expect(style.top).toBeFalsy();
- expect(style.bottom).toBeFalsy();
- });
-
- it('arrow classname', async () => {
- render(
- trigger}
- arrow={{
- className: 'abc',
- }}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- const arrowDom = document.querySelector('.rc-trigger-popup-arrow');
- expect(arrowDom.classList.contains('abc')).toBeTruthy();
- });
- });
-
- it('content', async () => {
- render(
- trigger}
- arrow={{
- content: ,
- }}
- >
-
- ,
- );
-
- await awaitFakeTimer();
-
- expect(document.querySelector('.my-content')).toBeTruthy();
- });
-});
diff --git a/tests/basic.test.jsx b/tests/basic.test.jsx
deleted file mode 100644
index 04d274b..0000000
--- a/tests/basic.test.jsx
+++ /dev/null
@@ -1,1180 +0,0 @@
-/* eslint-disable max-classes-per-file */
-import { act, cleanup, fireEvent, render } from '@testing-library/react';
-import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook';
-import React, { StrictMode, createRef } from 'react';
-import ReactDOM, { createPortal } from 'react-dom';
-import Trigger from '../src';
-import { awaitFakeTimer, placementAlignMap } from './util';
-
-describe('Trigger.Basic', () => {
- beforeAll(() => {
- spyElementPrototypes(HTMLElement, {
- offsetParent: {
- get: () => document.body,
- },
- });
- });
-
- beforeEach(() => {
- jest.useFakeTimers();
- });
-
- afterEach(() => {
- cleanup();
- jest.useRealTimers();
- });
-
- function trigger(dom, selector, method = 'click') {
- fireEvent[method](dom.querySelector(selector));
- act(() => jest.runAllTimers());
- }
-
- function isPopupHidden() {
- return document
- .querySelector('.rc-trigger-popup')
- .className.includes('-hidden');
- }
- function isPopupClassHidden(name) {
- return document.querySelector(name).className.includes('-hidden');
- }
- function isPopupAllHidden() {
- const popupArr = document.querySelectorAll('.rc-trigger-popup');
-
- return Array.from(popupArr).every((item) =>
- item.className.includes('-hidden'),
- );
- }
- describe('getPopupContainer', () => {
- it('defaults to document.body', () => {
- const { container } = render(
- tooltip2}
- >
- click
- ,
- );
-
- trigger(container, '.target');
-
- const popupDomNode = document.querySelector('.rc-trigger-popup');
- expect(popupDomNode.parentNode.parentNode).toBeInstanceOf(
- HTMLBodyElement,
- );
- });
-
- it('can change', () => {
- function getPopupContainer(node) {
- return node.parentNode;
- }
-
- const { container } = render(
-
-
tooltip2}
- >
- click
-
-
,
- document.createElement('div'),
- );
-
- trigger(container, '.target');
-
- const popupDomNode = document.querySelector('.rc-trigger-popup');
- expect(popupDomNode.parentNode).toBe(container.querySelector('.holder'));
- });
- });
-
- describe('action', () => {
- it('click works', () => {
- const { container } = render(
- tooltip2}
- >
- click
- ,
- );
-
- trigger(container, '.target');
- expect(document.querySelector('.x-content').textContent).toBe('tooltip2');
-
- trigger(container, '.target');
- expect(isPopupHidden()).toBeTruthy();
- });
-
- it('click works with function', () => {
- const popup = function renderPopup() {
- return tooltip3;
- };
- const { container } = render(
-
- click
- ,
- );
-
- trigger(container, '.target');
- expect(document.querySelector('.x-content').textContent).toBe('tooltip3');
-
- trigger(container, '.target');
- expect(isPopupHidden()).toBeTruthy();
- });
-
- describe('hover works', () => {
- it('mouse event', () => {
- const { container } = render(
- trigger}
- >
- click
- ,
- );
-
- trigger(container, '.target', 'mouseEnter');
- expect(isPopupHidden()).toBeFalsy();
-
- trigger(container, '.target', 'mouseLeave');
- expect(isPopupHidden()).toBeTruthy();
- });
-
- it('pointer event', () => {
- const { container } = render(
- trigger}
- >
- click
- ,
- );
-
- trigger(container, '.target', 'pointerEnter');
- expect(isPopupHidden()).toBeFalsy();
-
- trigger(container, '.target', 'pointerLeave');
- expect(isPopupHidden()).toBeTruthy();
-
- // Enter again but move in popup
- trigger(container, '.target', 'pointerEnter');
- expect(isPopupHidden()).toBeFalsy();
-
- fireEvent.pointerLeave(container.querySelector('.target'));
- trigger(document, '.rc-trigger-popup', 'pointerEnter');
- expect(isPopupHidden()).toBeFalsy();
- });
- });
-
- it('contextMenu works', () => {
- const triggerRef = createRef();
- const { container } = render(
- trigger}
- >
- contextMenu
- ,
- );
-
- trigger(container, '.target', 'contextMenu');
- expect(isPopupHidden()).toBeFalsy();
-
- fireEvent.click(document.querySelector('.target'));
-
- expect(isPopupHidden()).toBeTruthy();
- });
- it('contextMenu all close', () => {
- const triggerRef1 = createRef();
- const triggerRef2 = createRef();
- const { container } = render(
- <>
- trigger1}
- >
- contextMenu 1
-
- trigger2}
- >
- contextMenu 2
-
- >,
- );
-
- trigger(container, '.target1', 'contextMenu');
- trigger(container, '.target2', 'contextMenu');
- expect(isPopupClassHidden('.trigger-popup1')).toBeTruthy();
- expect(isPopupClassHidden('.trigger-popup2')).toBeFalsy();
-
- trigger(container, '.target1', 'contextMenu');
- expect(isPopupClassHidden('.trigger-popup1')).toBeFalsy();
- expect(isPopupClassHidden('.trigger-popup2')).toBeTruthy();
-
- fireEvent.mouseDown(document.body);
- expect(isPopupAllHidden()).toBeTruthy();
- });
- describe('afterOpenChange can be triggered', () => {
- it('uncontrolled', async () => {
- let triggered = 0;
- const afterPopupVisibleChange = jest.fn();
- const { container } = render(
- {
- triggered = 1;
- }}
- afterPopupVisibleChange={afterPopupVisibleChange}
- popup={trigger}
- >
- click
- ,
- );
-
- trigger(container, '.target');
-
- await awaitFakeTimer();
-
- expect(triggered).toBe(1);
- expect(afterPopupVisibleChange).toHaveBeenCalledWith(true);
- });
-
- it('controlled', async () => {
- let triggered = 0;
-
- const Demo = () => {
- const [visible, setVisible] = React.useState(false);
-
- return (
- <>
-