From 40403dde867184faae48494a96f29cef6a186c06 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Thu, 5 Nov 2026 22:22:26 -0400 Subject: [PATCH 1/2] fix(modal): keep the modal open when the clicked element is removed by a re-render --- src/components/overlay/modal/Modal.tsx | 5 ++- .../overlay/modal/__test__/Modal.test.tsx | 41 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/components/overlay/modal/Modal.tsx b/src/components/overlay/modal/Modal.tsx index d5d9921..8a05974 100644 --- a/src/components/overlay/modal/Modal.tsx +++ b/src/components/overlay/modal/Modal.tsx @@ -104,7 +104,10 @@ const Modal = ({ useEffect(() => { const handleClickOutside = (event: MouseEvent) => { - if (modalRef.current && !modalRef.current.contains(event.target as Node) && !preventClosing) { + const modal = modalRef.current; + const isClickOutsideModal = !!modal && !event.composedPath().includes(modal); + + if (isClickOutsideModal && !preventClosing) { event.preventDefault(); closeLastOpenModal(); } diff --git a/src/components/overlay/modal/__test__/Modal.test.tsx b/src/components/overlay/modal/__test__/Modal.test.tsx index de90eac..c54b97d 100644 --- a/src/components/overlay/modal/__test__/Modal.test.tsx +++ b/src/components/overlay/modal/__test__/Modal.test.tsx @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { describe, it, afterEach, expect, vi } from 'vitest'; +import { flushSync } from 'react-dom'; +import { describe, it, afterEach, beforeEach, expect, vi } from 'vitest'; import { Modal } from '../'; +import { Input } from '../../../input/input'; import { afterAll, beforeAll } from 'vitest'; describe('Modal Component', () => { @@ -101,6 +103,43 @@ describe('Modal Component', () => { expect(onCloseMock).toHaveBeenCalledTimes(1); }); + describe('when the clicked element is detached by a re-render before the document listener runs', () => { + const flushPendingRender = () => flushSync(() => {}); + + beforeEach(() => { + document.body.addEventListener('mousedown', flushPendingRender); + }); + + afterEach(() => { + document.body.removeEventListener('mousedown', flushPendingRender); + }); + + it('should not call onClose when toggling the password visibility of an input inside the modal', () => { + render( + + + , + ); + const passwordInput = screen.getByPlaceholderText('Password'); + fireEvent.focus(passwordInput); + const eyeIcon = screen.getByRole('button').querySelector('svg') as SVGElement; + + fireEvent.mouseDown(eyeIcon); + + expect(eyeIcon.isConnected).toBe(false); + expect(passwordInput).toHaveAttribute('type', 'text'); + expect(onCloseMock).not.toHaveBeenCalled(); + }); + + it('should still call onClose when the mousedown happens outside the modal', () => { + renderModal(); + + fireEvent.mouseDown(document.body); + + expect(onCloseMock).toHaveBeenCalledTimes(1); + }); + }); + it('should not call onClose when click outside if preventClosing is true', () => { render( From da347412cf36a4f05eeb28ca5200a82b229a4cc1 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Thu, 5 Nov 2026 22:22:29 -0400 Subject: [PATCH 2/2] chore: update package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b92c49f..bb69a5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@internxt/ui", - "version": "0.1.27", + "version": "0.1.28", "description": "Library of Internxt components", "repository": { "type": "git",