diff --git a/packages/components/popup/Popup.tsx b/packages/components/popup/Popup.tsx index 993106979f..22724da8b5 100644 --- a/packages/components/popup/Popup.tsx +++ b/packages/components/popup/Popup.tsx @@ -70,6 +70,7 @@ const Popup = forwardRef((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 元素 @@ -153,9 +154,11 @@ const Popup = forwardRef((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'); } @@ -242,12 +245,59 @@ const Popup = forwardRef((originalProps, ref) => { ); + // 处理 shadow root(web component)和 trigger 隐藏的情况 + function updatePopper() { + const popper = popperRef.current; + const triggerEl = getRefDom(triggerRef); + // 如果没有渲染弹层或不可见则不触发更新 + if (!popper || !visible) return; + + 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(); + } 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, () => ({ - getPopper: () => popperRef.current, + // 未公开 getPopupElement: () => popupRef.current, + // 未公开 getPortalElement: () => portalRef.current, + // 未公开 getPopupContentElement: () => contentRef.current, + /// 未公开 setVisible: (visible: boolean) => onVisibleChange(visible, { trigger: 'document' }), + /** 获取 popper 实例 */ + getPopper: () => popperRef.current, + /** 获取浮层元素 */ + getOverlay: () => portalRef.current, + /** 获取浮层悬浮状态 */ + getOverlayState: () => ({ hover: isOverlayHover }), + /** 更新浮层内容 */ + update: () => updatePopper(), })); return ( diff --git a/packages/components/popup/popup.en-US.md b/packages/components/popup/popup.en-US.md index 4e74ce91d9..fcd92a2d2f 100644 --- a/packages/components/popup/popup.en-US.md +++ b/packages/components/popup/popup.en-US.md @@ -38,6 +38,7 @@ Currently, this can be solved by `Fragment` or other `HTML` elements ``` ## API + ### Popup Props name | type | default | description | required @@ -64,3 +65,12 @@ zIndex | Number | - | \- | N onScroll | Function | | Typescript:`(context: { e: WheelEvent }) => void`
| N onScrollToBottom | Function | | Typescript:`(context: { e: WheelEvent }) => void`
| N onVisibleChange | Function | | Typescript:`(visible: boolean, context: PopupVisibleChangeContext) => void`
[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts)。
`interface PopupVisibleChangeContext { e?: PopupTriggerEvent; trigger?: PopupTriggerSource }`

`type PopupTriggerEvent = MouseEvent \| FocusEvent \| KeyboardEvent`

`type PopupTriggerSource = 'document' \| 'trigger-element-click' \| 'trigger-element-hover' \| 'trigger-element-blur' \| 'trigger-element-focus' \| 'trigger-element-mousedown' \| 'context-menu' \| 'keydown-esc'`
| 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)。
`import { Instance } from '@popperjs/core'`
+update | \- | \- | used to update overlay content diff --git a/packages/components/popup/popup.md b/packages/components/popup/popup.md index ae4ab1a5ff..9bfa3a6b11 100644 --- a/packages/components/popup/popup.md +++ b/packages/components/popup/popup.md @@ -38,6 +38,7 @@ https://popper.js.org/docs/v2/constructors/#types ``` ## API + ### Popup Props 名称 | 类型 | 默认值 | 说明 | 必传 @@ -64,3 +65,12 @@ zIndex | Number | - | 组件层级,Web 侧样式默认为 5500,移动端和 onScroll | Function | | TS 类型:`(context: { e: WheelEvent }) => void`
下拉选项滚动事件 | N onScrollToBottom | Function | | TS 类型:`(context: { e: WheelEvent }) => void`
下拉滚动触底事件,常用于滚动到底执行具体业务逻辑 | N onVisibleChange | Function | | TS 类型:`(visible: boolean, context: PopupVisibleChangeContext) => void`
当浮层隐藏或显示时触发,`trigger=document` 表示点击非浮层元素触发;`trigger=context-menu` 表示右击触发。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts)。
`interface PopupVisibleChangeContext { e?: PopupTriggerEvent; trigger?: PopupTriggerSource }`

`type PopupTriggerEvent = MouseEvent \| FocusEvent \| KeyboardEvent`

`type PopupTriggerSource = 'document' \| 'trigger-element-click' \| 'trigger-element-hover' \| 'trigger-element-blur' \| 'trigger-element-focus' \| 'trigger-element-mousedown' \| 'context-menu' \| 'keydown-esc'`
| N + +### PopupInstanceFunctions 组件实例方法 + +名称 | 参数 | 返回值 | 描述 +-- | -- | -- | -- +getOverlay | \- | `HTMLElement \| null` | 获取浮层元素 +getOverlayState | \- | `{ hover: boolean }` | 获取浮层悬浮状态 +getPopper | \- | `Instance \| null` | 获取当前组件 popper 实例。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/popup/type.ts)。
`import { Instance } from '@popperjs/core'`
+update | \- | \- | 更新浮层内容 diff --git a/packages/components/popup/type.ts b/packages/components/popup/type.ts index bb98bce7ee..c0efb2c278 100644 --- a/packages/components/popup/type.ts +++ b/packages/components/popup/type.ts @@ -4,12 +4,13 @@ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC * */ +import { Instance } from '@popperjs/core'; import { TNode, ClassName, Styles, AttachNode } from '../common'; import { MouseEvent, KeyboardEvent, FocusEvent, WheelEvent } from 'react'; export interface TdPopupProps { /** - * 制定挂载节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body + * 指定挂载节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body * @default 'body' */ attach?: AttachNode; @@ -104,6 +105,26 @@ export interface TdPopupProps { onVisibleChange?: (visible: boolean, context: PopupVisibleChangeContext) => void; } +/** 组件实例方法 */ +export interface PopupInstanceFunctions { + /** + * 获取浮层元素 + */ + getOverlay?: () => HTMLElement | null; + /** + * 获取浮层悬浮状态 + */ + getOverlayState?: () => { hover: boolean }; + /** + * 获取当前组件 popper 实例 + */ + getPopper?: () => Instance | null; + /** + * 更新浮层内容 + */ + update?: () => void; +} + export type PopupPlacement = | 'top' | 'left'