Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/components/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
const { keepExpand, keepFade } = useAnimation();
const { height: windowHeight, width: windowWidth } = useWindowSize();
const [visible, onVisibleChange] = useControlled(props, 'visible', props.onVisibleChange);
const [isOverlayHover, setIsOverlayHover] = useState(false);

const [popupElement, setPopupElement] = useState(null);
const triggerRef = useRef(null); // 记录 trigger 元素
Expand Down Expand Up @@ -153,9 +154,11 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
}, [visible, updateScrollTop, getTriggerDom]);

function handleExited() {
setIsOverlayHover(false);
!destroyOnClose && popupElement && (popupElement.style.display = 'none');
}
function handleEnter() {
setIsOverlayHover(true);
!destroyOnClose && popupElement && (popupElement.style.display = 'block');
}

Expand Down Expand Up @@ -242,12 +245,56 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
</CSSTransition>
);

// 处理 shadow root(web component)和 trigger 隐藏的情况
function updatePopper() {
const popper = popperRef.current;
const triggerEl = getRefDom(triggerRef);
// 如果没有渲染弹层或不可见则不触发更新
if (!popupRef.current || !visible) return;
if (!popper) return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么还得分两个if?


try {
// web component 的元素可能在 shadow root 内,需要特殊处理
const root = triggerEl?.getRootNode?.();
if (root && root instanceof ShadowRoot) {
// popper 的实例内部结构可能是 state.elements.reference
// 尝试兼容不同实现,先赋值再更新
if (popper.state) popper.state.elements.reference = triggerEl;
popper.update?.();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

popper还能没有update?

} else {
const rect = triggerEl?.getBoundingClientRect?.();
let parent = triggerEl as HTMLElement | null;
while (parent && parent !== document.body) {
parent = parent.parentElement;
}
const isHidden = parent !== document.body || (rect && rect.width === 0 && rect.height === 0);
if (!isHidden) {
if (popper.state) popper.state.elements.reference = triggerEl;
popper.update?.();
} else {
// trigger 不在文档流内或被隐藏,则隐藏浮层
onVisibleChange(false, { trigger: 'document' });
}
}
} catch (e) {
// 直接尝试更新
popper.update?.();
}
}

useImperativeHandle(ref, () => ({
/** 获取 popper 实例 */
getPopper: () => popperRef.current,
getPopupElement: () => popupRef.current,
getPortalElement: () => portalRef.current,
getPopupContentElement: () => contentRef.current,
setVisible: (visible: boolean) => onVisibleChange(visible, { trigger: 'document' }),
/** 获取浮层元素 */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里把文档提供的和内部方法/非文档提供的分开吧 不要交叉 未公开的注释说明下

getOverlay: () => portalRef.current,
/** 获取浮层悬浮状态 */
getOverlayState: () => ({ hover: isOverlayHover }),
/** 更新浮层内容 */
update: () => updatePopper(),
}));

return (
Expand Down
142 changes: 76 additions & 66 deletions packages/components/popup/popup.en-US.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上

Original file line number Diff line number Diff line change
@@ -1,66 +1,76 @@
:: BASE_DOC ::

### Called Popup via plugin

Calling Popup through the plug-in method is used to render Popup in a scene with existing nodes. At the same time, no matter how it is called, it will only be mounted on one node, which is used to reduce the number of Popup rendering nodes on the page.

Support functional calls `PopupPlugin` 。
- `PopupPlugin(triggerElement, content, popupProps)`

{{ plugin }}

### Dynamic Adaptation

When the trigger or popup display content changes dynamically, the position is adjusted adaptively

{{ dynamic }}

### popperOptions usage

https://popper.js.org/docs/v2/constructors/#types

- `popperOptions` = `Options`

{{ popper-options }}

## FAQ

### How to solve the problem of position offset when nesting `Popup` components?

Currently, this can be solved by `Fragment` or other `HTML` elements

```js
<Popup content="Popup Content">
<>
{children}
</>
</Popup>
```

## API
### Popup Props

name | type | default | description | required
-- | -- | -- | -- | --
attach | String / Function | 'body' | Typescript:`AttachNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
children | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
content | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
delay | Number / Array | - | delay to show or hide popover。Typescript:`number \| Array<number>` | N
destroyOnClose | Boolean | false | \- | N
disabled | Boolean | - | \- | N
hideEmptyPopup | Boolean | false | \- | N
overlayClassName | String / Object / Array | - | Typescript:`ClassName`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
overlayInnerClassName | String / Object / Array | - | Typescript:`ClassName`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
overlayInnerStyle | Boolean / Object / Function | - | Typescript:`Styles \| ((triggerElement: HTMLElement, popupElement: HTMLElement) => Styles)`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
overlayStyle | Boolean / Object / Function | - | Typescript:`Styles \| ((triggerElement: HTMLElement, popupElement: HTMLElement) => Styles)`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
placement | String | top | Typescript:`PopupPlacement` `type PopupPlacement = 'top'\|'left'\|'right'\|'bottom'\|'top-left'\|'top-right'\|'bottom-left'\|'bottom-right'\|'left-top'\|'left-bottom'\|'right-top'\|'right-bottom'`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts) | N
popperOptions | Object | - | popper initial options,details refer to https://popper.js.org/docs | N
showArrow | Boolean | false | \- | N
trigger | String | hover | options:hover/click/focus/mousedown/context-menu | N
triggerElement | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
visible | Boolean | - | Typescript:`boolean` | N
defaultVisible | Boolean | - | uncontrolled property。Typescript:`boolean` | N
zIndex | Number | - | \- | N
onScroll | Function | | Typescript:`(context: { e: WheelEvent }) => void`<br/> | N
onScrollToBottom | Function | | Typescript:`(context: { e: WheelEvent }) => void`<br/> | N
onVisibleChange | Function | | Typescript:`(visible: boolean, context: PopupVisibleChangeContext) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts)。<br/>`interface PopupVisibleChangeContext { e?: PopupTriggerEvent; trigger?: PopupTriggerSource }`<br/><br/>`type PopupTriggerEvent = MouseEvent \| FocusEvent \| KeyboardEvent`<br/><br/>`type PopupTriggerSource = 'document' \| 'trigger-element-click' \| 'trigger-element-hover' \| 'trigger-element-blur' \| 'trigger-element-focus' \| 'trigger-element-mousedown' \| 'context-menu' \| 'keydown-esc'`<br/> | N
:: BASE_DOC ::

