diff --git a/docs/examples/body-overflow.tsx b/docs/examples/body-overflow.tsx index b6ab5182..744e86a3 100644 --- a/docs/examples/body-overflow.tsx +++ b/docs/examples/body-overflow.tsx @@ -80,10 +80,11 @@ export default () => { htmlRegion: 'scroll', }} > - { display: 'inline-block', }} > - Target - + Button Target + { - if (mergedOpen !== nextOpen) { - setMergedOpen(nextOpen); - onPopupVisibleChange?.(nextOpen); - } + // Enter or Pointer will both trigger open state change + // We only need take one to avoid duplicated change event trigger + flushSync(() => { + if (mergedOpen !== nextOpen) { + setMergedOpen(nextOpen); + onPopupVisibleChange?.(nextOpen); + } + }); }); // Trigger for delay @@ -354,7 +359,9 @@ export function generateTrigger( 0, 0, ]); - const setMousePosByEvent = (event: React.MouseEvent) => { + const setMousePosByEvent = ( + event: Pick, + ) => { setMousePos([event.clientX, event.clientY]); }; @@ -463,13 +470,15 @@ export function generateTrigger( hideAction, ); - // Util wrapper for trigger action - const wrapperAction = ( + /** + * Util wrapper for trigger action + */ + function wrapperAction( eventName: string, nextOpen: boolean, delay?: number, - preEvent?: (event: any) => void, - ) => { + preEvent?: (event: Event) => void, + ) { cloneProps[eventName] = (event: any, ...args: any[]) => { preEvent?.(event); triggerOpen(nextOpen, delay); @@ -477,7 +486,7 @@ export function generateTrigger( // Pass to origin originChildProps[eventName]?.(event, ...args); }; - }; + } // ======================= Action: Click ======================== const clickToShow = showActions.has('click'); @@ -521,9 +530,23 @@ export function generateTrigger( let onPopupMouseLeave: VoidFunction; if (hoverToShow) { - wrapperAction('onMouseEnter', true, mouseEnterDelay, (event) => { - setMousePosByEvent(event); - }); + // Compatible with old browser which not support pointer event + wrapperAction( + 'onMouseEnter', + true, + mouseEnterDelay, + (event) => { + setMousePosByEvent(event); + }, + ); + wrapperAction( + 'onPointerEnter', + true, + mouseEnterDelay, + (event) => { + setMousePosByEvent(event); + }, + ); onPopupMouseEnter = () => { // Only trigger re-open when popup is visible if (mergedOpen || inMotion) { @@ -542,6 +565,7 @@ export function generateTrigger( if (hoverToHide) { wrapperAction('onMouseLeave', false, mouseLeaveDelay); + wrapperAction('onPointerLeave', false, mouseLeaveDelay); onPopupMouseLeave = () => { triggerOpen(false, mouseLeaveDelay); }; diff --git a/tests/basic.test.jsx b/tests/basic.test.jsx index 936a1407..cbee245c 100644 --- a/tests/basic.test.jsx +++ b/tests/basic.test.jsx @@ -131,22 +131,42 @@ describe('Trigger.Basic', () => { expect(isPopupHidden()).toBeTruthy(); }); - it('hover works', () => { - const { container } = render( - trigger} - > -
click
-
, - ); + describe('hover works', () => { + it('mouse event', () => { + const { container } = render( + trigger} + > +
click
+
, + ); - trigger(container, '.target', 'mouseEnter'); - expect(isPopupHidden()).toBeFalsy(); + trigger(container, '.target', 'mouseEnter'); + expect(isPopupHidden()).toBeFalsy(); - trigger(container, '.target', 'mouseLeave'); - expect(isPopupHidden()).toBeTruthy(); + 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(); + }); }); it('contextMenu works', () => {