From f56ae433ed7b37cb3b625109b818f3f62f7564c1 Mon Sep 17 00:00:00 2001 From: Ferit Date: Fri, 23 Oct 2026 17:11:07 +0300 Subject: [PATCH 1/4] fix: support Space key to toggle panel and prevent default scroll --- src/Panel.tsx | 11 +++++++++++ tests/index.spec.tsx | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/Panel.tsx b/src/Panel.tsx index c489ee2..e93f261 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -37,8 +37,19 @@ const CollapsePanel = React.forwardRef((prop onItemClick?.(panelKey); }, onKeyDown: (e: React.KeyboardEvent) => { + if (disabled) { + return; + } if (e.key === 'Enter' || e.keyCode === KeyCode.ENTER || e.which === KeyCode.ENTER) { onItemClick?.(panelKey); + } else if ( + e.key === ' ' || + e.key === 'Spacebar' || + e.keyCode === KeyCode.SPACE || + e.which === KeyCode.SPACE + ) { + e.preventDefault(); + onItemClick?.(panelKey); } }, role: accordion ? 'tab' : 'button', diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index 8197a30..c208b6b 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -445,6 +445,47 @@ describe('collapse', () => { ); }); + it('should toggle panel and prevent default when press space', () => { + const spaceKeyEvent = { + key: ' ', + keyCode: KeyCode.SPACE, + which: KeyCode.SPACE, + charCode: KeyCode.SPACE, + }; + + const { container } = render( + + + first + + + second + + + third + + , + ); + + const headers = container.querySelectorAll('.rc-collapse-header'); + fireEvent.keyDown(headers[2], spaceKeyEvent); + jest.runAllTimers(); + expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); + + const header = headers[0]; + const defaultPrevented = !fireEvent.keyDown(header, spaceKeyEvent); + jest.runAllTimers(); + + expect(defaultPrevented).toBeTruthy(); + expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(1); + expect(container.querySelector('.rc-collapse-panel')).toHaveClass('rc-collapse-panel-active'); + + fireEvent.keyDown(header, spaceKeyEvent); + jest.runAllTimers(); + + expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); + }); + describe('wrapped in Fragment', () => { const expandIcon = () => test{'>'}; const element = ( From ac167accaad4c0aaf672be1c5ff0637674845d34 Mon Sep 17 00:00:00 2001 From: Ferit Date: Tue, 27 Oct 2026 01:24:28 +0200 Subject: [PATCH 2/4] fix: ignore keyboard events from nested header controls --- src/Panel.tsx | 2 +- tests/index.spec.tsx | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Panel.tsx b/src/Panel.tsx index e93f261..52e4d6c 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -37,7 +37,7 @@ const CollapsePanel = React.forwardRef((prop onItemClick?.(panelKey); }, onKeyDown: (e: React.KeyboardEvent) => { - if (disabled) { + if (disabled || e.target !== e.currentTarget) { return; } if (e.key === 'Enter' || e.keyCode === KeyCode.ENTER || e.which === KeyCode.ENTER) { diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index c208b6b..f0544d7 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -486,6 +486,18 @@ describe('collapse', () => { expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); }); + it('should leave space on a nested extra button to the button', () => { + const { getByRole, container } = render( + Action, children: 'Content' }]} + />, + ); + + expect(fireEvent.keyDown(getByRole('button', { name: 'Action' }), { key: ' ' })).toBe(true); + jest.runAllTimers(); + expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); + }); + describe('wrapped in Fragment', () => { const expandIcon = () => test{'>'}; const element = ( From cc404157405360c4cf3034aa85f9167dbaeb94bc Mon Sep 17 00:00:00 2001 From: Ferit Date: Wed, 28 Oct 2026 14:50:05 +0200 Subject: [PATCH 3/4] Simplify normalized space key handling --- src/Panel.tsx | 7 +------ tests/index.spec.tsx | 14 +++++++++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Panel.tsx b/src/Panel.tsx index 643eb8f..d7828e3 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -44,12 +44,7 @@ const CollapsePanel = React.forwardRef((prop } if (e.key === 'Enter' || e.keyCode === KeyCode.ENTER || e.which === KeyCode.ENTER) { onItemClick?.(panelKey); - } else if ( - e.key === ' ' || - e.key === 'Spacebar' || - e.keyCode === KeyCode.SPACE || - e.which === KeyCode.SPACE - ) { + } else if (e.key === ' ') { e.preventDefault(); onItemClick?.(panelKey); } diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index bb3586e..b5feb40 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -468,7 +468,7 @@ describe('collapse', () => { ); const headers = container.querySelectorAll('.rc-collapse-header'); - fireEvent.keyDown(headers[2], spaceKeyEvent); + expect(fireEvent.keyDown(headers[2], spaceKeyEvent)).toBe(true); jest.runAllTimers(); expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); @@ -486,6 +486,18 @@ describe('collapse', () => { expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); }); + it.each([{ key: 'Spacebar' }, { key: '', keyCode: KeyCode.SPACE }])( + 'should toggle for a normalized legacy space event: %o', + event => { + const { container } = render( + , + ); + expect(fireEvent.keyDown(container.querySelector('.rc-collapse-header')!, event)).toBe(false); + jest.runAllTimers(); + expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(1); + }, + ); + it('should leave space on a nested extra button to the button', () => { const { getByRole, container } = render( Date: Wed, 28 Oct 2026 16:43:28 +0200 Subject: [PATCH 4/4] Test nested button Enter handling --- tests/index.spec.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index b5feb40..a0f9013 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -508,6 +508,10 @@ describe('collapse', () => { expect(fireEvent.keyDown(getByRole('button', { name: 'Action' }), { key: ' ' })).toBe(true); jest.runAllTimers(); expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); + + expect(fireEvent.keyDown(getByRole('button', { name: 'Action' }), { key: 'Enter' })).toBe(true); + jest.runAllTimers(); + expect(container.querySelectorAll('.rc-collapse-panel-active')).toHaveLength(0); }); it('should link the header to its panel with aria-controls', () => {