Skip to content

Commit 4f6a2e3

Browse files
authored
chore(a11y): support middle mouse button to close (#860)
* chore(a11y): support middle mouse button to close * chore: add unit test
1 parent 0be588c commit 4f6a2e3

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
@@ -311,6 +311,38 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
311311
setFocusKey(newKey);
312312
};
313313

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

@@ -377,25 +409,7 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
377409
// Backspace
378410
case 'Backspace':
379411
case 'Delete': {
380-
const removeIndex = enabledTabs.indexOf(focusKey);
381-
const removeTab = tabs.find(tab => tab.key === focusKey);
382-
const removable = getRemovable(
383-
removeTab?.closable,
384-
removeTab?.closeIcon,
385-
editable,
386-
removeTab?.disabled,
387-
);
388-
if (removable) {
389-
e.preventDefault();
390-
e.stopPropagation();
391-
editable.onEdit('remove', { key: focusKey, event: e });
392-
// when remove last tab, focus previous tab
393-
if (removeIndex === enabledTabs.length - 1) {
394-
onOffset(-1);
395-
} else {
396-
onOffset(1);
397-
}
398-
}
412+
handleRemoveTab(focusKey, e);
399413
break;
400414
}
401415
}
@@ -449,9 +463,7 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
449463
onBlur={() => {
450464
setFocusKey(undefined);
451465
}}
452-
onMouseDown={() => {
453-
setIsMouse(true);
454-
}}
466+
onMouseDown={e => handleMouseDown(key, e)}
455467
onMouseUp={() => {
456468
setIsMouse(false);
457469
}}

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';
@@ -280,4 +280,44 @@ describe('Tabs.Accessibility', () => {
280280
const firstTab = getByRole('tab', { name: /Tab1/i });
281281
expect(firstTab).toHaveFocus();
282282
});
283+
284+
it('should close tab on middle mouse button click', async () => {
285+
const Demo = () => {
286+
const [items, setItems] = React.useState(
287+
Array.from({ length: 3 }, (_, i) => ({
288+
key: `${i + 1}`,
289+
label: `Tab${i + 1}`,
290+
children: `Content ${i + 1}`,
291+
})),
292+
);
293+
return (
294+
<Tabs
295+
defaultActiveKey="1"
296+
items={items}
297+
editable={{
298+
onEdit: (type, { key }) => {
299+
if (type === 'remove') {
300+
setItems(prevItems => prevItems.filter(item => item.key !== key));
301+
}
302+
},
303+
}}
304+
/>
305+
);
306+
};
307+
308+
const { queryByRole } = render(<Demo />);
309+
310+
// Get the second tab
311+
const secondTab = queryByRole('tab', { name: /Tab2/i });
312+
expect(secondTab).toBeInTheDocument();
313+
314+
// Simulate middle mouse button click (button index 1)
315+
fireEvent.mouseDown(secondTab, { button: 1 });
316+
317+
expect(queryByRole('tab', { name: /Tab2/i })).toBeNull();
318+
319+
// First and third tabs should still be there
320+
expect(queryByRole('tab', { name: /Tab1/i })).toBeInTheDocument();
321+
expect(queryByRole('tab', { name: /Tab3/i })).toBeInTheDocument();
322+
});
283323
});

0 commit comments

Comments
 (0)