diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index 3580e7157..323bd7f42 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -601,7 +601,9 @@ export class TabBar extends Widget { this._evtDblClick(event as MouseEvent); break; case 'keydown': - this._evtKeyDown(event as KeyboardEvent); + event.eventPhase === Event.CAPTURING_PHASE + ? this._evtKeyDownCapturing(event as KeyboardEvent) + : this._evtKeyDown(event as KeyboardEvent); break; case 'contextmenu': event.preventDefault(); @@ -616,6 +618,7 @@ export class TabBar extends Widget { protected onBeforeAttach(msg: Message): void { this.node.addEventListener('pointerdown', this); this.node.addEventListener('dblclick', this); + this.node.addEventListener('keydown', this); } /** @@ -624,6 +627,7 @@ export class TabBar extends Widget { protected onAfterDetach(msg: Message): void { this.node.removeEventListener('pointerdown', this); this.node.removeEventListener('dblclick', this); + this.node.removeEventListener('keydown', this); this._releaseMouse(); } @@ -708,9 +712,13 @@ export class TabBar extends Widget { } /** - * Handle the `'keydown'` event for the tab bar. + * Handle the `'keydown'` event for the tab bar at capturing phase. */ - private _evtKeyDown(event: KeyboardEvent): void { + private _evtKeyDownCapturing(event: KeyboardEvent): void { + if (event.eventPhase !== Event.CAPTURING_PHASE) { + return; + } + // Stop all input events during drag. event.preventDefault(); event.stopPropagation(); @@ -721,6 +729,45 @@ export class TabBar extends Widget { } } + /** + * Handle the `'keydown'` event for the tab bar at target phase. + */ + private _evtKeyDown(event: KeyboardEvent): void { + // Allow for navigation using tab key + if (event.key === 'Tab' || event.eventPhase === Event.CAPTURING_PHASE) { + return; + } + + // Check if Enter or Spacebar key has been pressed and open that tab + if ( + event.key === 'Enter' || + event.key === 'Spacebar' || + event.key === ' ' + ) { + // Get focus element that is in focus by the tab key + const focusedElement = document.activeElement; + + // Test first if the focus is on the add button node + if ( + this.addButtonEnabled && + this.addButtonNode.contains(focusedElement) + ) { + event.preventDefault(); + event.stopPropagation(); + this._addRequested.emit(); + } else { + const index = ArrayExt.findFirstIndex(this.contentNode.children, tab => + tab.contains(focusedElement) + ); + if (index >= 0) { + event.preventDefault(); + event.stopPropagation(); + this.currentIndex = index; + } + } + } + } + /** * Handle the `'pointerdown'` event for the tab bar. */ @@ -1863,6 +1910,7 @@ namespace Private { let add = document.createElement('div'); add.className = 'lm-TabBar-addButton lm-mod-hidden'; + add.setAttribute('tabindex', '0'); node.appendChild(add); return node; } diff --git a/packages/widgets/tests/src/tabbar.spec.ts b/packages/widgets/tests/src/tabbar.spec.ts index 2d4c9d287..40bd3cea2 100644 --- a/packages/widgets/tests/src/tabbar.spec.ts +++ b/packages/widgets/tests/src/tabbar.spec.ts @@ -1522,6 +1522,76 @@ describe('@lumino/widgets', () => { simulateOnNode(tab, 'pointerdown'); expect(bar.events.indexOf('pointermove')).to.equal(-1); }); + + it('should activate the focused title on Enter', () => { + // Focus 3rd tab + (bar.contentNode.children[2] as HTMLElement).focus(); + + bar.node.dispatchEvent( + new KeyboardEvent('keydown', { + key: 'Enter', + cancelable: true, + bubbles: true + }) + ); + + expect(bar.currentIndex).to.equal(2); + }); + + it('should activate the focused title on Space', () => { + // Focus 2nd tab + (bar.contentNode.children[1] as HTMLElement).focus(); + + bar.node.dispatchEvent( + new KeyboardEvent('keydown', { + key: ' ', + cancelable: true, + bubbles: true + }) + ); + + expect(bar.currentIndex).to.equal(1); + }); + + it('should add a tab when Enter is pressed with focus on add button', () => { + let addRequested = false; + bar.addButtonEnabled = true; + bar.addButtonNode.focus(); + + bar.addRequested.connect(() => { + addRequested = true; + }); + + bar.node.dispatchEvent( + new KeyboardEvent('keydown', { + key: 'Enter', + cancelable: true, + bubbles: true + }) + ); + + expect(addRequested).to.be.true; + }); + + it('should add a tab when Space is pressed with focus on add button', () => { + let addRequested = false; + bar.addButtonEnabled = true; + bar.addButtonNode.focus(); + + bar.addRequested.connect(() => { + addRequested = true; + }); + + bar.node.dispatchEvent( + new KeyboardEvent('keydown', { + key: ' ', + cancelable: true, + bubbles: true + }) + ); + + expect(addRequested).to.be.true; + }); }); context('contextmenu', () => {