diff --git a/packages/widgets/src/accordionlayout.ts b/packages/widgets/src/accordionlayout.ts index a4247605f..fa2da95dc 100644 --- a/packages/widgets/src/accordionlayout.ts +++ b/packages/widgets/src/accordionlayout.ts @@ -4,6 +4,7 @@ */ import { ArrayExt } from '@lumino/algorithm'; +import { UUID } from '@lumino/coreutils'; import { SplitLayout } from './splitlayout'; import { Title } from './title'; import Utils from './utils'; @@ -83,6 +84,28 @@ export class AccordionLayout extends SplitLayout { this.parent!.node.replaceChild(newTitle, oldTitle); } + /** + * Insert a widget into the layout at the specified index. + * + * @param index - The index at which to insert the widget. + * + * @param widget - The widget to insert into the layout. + * + * #### Notes + * The index will be clamped to the bounds of the widgets. + * + * If the widget is already added to the layout, it will be moved. + * + * #### Undefined Behavior + * An `index` which is non-integral. + */ + insertWidget(index: number, widget: Widget): void { + if (!widget.id) { + widget.id = `id-${UUID.uuid4()}`; + } + super.insertWidget(index, widget); + } + /** * Attach a widget to the parent's DOM node. * diff --git a/packages/widgets/src/accordionpanel.ts b/packages/widgets/src/accordionpanel.ts index 79a4c1b79..37bee5c6c 100644 --- a/packages/widgets/src/accordionpanel.ts +++ b/packages/widgets/src/accordionpanel.ts @@ -401,7 +401,6 @@ export namespace AccordionPanel { */ createSectionTitle(data: Title): HTMLElement { const handle = document.createElement('h3'); - handle.setAttribute('role', 'tab'); handle.setAttribute('tabindex', '0'); handle.id = this.createTitleKey(data); handle.className = this.titleClassName; diff --git a/packages/widgets/tests/src/accordionpanel.spec.ts b/packages/widgets/tests/src/accordionpanel.spec.ts index 869e6079d..b93a22bdc 100644 --- a/packages/widgets/tests/src/accordionpanel.spec.ts +++ b/packages/widgets/tests/src/accordionpanel.spec.ts @@ -77,6 +77,36 @@ describe('@lumino/widgets', () => { }); }); + describe('#accessibility()', () => { + it('should create a widget ID if it does not exist', () => { + let panel = new LogAccordionPanel(); + let w1 = new Widget(); + let w2 = new Widget(); + + // Expects a widget ID to be created. + expect(w1.id).to.be.empty; + panel.addWidget(w1); + expect(w1.id).to.match(/id-[0-9a-z\-]+/); + + // Expects the widget ID to be unchanged. + w2.id = 'test-id'; + panel.addWidget(w2); + expect(w2.id).to.equal('test-id'); + }); + + it('should link the widget to its title', () => { + let layout = new AccordionLayout({ renderer }); + let panel = new LogAccordionPanel({ layout }); + let w = new Widget(); + panel.addWidget(w); + + expect(layout.titles[0].getAttribute('aria-controls')).to.equal(w.id); + expect(layout.widgets[0].node.getAttribute('aria-labelledby')).to.equal( + layout.titles[0].id + ); + }); + }); + describe('#dispose()', () => { it('should dispose of the resources held by the panel', () => { let panel = new LogAccordionPanel(); diff --git a/review/api/widgets.api.md b/review/api/widgets.api.md index fca3f1f82..de211d650 100644 --- a/review/api/widgets.api.md +++ b/review/api/widgets.api.md @@ -24,6 +24,7 @@ export class AccordionLayout extends SplitLayout { protected attachWidget(index: number, widget: Widget): void; protected detachWidget(index: number, widget: Widget): void; dispose(): void; + insertWidget(index: number, widget: Widget): void; protected moveWidget(fromIndex: number, toIndex: number, widget: Widget): void; readonly renderer: AccordionLayout.IRenderer; get titles(): ReadonlyArray;