Skip to content
18 changes: 14 additions & 4 deletions packages/components/_util/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { canUseDocument } from './dom';

export const on = ((): any => {
if (canUseDocument && document.addEventListener) {
return (element: Node, event: string, handler: EventListenerOrEventListenerObject): any => {
return (
element: Node,
event: string,
handler: EventListenerOrEventListenerObject,
options?: AddEventListenerOptions,
): any => {
if (element && event && handler) {
element.addEventListener(event, handler, false);
element.addEventListener(event, handler, options ?? false);
}
};
}
Expand All @@ -17,9 +22,14 @@ export const on = ((): any => {

export const off = ((): any => {
if (canUseDocument && document.removeEventListener) {
return (element: Node, event: string, handler: EventListenerOrEventListenerObject): any => {
return (
element: Node,
event: string,
handler: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
): any => {
if (element && event) {
element.removeEventListener(event, handler, false);
element.removeEventListener(event, handler, options ?? false);
}
};
}
Expand Down
13 changes: 13 additions & 0 deletions packages/components/_util/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,16 @@ export const getNodeRef: <T = any>(node: React.ReactNode) => React.Ref<T> | null
}
return null;
};

export const mergeRefs =
(...refs: any[]) =>
(instance: any) => {
refs.forEach((ref) => {
if (typeof ref === 'function') {
ref(instance);
} else if (ref && typeof ref === 'object') {
// eslint-disable-next-line no-param-reassign
ref.current = instance;
}
});
};
31 changes: 16 additions & 15 deletions packages/components/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from 'classnames';
import { debounce, isFunction } from 'lodash-es';
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import { getRefDom } from '../_util/ref';

import { getCssVarsValue } from '../_util/style';
import Portal from '../common/Portal';
import useAnimation from '../hooks/useAnimation';
Expand All @@ -16,9 +16,10 @@ import usePopper from '../hooks/usePopper';
import useWindowSize from '../hooks/useWindowSize';
import { popupDefaultProps } from './defaultProps';
import useTrigger from './hooks/useTrigger';
import type { TdPopupProps } from './type';
import { getTransitionParams } from './utils/transition';

import type { TdPopupProps } from './type';

export interface PopupProps extends TdPopupProps {
// 是否触发展开收起动画,内部下拉式组件使用
expandAnimation?: boolean;
Expand Down Expand Up @@ -72,7 +73,6 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
const [visible, onVisibleChange] = useControlled(props, 'visible', props.onVisibleChange);

const [popupElement, setPopupElement] = useState(null);
const triggerRef = useRef(null); // 记录 trigger 元素
const popupRef = useRef(null); // popup dom 元素,css transition 需要用
const portalRef = useRef(null); // portal dom 元素
const contentRef = useRef(null); // 内容部分
Expand Down Expand Up @@ -100,18 +100,20 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
[placement],
);

const { getTriggerNode, getPopupProps, getTriggerDom } = useTrigger({
triggerRef,
const { triggerElementIsString, getTriggerElement, getTriggerNode } = useTrigger({
children,
triggerElement,
content,
disabled,
trigger,
visible,
delay,
onVisibleChange,
});
const triggerEl = getTriggerElement();

const popperOptions = props.popperOptions as Options;
popperRef.current = usePopper(getRefDom(triggerRef), popupElement, {
popperRef.current = usePopper(triggerEl, popupElement, {
placement: popperPlacement,
...popperOptions,
});
Expand All @@ -128,8 +130,8 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {

const updateTimeRef = useRef(null);
// 监听 trigger 节点或内容变化动态更新 popup 定位
useMutationObserver(getRefDom(triggerRef), () => {
const isDisplayNone = getCssVarsValue('display', getRefDom(triggerRef)) === 'none';
useMutationObserver(triggerEl, () => {
const isDisplayNone = getCssVarsValue('display', triggerEl) === 'none';
if (visible && !isDisplayNone) {
clearTimeout(updateTimeRef.current);
updateTimeRef.current = setTimeout(() => popperRef.current?.update?.(), 0);
Expand All @@ -146,11 +148,10 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {

// 下拉展开时更新内部滚动条
useEffect(() => {
if (!triggerRef.current) triggerRef.current = getTriggerDom();
if (visible) {
updateScrollTop?.(contentRef.current);
}
}, [visible, updateScrollTop, getTriggerDom]);
}, [visible, updateScrollTop]);

function handleExited() {
!destroyOnClose && popupElement && (popupElement.style.display = 'none');
Expand All @@ -175,8 +176,8 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {

// 整理浮层样式
function getOverlayStyle(overlayStyle: TdPopupProps['overlayStyle']) {
if (getRefDom(triggerRef) && popupRef.current && typeof overlayStyle === 'function') {
return { ...overlayStyle(getRefDom(triggerRef), popupRef.current) };
if (triggerEl && popupRef.current && typeof overlayStyle === 'function') {
return { ...overlayStyle(triggerEl, popupRef.current) };
}
return { ...overlayStyle };
}
Expand All @@ -191,7 +192,7 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
onEnter={handleEnter}
onExited={handleExited}
>
<Portal triggerNode={getRefDom(triggerRef)} attach={popupAttach} ref={portalRef}>
<Portal triggerNode={triggerEl} attach={popupAttach} ref={portalRef}>
<CSSTransition
appear
timeout={0}
Expand All @@ -213,7 +214,6 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
style={{ ...styles.popper, zIndex, ...getOverlayStyle(overlayStyle) }}
className={classNames(`${classPrefix}-popup`, overlayClassName)}
{...attributes.popper}
{...getPopupProps()}
onClick={(e) => props.onOverlayClick?.({ e })}
>
<div
Expand All @@ -227,6 +227,7 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
)}
style={getOverlayStyle(overlayInnerStyle)}
onScroll={handleScroll}
// {...getPopupProps()}
>
{content}
{showArrow && (
Expand All @@ -253,7 +254,7 @@ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {

return (
<React.Fragment>
{triggerNode}
{triggerElementIsString ? null : triggerNode}
{overlay}
</React.Fragment>
);
Expand Down
19 changes: 11 additions & 8 deletions packages/components/popup/PopupPlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { createPopper, Instance, Placement, type Options } from '@popperjs/core';
import classNames from 'classnames';
import { isString } from 'lodash-es';
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import { createPopper, type Instance, type Placement, type Options } from '@popperjs/core';
import classNames from 'classnames';
import { isString } from 'lodash-es';

import { getAttach } from '../_util/dom';
import { off, on } from '../_util/listener';
import { render, unmount } from '../_util/react-render';
import type { TNode } from '../common';
import PluginContainer from '../common/PluginContainer';
import ConfigProvider from '../config-provider';
import useConfig from '../hooks/useConfig';
import useDefaultProps from '../hooks/useDefaultProps';
import { popupDefaultProps } from './defaultProps';

import type { TNode } from '../common';
import type { TdPopupProps } from './type';

export interface PopupPluginApi {
Expand All @@ -31,8 +34,6 @@ let overlayInstance: HTMLElement;
let timeout: NodeJS.Timeout;
let triggerEl: HTMLElement;

const componentName = 't-popup';

const triggerType = (triggerProps: string) =>
triggers.reduce(
(map, trigger) => ({
Expand Down Expand Up @@ -62,6 +63,9 @@ const Overlay: React.FC<OverlayProps> = (originalProps) => {
renderCallback,
} = props;

const { classPrefix } = useConfig();
const componentName = `${classPrefix}-popup`;

const [visibleState, setVisibleState] = useState(false);
const popperRef = useRef<HTMLDivElement>(null);
const overlayRef = useRef<HTMLDivElement>(null);
Expand All @@ -80,7 +84,6 @@ const Overlay: React.FC<OverlayProps> = (originalProps) => {
};
};

// useMemo
const hasTrigger = useMemo(() => triggerType(trigger), [trigger]);
const overlayClasses = useMemo(
() => [
Expand All @@ -92,7 +95,7 @@ const Overlay: React.FC<OverlayProps> = (originalProps) => {
},
overlayInnerClassName,
],
[content, overlayInnerClassName, showArrow, disabled],
[componentName, content, showArrow, disabled, overlayInnerClassName],
);

// method
Expand Down
Loading
Loading