From 692588c887d4b0407d91d697c30d874a6e8b8575 Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Wed, 28 Aug 2024 11:09:04 -0300 Subject: [PATCH 01/10] =?UTF-8?q?=E2=99=BF=EF=B8=8F=20access:=20add=20acce?= =?UTF-8?q?sibility=20attributes=20to=20elements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/dockview/components/tab/tab.ts | 10 ++++- .../components/titlebar/tabsContainer.ts | 43 +++++++++++++++++++ .../src/dockview/dockviewPanel.ts | 17 +++++++- .../dockview-core/src/dockview/options.ts | 12 ++++++ .../dockview-core/src/gridview/gridview.ts | 4 +- .../src/overlay/overlayRenderContainer.ts | 4 +- 6 files changed, 85 insertions(+), 5 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index c97f1f1104..ec3de0c297 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -72,10 +72,15 @@ export class Tab extends CompositeDisposable { super(); this._element = document.createElement('div'); + this._element.id = this.panel.tabComponentElId; this._element.className = 'tab'; - this._element.tabIndex = 0; this._element.draggable = true; + this._element.role = 'tab'; + this._element.tabIndex = -1; + this._element.ariaSelected = 'false'; + this._element.setAttribute('aria-controls', this.panel.componentElId); + toggleClass(this.element, 'inactive-tab', true); const dragHandler = new TabDragHandler( @@ -139,6 +144,9 @@ export class Tab extends CompositeDisposable { } public setActive(isActive: boolean): void { + this.element.tabIndex = isActive ? 0 : -1; + this.element.ariaSelected = isActive.toString(); + toggleClass(this.element, 'active-tab', isActive); toggleClass(this.element, 'inactive-tab', !isActive); } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 7557f3fde3..326b269cf3 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -195,6 +195,7 @@ export class TabsContainer this.tabContainer = document.createElement('div'); this.tabContainer.className = 'tabs-container'; + this.tabContainer.role = 'tablist'; this.voidContainer = new VoidContainer(this.accessor, this.group); @@ -223,6 +224,13 @@ export class TabsContainer ); } }), + this.accessor.onDidActivePanelChange((e) => { + if (e?.api.group === this.group) { + this.selectedIndex = this.indexOf(e.id); + } else { + this.selectedIndex = -1; + } + }), this._onWillShowOverlay, this._onDrop, this._onTabDragStart, @@ -288,6 +296,40 @@ export class TabsContainer if (isLeftClick) { this.accessor.doSetGroupActive(this.group); } + }), + addDisposableListener(this.tabContainer, 'keydown', (event) => { + if (event.defaultPrevented) { + return; + } + + let tab: IValueDisposable | undefined = undefined; + + switch (event.key) { + case 'ArrowLeft': { + if (this.selectedIndex > 0) { + tab = this.tabs[this.selectedIndex - 1]; + } + break; + } + case 'ArrowRight': { + if (this.selectedIndex + 1 < this.size) { + tab = this.tabs[this.selectedIndex + 1]; + } + break; + } + case 'Home': + tab = this.tabs[0]; + break; + case 'End': + tab = this.tabs[this.size - 1]; + break; + } + + if (tab == null) { + return; + } + + this.group.model.openPanel(tab.value.panel); }) ); } @@ -336,6 +378,7 @@ export class TabsContainer this.tabs.forEach((tab) => { const isActivePanel = panel.id === tab.value.panel.id; tab.value.setActive(isActivePanel); + tab.value.panel.runEvents(); }); } diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index 984c3fcdbe..6d93777cf6 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -18,6 +18,8 @@ export interface IDockviewPanel extends IDisposable, IPanel { readonly api: DockviewPanelApi; readonly title: string | undefined; readonly params: Parameters | undefined; + readonly componentElId: string; + readonly tabComponentElId: string; updateParentGroup( group: DockviewGroupPanel, options?: { skipSetActive?: boolean } @@ -34,6 +36,8 @@ export class DockviewPanel implements IDockviewPanel { readonly api: DockviewPanelApiImpl; + readonly componentElId: string; + readonly tabComponentElId: string; private _group: DockviewGroupPanel; private _params?: Parameters; @@ -64,12 +68,23 @@ export class DockviewPanel private readonly containerApi: DockviewApi, group: DockviewGroupPanel, readonly view: IDockviewPanelModel, - options: { renderer?: DockviewPanelRenderer } + options: { + renderer?: DockviewPanelRenderer; + componentElId?: string; + tabComponentElId?: string; + } ) { super(); this._renderer = options.renderer; this._group = group; + const randomId = Math.random().toString(36).slice(2); + + this.tabComponentElId = + options.tabComponentElId ?? `tab-${id}-${randomId}`; + this.componentElId = + options.componentElId ?? `tab-panel-${id}-${randomId}`; + this.api = new DockviewPanelApiImpl( this, this._group, diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 5fce93ebfe..936e94dfe7 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -230,6 +230,18 @@ export type AddPanelOptions

= { * Defaults to `false` which forces newly added panels to become active. */ inactive?: boolean; + /** + * The unique DOM id for the rendered panel element + * + * Used for accessibility attributes + */ + componentElId?: string; + /** + * The unique DOM id for the rendered tab element + * + * Used for accessibility attributes + */ + tabComponentElId?: string; } & Partial; type AddGroupOptionsWithPanel = { diff --git a/packages/dockview-core/src/gridview/gridview.ts b/packages/dockview-core/src/gridview/gridview.ts index f2b4806cfb..daf99f1a67 100644 --- a/packages/dockview-core/src/gridview/gridview.ts +++ b/packages/dockview-core/src/gridview/gridview.ts @@ -575,7 +575,7 @@ export class Gridview implements IDisposable { } this._root = root; - this.element.appendChild(this._root.element); + this.element.prepend(this._root.element); this.disposable.value = this._root.onDidChange((e) => { this._onDidChange.fire(e); }); @@ -631,7 +631,7 @@ export class Gridview implements IDisposable { this._root.addChild(oldRoot, Sizing.Distribute, 0); } - this.element.appendChild(this._root.element); + this.element.prepend(this._root.element); this.disposable.value = this._root.onDidChange((e) => { this._onDidChange.fire(e); diff --git a/packages/dockview-core/src/overlay/overlayRenderContainer.ts b/packages/dockview-core/src/overlay/overlayRenderContainer.ts index 1d68042e97..25a24048bd 100644 --- a/packages/dockview-core/src/overlay/overlayRenderContainer.ts +++ b/packages/dockview-core/src/overlay/overlayRenderContainer.ts @@ -74,12 +74,14 @@ export class OverlayRenderContainer extends CompositeDisposable { if (!this.map[panel.api.id]) { const element = createFocusableElement(); element.className = 'dv-render-overlay'; + element.role = 'tabpanel'; + element.tabIndex = 0; + element.setAttribute('aria-labelledby', panel.tabComponentElId); this.map[panel.api.id] = { panel, disposable: Disposable.NONE, destroy: Disposable.NONE, - element, }; } From 23a7bbeb05ce47e2685253d7901e38164d8a2697 Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Wed, 28 Aug 2024 11:09:24 -0300 Subject: [PATCH 02/10] =?UTF-8?q?=E2=9C=85=20test:=20add=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/api/dockviewPanelApi.spec.ts | 2 + .../components/titlebar/tabsContainer.spec.ts | 13 +++ .../dockview/dockviewComponent.spec.ts | 89 ++++++++++++++++++- .../dockview/dockviewGroupPanelModel.spec.ts | 11 +++ .../__tests__/gridview/gridviewPanel.spec.ts | 1 + 5 files changed, 115 insertions(+), 1 deletion(-) diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 0bc24eaa51..7208232426 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -49,6 +49,7 @@ describe('groupPanelApi', () => { const accessor = fromPartial({ onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -81,6 +82,7 @@ describe('groupPanelApi', () => { const accessor = fromPartial({ onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index bdcc370ee2..d751a26e0a 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -16,6 +16,7 @@ describe('tabsContainer', () => { const accessor = fromPartial({ onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -69,6 +70,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -135,6 +137,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -198,6 +201,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -261,6 +265,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -329,6 +334,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -393,6 +399,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, }); @@ -457,6 +464,7 @@ describe('tabsContainer', () => { options: {}, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), element: document.createElement('div'), addFloatingGroup: jest.fn(), }); @@ -511,6 +519,7 @@ describe('tabsContainer', () => { options: {}, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), element: document.createElement('div'), addFloatingGroup: jest.fn(), }); @@ -560,6 +569,7 @@ describe('tabsContainer', () => { options: {}, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), @@ -616,6 +626,7 @@ describe('tabsContainer', () => { options: {}, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), @@ -683,6 +694,7 @@ describe('tabsContainer', () => { options: {}, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), @@ -750,6 +762,7 @@ describe('tabsContainer', () => { options: {}, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index ae45adf8f5..e3580a7058 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -8,7 +8,7 @@ import { PanelUpdateEvent } from '../../panel/types'; import { Orientation } from '../../splitview/splitview'; import { CompositeDisposable } from '../../lifecycle'; import { Emitter } from '../../events'; -import { IDockviewPanel } from '../../dockview/dockviewPanel'; +import { IDockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { fireEvent, queryByTestId } from '@testing-library/dom'; import { getPanelData } from '../../dnd/dataTransfer'; @@ -5637,4 +5637,91 @@ describe('dockviewComponent', () => { expect(dockview.gap).toBe(15); }); }); + + 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(); + }); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 01d0c84d18..a93659dd78 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -188,6 +188,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 ''; @@ -203,6 +205,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: {}, @@ -280,6 +284,7 @@ describe('dockviewGroupPanelModel', () => { removeGroup: removeGroupMock, onDidAddPanel: () => ({ dispose: jest.fn() }), onDidRemovePanel: () => ({ dispose: jest.fn() }), + onDidActivePanelChange: () => ({ dispose: jest.fn() }), overlayRenderContainer: new OverlayRenderContainer( document.createElement('div'), fromPartial({}) @@ -665,6 +670,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -727,6 +733,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -819,6 +826,7 @@ describe('dockviewGroupPanelModel', () => { doSetGroupActive: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), overlayRenderContainer: new OverlayRenderContainer( document.createElement('div'), fromPartial({}) @@ -891,6 +899,7 @@ describe('dockviewGroupPanelModel', () => { doSetGroupActive: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), overlayRenderContainer: new OverlayRenderContainer( document.createElement('div'), fromPartial({}) @@ -964,6 +973,7 @@ describe('dockviewGroupPanelModel', () => { doSetGroupActive: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), overlayRenderContainer: new OverlayRenderContainer( document.createElement('div'), fromPartial({}) @@ -1044,6 +1054,7 @@ describe('dockviewGroupPanelModel', () => { return { id: 'testgroupid', model: groupView, + dispose: jest.fn() }; }); diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts index cfae5ea6ba..88672e7744 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts @@ -7,6 +7,7 @@ describe('gridviewPanel', () => { return { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidActivePanelChange: jest.fn(), options: {}, } as any; }); From 9d9a9281c26e0ebbbce8cb12dee1a7c98de454cb Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Wed, 2 Apr 2025 11:56:16 -0300 Subject: [PATCH 03/10] =?UTF-8?q?=E2=99=BF=EF=B8=8F=20access:=20remove=20i?= =?UTF-8?q?d=20options,=20focus=20tab=20on=20select,=20close=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/dockview/components/tab/defaultTab.ts | 12 ++++++++++ .../src/dockview/components/tab/tab.ts | 4 +--- .../src/dockview/components/titlebar/tabs.ts | 5 +++-- .../src/dockview/dockviewPanel.ts | 22 +++++++++++-------- .../dockview-core/src/dockview/options.ts | 12 ---------- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts index 205e4e562c..2d595242d5 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts @@ -24,16 +24,28 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { this.action = document.createElement('div'); this.action.className = 'dv-default-tab-action'; + this.action.tabIndex = 0; + 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}`; this.addDisposables( params.api.onDidTitleChange((event) => { diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index c40eff92f7..5933b844ce 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -75,11 +75,9 @@ export class Tab extends CompositeDisposable { this._element = document.createElement('div'); this._element.id = this.panel.tabComponentElId; this._element.className = 'dv-tab'; - this._element.tabIndex = 0; - this._element.draggable = true; - this._element.role = 'tab'; this._element.tabIndex = -1; + this._element.draggable = true; this._element.ariaSelected = 'false'; this._element.setAttribute('aria-controls', this.panel.componentElId); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index 5549b6e231..d4f4b489d4 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -109,7 +109,7 @@ export class Tabs extends CompositeDisposable { this.addDisposables(scrollbar); } - this.element.role = "tablist" + this.element.role = 'tablist'; this.addDisposables( this._onOverflowTabsChange, @@ -167,6 +167,7 @@ export class Tabs extends CompositeDisposable { return; } + tab.value.element.focus(); this.group.model.openPanel(tab.value.panel); }), Disposable.from(() => { @@ -197,7 +198,7 @@ export class Tabs extends CompositeDisposable { for (const tab of this._tabs) { const isActivePanel = panel.id === tab.value.panel.id; tab.value.setActive(isActivePanel); - tab.value.panel.runEvents() + tab.value.panel.runEvents(); if (isActivePanel) { const element = tab.value.element; diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index 0c26f6eb89..cf904ff16a 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -41,7 +41,17 @@ export class DockviewPanel implements IDockviewPanel { readonly api: DockviewPanelApiImpl; + /** + * The unique DOM id for the rendered panel element + * + * Used for accessibility attributes + */ readonly componentElId: string; + /** + * The unique DOM id for the rendered tab element + * + * Used for accessibility attributes + */ readonly tabComponentElId: string; private _group: DockviewGroupPanel; @@ -94,11 +104,7 @@ export class DockviewPanel private readonly containerApi: DockviewApi, group: DockviewGroupPanel, readonly view: IDockviewPanelModel, - options: { - renderer?: DockviewPanelRenderer; - componentElId?: string; - tabComponentElId?: string; - } & Partial + options: { renderer?: DockviewPanelRenderer } & Partial ) { super(); this._renderer = options.renderer; @@ -110,10 +116,8 @@ export class DockviewPanel const randomId = Math.random().toString(36).slice(2); - this.tabComponentElId = - options.tabComponentElId ?? `tab-${id}-${randomId}`; - this.componentElId = - options.componentElId ?? `tab-panel-${id}-${randomId}`; + this.tabComponentElId = `tab-${id}-${randomId}`; + this.componentElId = `tab-panel-${id}-${randomId}`; this.api = new DockviewPanelApiImpl( this, diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 762003461c..39e66d2c36 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -258,18 +258,6 @@ export type AddPanelOptions

= { * Defaults to `false` which forces newly added panels to become active. */ inactive?: boolean; - /** - * The unique DOM id for the rendered panel element - * - * Used for accessibility attributes - */ - componentElId?: string; - /** - * The unique DOM id for the rendered tab element - * - * Used for accessibility attributes - */ - tabComponentElId?: string; initialWidth?: number; initialHeight?: number; } & Partial & From cef78168f7f766617bc3c013224087a8dc2512e8 Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Fri, 4 Apr 2025 09:18:47 -0300 Subject: [PATCH 04/10] =?UTF-8?q?=E2=99=BF=EF=B8=8F=20access:=20allow=20En?= =?UTF-8?q?ter/Space=20to=20trigger=20tab=20close?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/dockview/components/tab/defaultTab.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts index 2d595242d5..7105f2024a 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts @@ -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 { @@ -22,10 +22,13 @@ 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'; - this.action.tabIndex = 0; + // originally hide this, so only when it is focused is it read out. + // so the SR when focused on the tab, doesn't read " Close Button" this.action.ariaHidden = 'true'; + this.action.appendChild(createCloseButton()); this._element.appendChild(this._content); @@ -45,11 +48,12 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { init(params: GroupPanelPartInitParameters): void { this._title = params.title; - this.action.ariaLabel = `Close ${this._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) => { @@ -62,6 +66,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; + } }) ); From 000fa474ba8085e3c988308ec864e42a17be919a Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Fri, 4 Apr 2025 09:45:58 -0300 Subject: [PATCH 05/10] =?UTF-8?q?=E2=99=BF=EF=B8=8F=20access:=20add=20outl?= =?UTF-8?q?ine=20to=20tab=20on=20focus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/dockview-core/src/dockview/dockviewComponent.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/dockview-core/src/dockview/dockviewComponent.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index 440f6cc5ee..df182caab8 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -20,6 +20,9 @@ &.dv-active-group { > .dv-tabs-and-actions-container { .dv-tabs-container > .dv-tab { + &:focus { + outline: 1px solid var(--dv-paneview-active-outline-color); + } &.dv-active-tab { background-color: var( --dv-activegroup-visiblepanel-tab-background-color @@ -38,6 +41,9 @@ &.dv-inactive-group { > .dv-tabs-and-actions-container { .dv-tabs-container > .dv-tab { + &:focus { + outline: 1px solid var(--dv-paneview-active-outline-color); + } &.dv-active-tab { background-color: var( --dv-inactivegroup-visiblepanel-tab-background-color From 5c78f2bd67626bdcc6b4ace2dd762c6ae5b664ab Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Fri, 4 Apr 2025 10:59:49 -0300 Subject: [PATCH 06/10] =?UTF-8?q?=E2=99=BF=EF=B8=8F=20access:=20allow=20En?= =?UTF-8?q?ter/Space=20on=20tab=20to=20activate=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dockview-core/src/dockview/components/tab/tab.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index 5933b844ce..159de4f967 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -143,6 +143,18 @@ export class Tab extends CompositeDisposable { addDisposableListener(this._element, 'pointerdown', (event) => { this._onPointDown.fire(event); }), + addDisposableListener(this.element, 'keydown', (event) => { + if (event.defaultPrevented) { + return; + } + + switch (event.key) { + case 'Enter': + case 'Space': + this.group.model.openPanel(this.panel); + break; + } + }), this.dropTarget.onDrop((event) => { this._onDropped.fire(event); }), From 8606692e3884a8185888e0b03bbeff06f8fede08 Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Fri, 4 Apr 2025 11:15:05 -0300 Subject: [PATCH 07/10] =?UTF-8?q?=E2=99=BF=EF=B8=8F=20access:=20add=20inst?= =?UTF-8?q?ructions=20to=20tab-list,=20and=20orientation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/dockview/components/titlebar/tabs.ts | 3 +++ packages/dockview-core/src/splitview/splitview.ts | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index d4f4b489d4..08fc329de5 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -98,6 +98,7 @@ export class Tabs extends CompositeDisposable { this._tabsList = document.createElement('div'); this._tabsList.className = 'dv-tabs-container dv-horizontal'; + this._tabsList.ariaOrientation = 'horizontal'; this.showTabsOverflowControl = options.showTabsOverflowControl; @@ -110,6 +111,8 @@ export class Tabs extends CompositeDisposable { } this.element.role = 'tablist'; + this.element.ariaLabel = + 'Use Left Arrow to select the previous tab, Right Arrow for the next tab, Home for the first tab, and End for the last tab. Press Enter to select the focused tab.'; this.addDisposables( this._onOverflowTabsChange, diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index c511d54f65..a7e6b2d0e7 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -165,6 +165,10 @@ export class Splitview { ? 'dv-horizontal' : 'dv-vertical' ); + this.element.ariaOrientation = + this.orientation == Orientation.HORIZONTAL + ? 'horizontal' + : 'vertical'; } get minimumSize(): number { @@ -1148,6 +1152,10 @@ export class Splitview { ? 'dv-horizontal' : 'dv-vertical'; element.className = `dv-split-view-container ${orientationClassname}`; + element.ariaOrientation = + this._orientation == Orientation.HORIZONTAL + ? 'horizontal' + : 'vertical'; return element; } From 57a1624e754216e573c8ff706711b3c8ee45f949 Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Fri, 4 Apr 2025 15:51:22 -0300 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=92=AC=20text:=20verbiage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/dockview-core/src/dockview/components/titlebar/tabs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index 08fc329de5..1e60d28b7b 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -112,7 +112,7 @@ export class Tabs extends CompositeDisposable { this.element.role = 'tablist'; this.element.ariaLabel = - 'Use Left Arrow to select the previous tab, Right Arrow for the next tab, Home for the first tab, and End for the last tab. Press Enter to select the focused tab.'; + 'Use the Left Arrow to select the previous tab, Right Arrow for the next tab, Home for the first tab, and End for the last tab. Press Enter to select the focused tab.'; this.addDisposables( this._onOverflowTabsChange, From b5d28ce042d71e5a05c9125808531b67f51a1dfb Mon Sep 17 00:00:00 2001 From: Ademola Adedeji Date: Mon, 14 Apr 2025 13:03:54 -0300 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=90=9B=20fix:=20use=20focused=20tab?= =?UTF-8?q?=20as=20reference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/dockview/components/tab/tab.ts | 16 ++--- .../src/dockview/components/titlebar/tabs.ts | 69 +++++++++---------- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index 159de4f967..de09c714b6 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -52,6 +52,9 @@ export class Tab extends CompositeDisposable { private readonly _onPointDown = new Emitter(); readonly onPointerDown: Event = this._onPointDown.event; + + private readonly _onKeyDown = new Emitter(); + readonly onKeyDown: Event = this._onKeyDown.event; private readonly _onDropped = new Emitter(); readonly onDrop: Event = this._onDropped.event; @@ -143,17 +146,8 @@ export class Tab extends CompositeDisposable { addDisposableListener(this._element, 'pointerdown', (event) => { this._onPointDown.fire(event); }), - addDisposableListener(this.element, 'keydown', (event) => { - if (event.defaultPrevented) { - return; - } - - switch (event.key) { - case 'Enter': - case 'Space': - this.group.model.openPanel(this.panel); - break; - } + addDisposableListener(this._element, 'keydown', (event) => { + this._onKeyDown.fire(event); }), this.dropTarget.onDrop((event) => { this._onDropped.fire(event); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index 1e60d28b7b..317c11b18c 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -138,41 +138,6 @@ export class Tabs extends CompositeDisposable { this.accessor.doSetGroupActive(this.group); } }), - addDisposableListener(this.element, 'keydown', (event) => { - if (event.defaultPrevented) { - return; - } - - let tab: IValueDisposable | undefined = undefined; - - switch (event.key) { - case 'ArrowLeft': { - if (this.selectedIndex > 0) { - tab = this._tabs[this.selectedIndex - 1]; - } - break; - } - case 'ArrowRight': { - if (this.selectedIndex + 1 < this.size) { - tab = this._tabs[this.selectedIndex + 1]; - } - break; - } - case 'Home': - tab = this._tabs[0]; - break; - case 'End': - tab = this._tabs[this.size - 1]; - break; - } - - if (tab == null) { - return; - } - - tab.value.element.focus(); - this.group.model.openPanel(tab.value.panel); - }), Disposable.from(() => { for (const { value, disposable } of this._tabs) { disposable.dispose(); @@ -272,6 +237,40 @@ export class Tabs extends CompositeDisposable { break; } }), + tab.onKeyDown((event) => { + if (event.defaultPrevented) { + return; + } + + const index = this.indexOf(tab.panel.id); + let nextTab: Tab | undefined = undefined; + + switch (event.key) { + case 'ArrowLeft': + nextTab = this.tabs[Math.max(0, index - 1)]; + break; + case 'ArrowRight': + nextTab = this.tabs[Math.min(this.size - 1, index + 1)]; + break; + case 'Home': + nextTab = this.tabs[0]; + break; + case 'End': + nextTab = this.tabs[this.size - 1]; + break; + case 'Enter': + case 'Space': + nextTab = tab; + } + + if ( + nextTab != null && + this.group.activePanel !== nextTab.panel + ) { + nextTab.element.focus(); + this.group.model.openPanel(nextTab.panel); + } + }), tab.onDrop((event) => { this._onDrop.fire({ event: event.nativeEvent, From 182a2ae664ffcbd47d14d80e99c0134395261036 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 3 Jun 2025 21:39:38 +0100 Subject: [PATCH 10/10] chore: standardize api --- .../src/dockview/components/panel/content.ts | 1 + .../src/dockview/components/tab/defaultTab.ts | 3 +- .../src/dockview/components/tab/tab.ts | 10 +++++-- .../src/dockview/components/titlebar/tabs.ts | 7 ++--- .../src/dockview/dockviewComponent.ts | 8 +++++ .../src/dockview/dockviewPanel.ts | 30 +++++++------------ .../dockview-core/src/dockview/options.ts | 2 ++ .../src/overlay/overlayRenderContainer.ts | 2 -- .../dockview-core/src/splitview/splitview.ts | 28 ++++++++--------- 9 files changed, 46 insertions(+), 45 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 6c37035b07..c7de9e5682 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -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); diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts index 7105f2024a..82d4b4b3f3 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts @@ -24,6 +24,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { 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 " Close Button" @@ -49,7 +50,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { 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; diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index de09c714b6..8e92db1f81 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -52,7 +52,7 @@ export class Tab extends CompositeDisposable { private readonly _onPointDown = new Emitter(); readonly onPointerDown: Event = this._onPointDown.event; - + private readonly _onKeyDown = new Emitter(); readonly onKeyDown: Event = this._onKeyDown.event; @@ -76,13 +76,17 @@ export class Tab extends CompositeDisposable { super(); this._element = document.createElement('div'); - this._element.id = this.panel.tabComponentElId; this._element.className = 'dv-tab'; this._element.role = 'tab'; this._element.tabIndex = -1; this._element.draggable = true; this._element.ariaSelected = 'false'; - this._element.setAttribute('aria-controls', this.panel.componentElId); + + Object.entries(this.panel.tabComponentAttributes).forEach( + ([key, value]) => { + this._element.setAttribute(key, value); + } + ); toggleClass(this.element, 'dv-inactive-tab', true); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index 317c11b18c..a0ca25df31 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -99,6 +99,9 @@ export class Tabs extends CompositeDisposable { this._tabsList = document.createElement('div'); this._tabsList.className = 'dv-tabs-container dv-horizontal'; this._tabsList.ariaOrientation = 'horizontal'; + this.element.role = 'tablist'; + this.element.ariaLabel = + 'Use the Left Arrow to select the previous tab, Right Arrow for the next tab, Home for the first tab, and End for the last tab. Press Enter to select the focused tab.'; this.showTabsOverflowControl = options.showTabsOverflowControl; @@ -110,10 +113,6 @@ export class Tabs extends CompositeDisposable { this.addDisposables(scrollbar); } - this.element.role = 'tablist'; - this.element.ariaLabel = - 'Use the Left Arrow to select the previous tab, Right Arrow for the next tab, Home for the first tab, and End for the last tab. Press Enter to select the focused tab.'; - this.addDisposables( this._onOverflowTabsChange, this._observerDisposable, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index a0c4fb6f1c..43320e6c64 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -2524,6 +2524,12 @@ export class DockviewComponent tabComponent ); + Object.entries(options.componentAttributes ?? {}).forEach( + ([key, value]) => { + view.content.element.setAttribute(key, value); + } + ); + const panel = new DockviewPanel( options.id, contentComponent, @@ -2538,6 +2544,8 @@ export class DockviewComponent minimumHeight: options.minimumHeight, maximumWidth: options.maximumWidth, maximumHeight: options.maximumHeight, + componentAttributes: options.componentAttributes, + tabComponentAttributes: options.tabComponentAttributes, } ); diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index cf904ff16a..44c2f2b0c5 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -19,8 +19,8 @@ export interface IDockviewPanel extends IDisposable, IPanel { readonly api: DockviewPanelApi; readonly title: string | undefined; readonly params: Parameters | undefined; - readonly componentElId: string; - readonly tabComponentElId: string; + readonly componentAttributes: Record; + readonly tabComponentAttributes: Record; readonly minimumWidth?: number; readonly minimumHeight?: number; readonly maximumWidth?: number; @@ -41,18 +41,9 @@ export class DockviewPanel implements IDockviewPanel { readonly api: DockviewPanelApiImpl; - /** - * The unique DOM id for the rendered panel element - * - * Used for accessibility attributes - */ - readonly componentElId: string; - /** - * The unique DOM id for the rendered tab element - * - * Used for accessibility attributes - */ - readonly tabComponentElId: string; + + readonly componentAttributes: Record; + readonly tabComponentAttributes: Record; private _group: DockviewGroupPanel; private _params?: Parameters; @@ -104,7 +95,10 @@ export class DockviewPanel private readonly containerApi: DockviewApi, group: DockviewGroupPanel, readonly view: IDockviewPanelModel, - options: { renderer?: DockviewPanelRenderer } & Partial + options: { renderer?: DockviewPanelRenderer } & Partial & { + componentAttributes?: Record; + tabComponentAttributes?: Record; + } ) { super(); this._renderer = options.renderer; @@ -114,10 +108,8 @@ export class DockviewPanel this._maximumWidth = options.maximumWidth; this._maximumHeight = options.maximumHeight; - const randomId = Math.random().toString(36).slice(2); - - this.tabComponentElId = `tab-${id}-${randomId}`; - this.componentElId = `tab-panel-${id}-${randomId}`; + this.componentAttributes = options.componentAttributes ?? {}; + this.tabComponentAttributes = options.tabComponentAttributes ?? {}; this.api = new DockviewPanelApiImpl( this, diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 39e66d2c36..bde41b22d8 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -260,6 +260,8 @@ export type AddPanelOptions

= { inactive?: boolean; initialWidth?: number; initialHeight?: number; + componentAttributes?: Record; + tabComponentAttributes?: Record; } & Partial & Partial; diff --git a/packages/dockview-core/src/overlay/overlayRenderContainer.ts b/packages/dockview-core/src/overlay/overlayRenderContainer.ts index 12981e0392..c16af704a9 100644 --- a/packages/dockview-core/src/overlay/overlayRenderContainer.ts +++ b/packages/dockview-core/src/overlay/overlayRenderContainer.ts @@ -73,9 +73,7 @@ export class OverlayRenderContainer extends CompositeDisposable { if (!this.map[panel.api.id]) { const element = createFocusableElement(); element.className = 'dv-render-overlay'; - element.role = 'tabpanel'; element.tabIndex = 0; - element.setAttribute('aria-labelledby', panel.tabComponentElId); this.map[panel.api.id] = { panel, diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index a7e6b2d0e7..57d9094673 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -102,7 +102,7 @@ export class Splitview { private readonly sashContainer: HTMLElement; private readonly viewItems: ViewItem[] = []; private readonly sashes: ISashItem[] = []; - private _orientation: Orientation; + private _orientation = Orientation.VERTICAL; private _size = 0; private _orthogonalSize = 0; private _contentSize = 0; @@ -159,11 +159,15 @@ export class Splitview { this.size = this.orthogonalSize; this.orthogonalSize = tmp; - removeClasses(this.element, 'dv-horizontal', 'dv-vertical'); - this.element.classList.add( - this.orientation == Orientation.HORIZONTAL - ? 'dv-horizontal' - : 'dv-vertical' + toggleClass( + this.element, + 'dv-horizontal', + this._orientation == Orientation.HORIZONTAL + ); + toggleClass( + this.element, + 'dv-vertical', + this._orientation == Orientation.VERTICAL ); this.element.ariaOrientation = this.orientation == Orientation.HORIZONTAL @@ -231,8 +235,8 @@ export class Splitview { private readonly container: HTMLElement, options: SplitViewOptions ) { - this._orientation = options.orientation ?? Orientation.VERTICAL; this.element = this.createContainer(); + this.orientation = options.orientation ?? Orientation.VERTICAL; this.margin = options.margin ?? 0; @@ -1147,15 +1151,7 @@ export class Splitview { private createContainer(): HTMLElement { const element = document.createElement('div'); - const orientationClassname = - this._orientation === Orientation.HORIZONTAL - ? 'dv-horizontal' - : 'dv-vertical'; - element.className = `dv-split-view-container ${orientationClassname}`; - element.ariaOrientation = - this._orientation == Orientation.HORIZONTAL - ? 'horizontal' - : 'vertical'; + element.className = `dv-split-view-container`; return element; }