diff --git a/src/lib/__tests__/modal-factory.test.tsx b/src/lib/__tests__/modal-factory.test.tsx
new file mode 100644
index 0000000..2fdaa17
--- /dev/null
+++ b/src/lib/__tests__/modal-factory.test.tsx
@@ -0,0 +1,75 @@
+import React, { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { BaseStyles, ThemeProvider } from '@primer/react'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+vi.mock('@/lib/debug-logger', () => ({
+ logger: { log: () => {}, warn: () => {}, error: () => {}, info: () => {} },
+ initDebugLogger: async () => {},
+}))
+vi.mock('@/lib/tippy-utils', () => ({ ensureTippyCss: () => {} }))
+vi.mock('@/lib/toast-store', () => ({
+ toastStore: { show: () => {} },
+}))
+
+import { createModal } from '@/lib/modal-factory'
+;(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+
+const TestModal = createModal<{ onConfirm: () => void }>({
+ name: 'Test',
+ renderContent: (_props, _helpers) => ,
+ onSubmit: async () => {},
+})
+
+let mounted: Array<{ container: HTMLDivElement; root: Root }> = []
+
+function render(node: React.ReactElement) {
+ const container = document.createElement('div')
+ document.body.appendChild(container)
+ const root = createRoot(container)
+ act(() => {
+ root.render(node)
+ })
+ mounted.push({ container, root })
+ return container
+}
+
+beforeEach(() => {
+ mounted = []
+})
+
+afterEach(() => {
+ for (const { container, root } of mounted) {
+ act(() => root.unmount())
+ container.remove()
+ }
+})
+
+describe('createModal keyboard propagation', () => {
+ it('stops keydown/keyup propagation to document when typing inside the modal panel', () => {
+ const container = render(
+
+
+ {}} onClose={() => {}} />
+
+ ,
+ )
+
+ const input = container.querySelector('[data-testid="modal-input"]') as HTMLInputElement
+ expect(input).not.toBeNull()
+
+ const spy = vi.fn()
+ document.addEventListener('keydown', spy)
+ document.addEventListener('keyup', spy)
+
+ act(() => {
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true }))
+ input.dispatchEvent(new KeyboardEvent('keyup', { key: 'a', bubbles: true }))
+ })
+
+ document.removeEventListener('keydown', spy)
+ document.removeEventListener('keyup', spy)
+
+ expect(spy).not.toHaveBeenCalled()
+ })
+})
diff --git a/src/lib/modal-factory.tsx b/src/lib/modal-factory.tsx
index b87088a..04f59f7 100644
--- a/src/lib/modal-factory.tsx
+++ b/src/lib/modal-factory.tsx
@@ -111,7 +111,12 @@ export function createModal(opts: CreateModalOptions): React.FC
- e.stopPropagation()}>
+ e.stopPropagation()}
+ onKeyDown={(e: React.KeyboardEvent) => { if (e.key !== 'Escape') e.stopPropagation() }}
+ onKeyUp={(e: React.KeyboardEvent) => e.stopPropagation()}
+ >
{error && (
diff --git a/src/ui/__tests__/bulk-flyout.test.tsx b/src/ui/__tests__/bulk-flyout.test.tsx
index eca45a1..430268e 100644
--- a/src/ui/__tests__/bulk-flyout.test.tsx
+++ b/src/ui/__tests__/bulk-flyout.test.tsx
@@ -193,6 +193,43 @@ describe(' simple mode', () => {
expect(style!.textContent).toContain('prefers-reduced-motion: no-preference')
expect(style!.textContent).toContain('@keyframes rgp-flyout-in')
})
+
+ it('stops keydown/keyup propagation to document when typing inside', () => {
+ const anchorRef = { current: document.createElement('button') }
+ document.body.appendChild(anchorRef.current)
+ const { find } = render(
+
+
+ }
+ open={true}
+ onClose={() => {}}
+ title="Test"
+ >
+
+
+
+ ,
+ )
+ const input = find('[data-testid="rgp-test-input"]') as HTMLInputElement
+ expect(input).not.toBeNull()
+
+ const spy = vi.fn()
+ document.addEventListener('keydown', spy)
+ document.addEventListener('keyup', spy)
+
+ act(() => {
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true }))
+ input.dispatchEvent(new KeyboardEvent('keyup', { key: 'a', bubbles: true }))
+ })
+
+ document.removeEventListener('keydown', spy)
+ document.removeEventListener('keyup', spy)
+ anchorRef.current.remove()
+
+ expect(spy).not.toHaveBeenCalled()
+ })
})
describe(' apply/cancel footer', () => {
diff --git a/src/ui/bulk-flyout.tsx b/src/ui/bulk-flyout.tsx
index b3f1167..2c88932 100644
--- a/src/ui/bulk-flyout.tsx
+++ b/src/ui/bulk-flyout.tsx
@@ -206,6 +206,8 @@ export function BulkFlyout(props: BulkFlyoutProps) {
minWidth: width,
}}
data-testid="rgp-bulk-flyout"
+ onKeyDown={(e: React.KeyboardEvent) => e.stopPropagation()}
+ onKeyUp={(e: React.KeyboardEvent) => e.stopPropagation()}
>
{header}