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 }));
+ });
+
});
});