Skip to content

Commit d715240

Browse files
authored
chore(a11y): support middle mouse button to close (#860) (#870)
* chore(a11y): support middle mouse button to close * chore: add unit test (cherry picked from commit 4f6a2e3) # Conflicts: # tests/accessibility.test.tsx
1 parent ba47b30 commit d715240

File tree

2 files changed

+75
-23
lines changed

2 files changed

+75
-23
lines changed

src/TabNavList/index.tsx

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,38 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
315315
setFocusKey(newKey);
316316
};
317317

318+
const handleRemoveTab = (removalTabKey: string, e: React.MouseEvent | React.KeyboardEvent) => {
319+
const removeIndex = enabledTabs.indexOf(removalTabKey);
320+
const removeTab = tabs.find(tab => tab.key === removalTabKey);
321+
const removable = getRemovable(
322+
removeTab?.closable,
323+
removeTab?.closeIcon,
324+
editable,
325+
removeTab?.disabled,
326+
);
327+
328+
if (removable) {
329+
e.preventDefault();
330+
e.stopPropagation();
331+
editable.onEdit('remove', { key: removalTabKey, event: e });
332+
333+
// when remove last tab, focus previous tab
334+
if (removeIndex === enabledTabs.length - 1) {
335+
onOffset(-1);
336+
} else {
337+
onOffset(1);
338+
}
339+
}
340+
};
341+
342+
const handleMouseDown = (key: string, e: React.MouseEvent) => {
343+
setIsMouse(true);
344+
// Middle mouse button
345+
if (e.button === 1) {
346+
handleRemoveTab(key, e);
347+
}
348+
};
349+
318350
const handleKeyDown = (e: React.KeyboardEvent) => {
319351
const { code } = e;
320352

@@ -381,25 +413,7 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
381413
// Backspace
382414
case 'Backspace':
383415
case 'Delete': {
384-
const removeIndex = enabledTabs.indexOf(focusKey);
385-
const removeTab = tabs.find(tab => tab.key === focusKey);
386-
const removable = getRemovable(
387-
removeTab?.closable,
388-
removeTab?.closeIcon,
389-
editable,
390-
removeTab?.disabled,
391-
);
392-
if (removable) {
393-
e.preventDefault();
394-
e.stopPropagation();
395-
editable.onEdit('remove', { key: focusKey, event: e });
396-
// when remove last tab, focus previous tab
397-
if (removeIndex === enabledTabs.length - 1) {
398-
onOffset(-1);
399-
} else {
400-
onOffset(1);
401-
}
402-
}
416+
handleRemoveTab(focusKey, e);
403417
break;
404418
}
405419
}
@@ -454,9 +468,7 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
454468
onBlur={() => {
455469
setFocusKey(undefined);
456470
}}
457-
onMouseDown={() => {
458-
setIsMouse(true);
459-
}}
471+
onMouseDown={e => handleMouseDown(key, e)}
460472
onMouseUp={() => {
461473
setIsMouse(false);
462474
}}

tests/accessibility.test.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render } from '@testing-library/react';
1+
import { render, fireEvent } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import React from 'react';
44
import type { TabsProps } from '../src';
@@ -292,4 +292,44 @@ describe('Tabs.Accessibility', () => {
292292
await user.tab();
293293
expect(tabPanel).not.toHaveFocus();
294294
});
295+
296+
it('should close tab on middle mouse button click', async () => {
297+
const Demo = () => {
298+
const [items, setItems] = React.useState(
299+
Array.from({ length: 3 }, (_, i) => ({
300+
key: `${i + 1}`,
301+
label: `Tab${i + 1}`,
302+
children: `Content ${i + 1}`,
303+
})),
304+
);
305+
return (
306+
<Tabs
307+
defaultActiveKey="1"
308+
items={items}
309+
editable={{
310+
onEdit: (type, { key }) => {
311+
if (type === 'remove') {
312+
setItems(prevItems => prevItems.filter(item => item.key !== key));
313+
}
314+
},
315+
}}
316+
/>
317+
);
318+
};
319+
320+
const { queryByRole } = render(<Demo />);
321+
322+
// Get the second tab
323+
const secondTab = queryByRole('tab', { name: /Tab2/i });
324+
expect(secondTab).toBeInTheDocument();
325+
326+
// Simulate middle mouse button click (button index 1)
327+
fireEvent.mouseDown(secondTab, { button: 1 });
328+
329+
expect(queryByRole('tab', { name: /Tab2/i })).toBeNull();
330+
331+
// First and third tabs should still be there
332+
expect(queryByRole('tab', { name: /Tab1/i })).toBeInTheDocument();
333+
expect(queryByRole('tab', { name: /Tab3/i })).toBeInTheDocument();
334+
});
295335
});

0 commit comments

Comments
 (0)