diff --git a/src/useEscKeyDown.ts b/src/useEscKeyDown.ts index 90e31c6..31b2cb9 100644 --- a/src/useEscKeyDown.ts +++ b/src/useEscKeyDown.ts @@ -24,6 +24,10 @@ export default function useEscKeyDown(open: boolean, onEsc?: EscCallback) { }, [open, id]); useEffect(() => { + if (open && !stack.includes(id)) { + stack.push(id); + } + if (!open) { return; } diff --git a/tests/index.test.tsx b/tests/index.test.tsx index f95eeba..db50b20 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -381,5 +381,49 @@ describe('Portal', () => { unmount(); expect(stack).toHaveLength(0); }); + + it('onEsc should treat first mounted portal as top in StrictMode', () => { + const onEsc = jest.fn(); + + const Demo = ({ visible }: { visible: boolean }) => + visible ? ( + +
+ + ) : null; + + render(, { wrapper: React.StrictMode }); + + expect(stack).toHaveLength(1); + + fireEvent.keyDown(window, { key: 'Escape' }); + + expect(onEsc).toHaveBeenCalledWith(expect.objectContaining({ top: true })); + }); + + it('nested portals should trigger in correct order', () => { + const onEsc = jest.fn(); + const onEsc2 = jest.fn(); + const onEsc3 = jest.fn(); + + render( + +
+ +
+ +
+ + + + ); + + fireEvent.keyDown(window, { key: 'Escape' }); + + expect(onEsc).toHaveBeenCalledWith(expect.objectContaining({ top: false })); + expect(onEsc2).toHaveBeenCalledWith(expect.objectContaining({ top: false })); + expect(onEsc3).toHaveBeenCalledWith(expect.objectContaining({ top: true })); + }); + }); });