### Called Popup via plugin

Calling Popup through the plug-in method is used to render Popup in a scene with existing nodes. At the same time, no matter how it is called, it will only be mounted on one node, which is used to reduce the number of Popup rendering nodes on the page.

Support functional calls `PopupPlugin` 。
- `PopupPlugin(triggerElement, content, popupProps)`

{{ plugin }}

### Dynamic Adaptation

When the trigger or popup display content changes dynamically, the position is adjusted adaptively

{{ dynamic }}

### popperOptions usage

https://popper.js.org/docs/v2/constructors/#types

- `popperOptions` = `Options`

{{ popper-options }}

## FAQ

### How to solve the problem of position offset when nesting `Popup` components?

Currently, this can be solved by `Fragment` or other `HTML` elements

```js
<Popup content="Popup Content">
<>
{children}
</>
</Popup>
```

## API

### Popup Props

name | type | default | description | required
-- | -- | -- | -- | --
attach | String / Function | 'body' | Typescript:`AttachNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
children | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
content | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
delay | Number / Array | - | delay to show or hide popover。Typescript:`number \| Array<number>` | N
destroyOnClose | Boolean | false | \- | N
disabled | Boolean | - | \- | N
hideEmptyPopup | Boolean | false | \- | N
overlayClassName | String / Object / Array | - | Typescript:`ClassName`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
overlayInnerClassName | String / Object / Array | - | Typescript:`ClassName`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
overlayInnerStyle | Boolean / Object / Function | - | Typescript:`Styles \| ((triggerElement: HTMLElement, popupElement: HTMLElement) => Styles)`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
overlayStyle | Boolean / Object / Function | - | Typescript:`Styles \| ((triggerElement: HTMLElement, popupElement: HTMLElement) => Styles)`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
placement | String | top | Typescript:`PopupPlacement` `type PopupPlacement = 'top'\|'left'\|'right'\|'bottom'\|'top-left'\|'top-right'\|'bottom-left'\|'bottom-right'\|'left-top'\|'left-bottom'\|'right-top'\|'right-bottom'`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts) | N
popperOptions | Object | - | popper initial options,details refer to https://popper.js.org/docs | N
showArrow | Boolean | false | \- | N
trigger | String | hover | options:hover/click/focus/mousedown/context-menu | N
triggerElement | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
visible | Boolean | - | Typescript:`boolean` | N
defaultVisible | Boolean | - | uncontrolled property。Typescript:`boolean` | N
zIndex | Number | - | \- | N
onScroll | Function | | Typescript:`(context: { e: WheelEvent }) => void`<br/> | N
onScrollToBottom | Function | | Typescript:`(context: { e: WheelEvent }) => void`<br/> | N
onVisibleChange | Function | | Typescript:`(visible: boolean, context: PopupVisibleChangeContext) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts)。<br/>`interface PopupVisibleChangeContext { e?: PopupTriggerEvent; trigger?: PopupTriggerSource }`<br/><br/>`type PopupTriggerEvent = MouseEvent \| FocusEvent \| KeyboardEvent`<br/><br/>`type PopupTriggerSource = 'document' \| 'trigger-element-click' \| 'trigger-element-hover' \| 'trigger-element-blur' \| 'trigger-element-focus' \| 'trigger-element-mousedown' \| 'context-menu' \| 'keydown-esc'`<br/> | N

### PopupInstanceFunctions 组件实例方法

name | params | return | description
-- | -- | -- | --
getOverlay | \- | `HTMLElement \| null` | used to get overly html element
getOverlayState | \- | `{ hover: boolean }` | get mouseover state of overlay
getPopper | \- | `Instance \| null` | get the popup component popper instance。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts)。<br/>`import { Instance } from '@popperjs/core'`<br/>
update | \- | \- | used to update overlay content
Loading
Loading