Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
692588c
♿️ access: add accesibility attributes to elements
iammola Aug 28, 2024
23a7bbe
✅ test: add tests
iammola Aug 28, 2024
3c4e7e8
🔀 merge: update from `upstream/master`
iammola Sep 2, 2024
0761df4
Merge branch 'mathuo:master' into tab-accessibility
iammola Oct 1, 2024
e380f13
🔀 merge: update from upstream/master
iammola Oct 29, 2024
658e56d
Merge branch 'mathuo:master' into tab-accessibility
iammola Nov 11, 2024
160d97b
Merge remote-tracking branch 'upstream/master' into tab-accessibility
iammola Jan 6, 2025
3f9ef60
Merge remote-tracking branch 'upstream/master' into tab-accessibility
iammola Feb 5, 2025
86d1ea7
Merge remote-tracking branch 'upstream/master' into tab-accessibility
iammola Feb 19, 2025
3785771
🔀 merge: update from master
iammola Apr 2, 2025
9d9a928
♿️ access: remove id options, focus tab on select, close button
iammola Apr 2, 2025
cef7816
♿️ access: allow Enter/Space to trigger tab close
iammola Apr 4, 2025
000fa47
♿️ access: add outline to tab on focus
iammola Apr 4, 2025
5c78f2b
♿️ access: allow Enter/Space on tab to activate it
iammola Apr 4, 2025
8606692
♿️ access: add instructions to tab-list, and orientation
iammola Apr 4, 2025
57a1624
💬 text: verbiage
iammola Apr 4, 2025
b5d28ce
🐛 fix: use focused tab as reference
iammola Apr 14, 2025
b021b9a
🔀 merge: update from master
iammola Apr 14, 2025
eead24c
Merge branch 'mathuo:master' into tab-accessibility
iammola May 21, 2025
9c04db8
Merge pull request #701 from iammola/tab-accessibility
mathuo Jun 3, 2025
182a2ae
chore: standardize api
mathuo Jun 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('groupPanelApi', () => {
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -83,6 +84,7 @@ describe('groupPanelApi', () => {
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('tabsContainer', () => {
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -71,6 +72,7 @@ describe('tabsContainer', () => {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -141,6 +143,7 @@ describe('tabsContainer', () => {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -205,6 +208,7 @@ describe('tabsContainer', () => {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -269,6 +273,7 @@ describe('tabsContainer', () => {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -338,6 +343,7 @@ describe('tabsContainer', () => {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -403,6 +409,7 @@ describe('tabsContainer', () => {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
Expand Down Expand Up @@ -468,6 +475,7 @@ describe('tabsContainer', () => {
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
doSetGroupActive: jest.fn(),
Expand Down Expand Up @@ -525,6 +533,7 @@ describe('tabsContainer', () => {
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
doSetGroupActive: jest.fn(),
Expand Down Expand Up @@ -577,6 +586,7 @@ describe('tabsContainer', () => {
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
Expand Down Expand Up @@ -634,6 +644,7 @@ describe('tabsContainer', () => {
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
Expand Down Expand Up @@ -702,6 +713,7 @@ describe('tabsContainer', () => {
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
Expand Down Expand Up @@ -770,6 +782,7 @@ describe('tabsContainer', () => {
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6917,4 +6917,91 @@ describe('dockviewComponent', () => {
dockview.layout(1000, 1000);
});
});

test('that arrow keys should activate appropriate tabs', () => {
dockview.layout(500, 1000);

dockview.addPanel({
id: 'panel1',
component: 'default',
});

dockview.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: 'panel1', direction: 'within' },
});

dockview.addPanel({
id: 'panel3',
component: 'default',
});

dockview.addPanel({
id: 'panel4',
component: 'default',
position: { referencePanel: 'panel3', direction: 'below' },
});

const panel1 = dockview.getGroupPanel('panel1')!;
const panel2 = dockview.getGroupPanel('panel2')!;
const panel3 = dockview.getGroupPanel('panel3')!;
const panel4 = dockview.getGroupPanel('panel4')!;

panel1.api.setActive();

expect(panel1.api.isActive).toBeTruthy();
expect(panel2.api.isActive).toBeFalsy();
expect(panel3.api.isActive).toBeFalsy();
expect(panel4.api.isActive).toBeFalsy();

const tabsContainer = (panel: IDockviewPanel) =>
panel.api.group.element.querySelector('.tabs-container')!;

const event = new KeyboardEvent('keydown', { key: 'ArrowRight' });

fireEvent(tabsContainer(panel1), event);
expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeTruthy();
expect(panel3.api.isActive).toBeFalsy();
expect(panel4.api.isActive).toBeFalsy();

fireEvent(tabsContainer(panel1), event);
expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeFalsy();
expect(panel3.api.isActive).toBeTruthy();
expect(panel4.api.isActive).toBeFalsy();

const event2 = new KeyboardEvent('keydown', { key: 'ArrowLeft' });

fireEvent(tabsContainer(panel1), event2);
expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeTruthy();
expect(panel3.api.isActive).toBeFalsy();
expect(panel4.api.isActive).toBeFalsy();

fireEvent(tabsContainer(panel1), event2);
expect(panel1.api.isActive).toBeTruthy();
expect(panel2.api.isActive).toBeFalsy();
expect(panel3.api.isActive).toBeFalsy();
expect(panel4.api.isActive).toBeFalsy();

panel4.api.setActive();
expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeFalsy();
expect(panel3.api.isActive).toBeFalsy();
expect(panel4.api.isActive).toBeTruthy();

fireEvent(tabsContainer(panel4), event2);
expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeFalsy();
expect(panel3.api.isActive).toBeFalsy();
expect(panel4.api.isActive).toBeTruthy();

fireEvent(tabsContainer(panel4), event);
expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeFalsy();
expect(panel3.api.isActive).toBeFalsy();
expect(panel4.api.isActive).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ export class TestPanel implements IDockviewPanel {
private _group: DockviewGroupPanel | undefined;
private _params: IGroupPanelInitParameters | undefined;
readonly view: IDockviewPanelModel;
readonly componentElId: string;
readonly tabComponentElId: string;

get title() {
return '';
Expand All @@ -189,6 +191,8 @@ export class TestPanel implements IDockviewPanel {

constructor(public readonly id: string, public api: DockviewPanelApi) {
this.view = new TestModel(id);
this.tabComponentElId = `tab-${id}`;
this.componentElId = `tab-panel-${id}`;
this.init({
title: `${id}`,
params: {},
Expand Down Expand Up @@ -266,6 +270,7 @@ describe('dockviewGroupPanelModel', () => {
removeGroup: removeGroupMock,
onDidAddPanel: () => ({ dispose: jest.fn() }),
onDidRemovePanel: () => ({ dispose: jest.fn() }),
onDidActivePanelChange: () => ({ dispose: jest.fn() }),
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
Expand Down Expand Up @@ -653,6 +658,7 @@ describe('dockviewGroupPanelModel', () => {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidOptionsChange: jest.fn(),
onDidActivePanelChange: jest.fn(),
});

const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
Expand Down Expand Up @@ -716,6 +722,7 @@ describe('dockviewGroupPanelModel', () => {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidOptionsChange: jest.fn(),
onDidActivePanelChange: jest.fn(),
});

const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
Expand Down Expand Up @@ -808,6 +815,7 @@ describe('dockviewGroupPanelModel', () => {
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
Expand Down Expand Up @@ -875,6 +883,7 @@ describe('dockviewGroupPanelModel', () => {
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
Expand Down Expand Up @@ -949,6 +958,7 @@ describe('dockviewGroupPanelModel', () => {
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
Expand Down Expand Up @@ -1030,6 +1040,7 @@ describe('dockviewGroupPanelModel', () => {
return {
id: 'testgroupid',
model: groupView,
dispose: jest.fn()
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe('gridviewPanel', () => {
return {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidActivePanelChange: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
} as any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class ContentContainer
super();
this._element = document.createElement('div');
this._element.className = 'dv-content-container';
this.element.role = 'tabpanel';
this._element.tabIndex = -1;

this.addDisposables(this._onDidFocus, this._onDidBlur);
Expand Down
33 changes: 31 additions & 2 deletions packages/dockview-core/src/dockview/components/tab/defaultTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createCloseButton } from '../../../svg';
export class DefaultTab extends CompositeDisposable implements ITabRenderer {
private readonly _element: HTMLElement;
private readonly _content: HTMLElement;
private readonly action: HTMLElement;
private readonly action: HTMLButtonElement;
private _title: string | undefined;

get element(): HTMLElement {
Expand All @@ -22,22 +22,39 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this._content = document.createElement('div');
this._content.className = 'dv-default-tab-content';

this.action = document.createElement('div');
this.action = document.createElement('button');
this.action.type = 'button';

this.action.className = 'dv-default-tab-action';
// originally hide this, so only when it is focused is it read out.
// so the SR when focused on the tab, doesn't read "<Tab Content> Close Button"
this.action.ariaHidden = 'true';

this.action.appendChild(createCloseButton());

this._element.appendChild(this._content);
this._element.appendChild(this.action);

this.addDisposables(
addDisposableListener(this.action, 'focus', (event) => {
this.action.ariaHidden = 'false';
}),
addDisposableListener(this.action, 'blur', (event) => {
this.action.ariaHidden = 'true';
})
);

this.render();
}

init(params: GroupPanelPartInitParameters): void {
this._title = params.title;
this.action.ariaLabel = `Close "${this._title}" tab`;

this.addDisposables(
params.api.onDidTitleChange((event) => {
this._title = event.title;
this.action.ariaLabel = `Close "${event.title}" tab`;
this.render();
}),
addDisposableListener(this.action, 'pointerdown', (ev) => {
Expand All @@ -50,6 +67,18 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {

ev.preventDefault();
params.api.close();
}),
addDisposableListener(this.action, 'keydown', (ev) => {
if (ev.defaultPrevented) {
return;
}

switch (ev.key) {
case 'Enter':
case 'Space':
params.api.close();
break;
}
})
);

Expand Down
Loading
Loading