diff --git a/react-responsive-modal/__tests__/index.test.tsx b/react-responsive-modal/__tests__/index.test.tsx index a8695f30..a6a77881 100644 --- a/react-responsive-modal/__tests__/index.test.tsx +++ b/react-responsive-modal/__tests__/index.test.tsx @@ -746,6 +746,49 @@ describe('modal', () => { fireEvent.animationEnd(getByTestId('modal')); expect(onAnimationEnd).toHaveBeenCalledTimes(1); }); + + it('should pass the animation event to onAnimationEnd', async () => { + const onAnimationEnd = vitest.fn(); + const { getByTestId } = render( + null} onAnimationEnd={onAnimationEnd}> +
modal content
+
, + ); + + const modal = getByTestId('modal'); + fireEvent.animationEnd(modal); + expect(onAnimationEnd).toHaveBeenCalledTimes(1); + expect(onAnimationEnd.mock.calls[0][0].target).toBe(modal); + }); + + it('should not be called when a child animation ends', async () => { + const onAnimationEnd = vitest.fn(); + const { getByTestId } = render( + null} onAnimationEnd={onAnimationEnd}> +
modal content
+
, + ); + + fireEvent.animationEnd(getByTestId('child')); + expect(onAnimationEnd).not.toHaveBeenCalled(); + }); + + it('should not hide the modal when a child animation ends while closing', async () => { + const { getByTestId, queryByTestId, rerender } = render( + null} animationDuration={0.01}> +
modal content
+
, + ); + + rerender( + null} animationDuration={0.01}> +
modal content
+
, + ); + + fireEvent.animationEnd(getByTestId('child')); + expect(queryByTestId('modal')).toBeTruthy(); + }); }); describe('prop: containerId', () => { diff --git a/react-responsive-modal/src/index.tsx b/react-responsive-modal/src/index.tsx index 78465784..15d761eb 100644 --- a/react-responsive-modal/src/index.tsx +++ b/react-responsive-modal/src/index.tsx @@ -164,7 +164,7 @@ export interface ModalProps { /** * Callback fired when the Modal has exited and the animation is finished. */ - onAnimationEnd?: () => void; + onAnimationEnd?: (event: React.AnimationEvent) => void; children?: React.ReactNode; } @@ -313,12 +313,19 @@ export const Modal = React.forwardRef( refShouldClose.current = false; }; - const handleAnimationEnd = () => { + const handleAnimationEnd = ( + event: React.AnimationEvent, + ) => { + // Ignore animations bubbling up from elements inside the modal + if (event.target !== event.currentTarget) { + return; + } + if (!open) { setShowPortal(false); } - onAnimationEnd?.(); + onAnimationEnd?.(event); }; const containerModal = container || refContainer.current; diff --git a/website/src/docs/index.mdx b/website/src/docs/index.mdx index 055d96e2..52065c70 100644 --- a/website/src/docs/index.mdx +++ b/website/src/docs/index.mdx @@ -219,7 +219,7 @@ By default, the Modal will be rendered at the end of the html body tag. If you w | **onClose\*** | `() => void` | | Callback fired when the Modal is requested to be closed by a click on the overlay or when user press esc key. | | **onEscKeyDown\*** | `(event: KeyboardEvent) => void` | | Callback fired when the escape key is pressed. | | **onOverlayClick\*** | `(event: React.MouseEvent) => void` | | Callback fired when the overlay is clicked. | -| **onAnimationEnd\*** | `() => void` | | Callback fired when the Modal has exited and the animation is finished. | +| **onAnimationEnd\*** | `(event: React.AnimationEvent) => void` | | Callback fired when the Modal has exited and the animation is finished. | ## License