From 8197c52f075011fbb87ce5c72d1b074162818a3a Mon Sep 17 00:00:00 2001 From: Fathiraz Arthuro Date: Wed, 3 Jun 2026 01:13:34 +0700 Subject: [PATCH 1/2] fix(ui): isolate flyout and modal keyboard input Stop keydown/keyup propagation on bulk flyout and modal panels so typing in extension fields does not reach GitHub document handlers (e.g. project search). Add regression tests and quiet INVALID_ANNOTATION rolldown warnings in the WXT build. --- src/lib/__tests__/modal-factory.test.tsx | 75 ++++++++++++++++++++++++ src/lib/modal-factory.tsx | 7 ++- src/ui/__tests__/bulk-flyout.test.tsx | 37 ++++++++++++ src/ui/bulk-flyout.tsx | 2 + wxt.config.ts | 8 ++- 5 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/lib/__tests__/modal-factory.test.tsx 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..81ce761 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) => 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} Date: Wed, 3 Jun 2026 01:21:56 +0700 Subject: [PATCH 2/2] Update src/lib/modal-factory.tsx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- src/lib/modal-factory.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/modal-factory.tsx b/src/lib/modal-factory.tsx index 81ce761..04f59f7 100644 --- a/src/lib/modal-factory.tsx +++ b/src/lib/modal-factory.tsx @@ -114,7 +114,7 @@ export function createModal(opts: CreateModalOptions): React.FC e.stopPropagation()} - onKeyDown={(e: React.KeyboardEvent) => e.stopPropagation()} + onKeyDown={(e: React.KeyboardEvent) => { if (e.key !== 'Escape') e.stopPropagation() }} onKeyUp={(e: React.KeyboardEvent) => e.stopPropagation()} >