diff --git a/src/UniqueProvider/index.tsx b/src/UniqueProvider/index.tsx index 55403f74..f1fbe68d 100644 --- a/src/UniqueProvider/index.tsx +++ b/src/UniqueProvider/index.tsx @@ -45,19 +45,33 @@ const UniqueProvider = ({ children }: UniqueProviderProps) => { // ========================== Register ========================== const [popupId, setPopupId] = React.useState(0); + // Store the isOpen function from the latest show call + const isOpenRef = React.useRef<(() => boolean) | null>(null); + const delayInvoke = useDelay(); - const show = useEvent((showOptions: UniqueShowOptions) => { - delayInvoke(() => { - if (showOptions.id !== options?.id) { - setPopupId((i) => i + 1); - } - trigger(showOptions); - }, showOptions.delay); - }); + const show = useEvent( + (showOptions: UniqueShowOptions, isOpen: () => boolean) => { + // Store the isOpen function for later use in hide + isOpenRef.current = isOpen; + + delayInvoke(() => { + if (showOptions.id !== options?.id) { + setPopupId((i) => i + 1); + } + trigger(showOptions); + }, showOptions.delay); + }, + ); const hide = (delay: number) => { delayInvoke(() => { + // Check if we should still hide by calling the isOpen function + // If isOpen returns true, it means another trigger wants to keep it open + if (isOpenRef.current?.()) { + return; // Don't hide if something else wants it open + } + trigger(false); // Don't clear target, currentNode, options immediately, wait until animation completes }, delay); @@ -106,7 +120,10 @@ const UniqueProvider = ({ children }: UniqueProviderProps) => { false, // alignPoint is false for UniqueProvider ); - return classNames(baseClassName, options.getPopupClassNameFromAlign?.(alignInfo)); + return classNames( + baseClassName, + options.getPopupClassNameFromAlign?.(alignInfo), + ); }, [ alignInfo, options?.getPopupClassNameFromAlign, diff --git a/src/context.ts b/src/context.ts index 54f92103..365c265e 100644 --- a/src/context.ts +++ b/src/context.ts @@ -35,7 +35,7 @@ export interface UniqueShowOptions { } export interface UniqueContextProps { - show: (options: UniqueShowOptions) => void; + show: (options: UniqueShowOptions, isOpen: () => boolean) => void; hide: (delay: number) => void; } diff --git a/src/index.tsx b/src/index.tsx index 945b9733..84390e07 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -309,6 +309,9 @@ export function generateTrigger( } }); + // Support ref + const isOpen = useEvent(() => mergedOpen); + useLayoutEffect(() => { setInternalOpen(popupVisible || false); }, [popupVisible]); @@ -347,9 +350,7 @@ export function generateTrigger( !parentContext ) { if (mergedOpen) { - Promise.resolve().then(() => { - uniqueContext.show(getUniqueOptions(0)); - }); + uniqueContext.show(getUniqueOptions(0), isOpen); } else { uniqueContext.hide(0); } @@ -395,7 +396,7 @@ export function generateTrigger( // If there is a parentContext, don't call uniqueContext methods if (uniqueContext && unique && openUncontrolled && !parentContext) { if (nextOpen) { - uniqueContext.show(getUniqueOptions(delay)); + uniqueContext.show(getUniqueOptions(delay), isOpen); } else { uniqueContext.hide(delay); } diff --git a/tests/unique.test.tsx b/tests/unique.test.tsx index 0174becf..f6fae182 100644 --- a/tests/unique.test.tsx +++ b/tests/unique.test.tsx @@ -151,8 +151,5 @@ describe('Trigger.Unique', () => { // Check that custom className from getPopupClassNameFromAlign is applied expect(popup.className).toContain('custom-align'); expect(popup.className).toContain('rc-trigger-popup-unique-controlled'); - - // The base placement className might not be available immediately due to async alignment - // but the custom className should always be applied }); });