From 8277c16679f6c19650fb84a81dd62a590269c833 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:56:42 +0000 Subject: [PATCH 01/15] added aria labe to keyboard shortcuts in main menu --- packages/widgets/src/menu.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 054162675..d6c3df1a2 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -1221,7 +1221,13 @@ export namespace Menu { */ renderShortcut(data: IRenderData): VirtualElement { let content = this.formatShortcut(data); - return h.div({ className: 'lm-Menu-itemShortcut' }, content); + return h.div( + { + className: 'lm-Menu-itemShortcut', + 'aria-keyshortcuts': `${content}` + }, + content + ); } /** From 37881ea93c02ace2b3cd13350e067888faefe1dc Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:10:06 +0000 Subject: [PATCH 02/15] function to create menu item keyboard shortcut aria labels --- packages/widgets/src/menu.ts | 47 ++++++-- packages/widgets/tests/src/menu.spec.ts | 145 ++++++++++++++---------- 2 files changed, 128 insertions(+), 64 deletions(-) diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index d6c3df1a2..7029aaa37 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -27,7 +27,7 @@ import { ElementDataset, h, VirtualDOM, - VirtualElement + VirtualElement, } from '@lumino/virtualdom'; import { Widget } from './widget'; @@ -556,7 +556,7 @@ export class Menu extends Widget { collapsed, onfocus: () => { this.activeIndex = i; - } + }, }); } VirtualDOM.render(content, this.contentNode); @@ -708,7 +708,7 @@ export class Menu extends Widget { */ private _evtMouseMove(event: MouseEvent): void { // Hit test the item nodes for the item under the mouse. - let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { return ElementExt.hitTest(node, event.clientX, event.clientY); }); @@ -1177,7 +1177,7 @@ export namespace Menu { dataset, tabindex: '0', onfocus: data.onfocus, - ...aria + ...aria, }, this.renderIcon(data), this.renderLabel(data), @@ -1221,10 +1221,11 @@ export namespace Menu { */ renderShortcut(data: IRenderData): VirtualElement { let content = this.formatShortcut(data); + let ariaContent = this.formatShortcutText(data); return h.div( { className: 'lm-Menu-itemShortcut', - 'aria-keyshortcuts': `${content}` + 'aria-label': `${ariaContent}`, }, content ); @@ -1377,6 +1378,38 @@ export namespace Menu { let kb = data.item.keyBinding; return kb ? CommandRegistry.formatKeystroke(kb.keys) : null; } + + formatShortcutText(data: IRenderData): h.Child { + const keyToText: { [key: string]: string } = { + ']': 'Closing bracket', + '[': 'Opening bracket', + ',': 'Comma', + '.': 'Full stop', + "'": 'Single quote', + '-': 'Hyphen-minus', + }; + + let kbText = data.item.keyBinding; + let result = kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; + + let punctuationRegex = /\p{P}/u; + let punctuations = result?.match(punctuationRegex); + if (!punctuations) { + return []; + } + for (const punctuation of punctuations) { + if (result != null && Object.keys(keyToText).includes(punctuation)) { + const individualKeys = result.split('+'); + let index = individualKeys.indexOf(punctuation); + if (index != -1) { + individualKeys[index] = keyToText[punctuation]; + } + const textShortcut = individualKeys.join('+'); + return textShortcut; + } + } + return kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; + } } /** @@ -1529,7 +1562,7 @@ namespace Private { pageXOffset: window.pageXOffset, pageYOffset: window.pageYOffset, clientWidth: document.documentElement.clientWidth, - clientHeight: document.documentElement.clientHeight + clientHeight: document.documentElement.clientHeight, }; } @@ -1915,7 +1948,7 @@ namespace Private { if (this.type === 'command') { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, kb => { + ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); diff --git a/packages/widgets/tests/src/menu.spec.ts b/packages/widgets/tests/src/menu.spec.ts index a4f1c1983..55dbfb6bf 100644 --- a/packages/widgets/tests/src/menu.spec.ts +++ b/packages/widgets/tests/src/menu.spec.ts @@ -70,7 +70,7 @@ describe('@lumino/widgets', () => { const renderNode = document.createElement('div'); host.classList.add(iconClass); host.appendChild(renderNode); - } + }, }; before(() => { @@ -83,7 +83,18 @@ describe('@lumino/widgets', () => { iconClass, caption: 'Test Caption', className: 'testClass', - mnemonic: 0 + mnemonic: 0, + }); + commands.addCommand('test-aria', { + execute: (args: JSONObject) => { + executed = 'test-aria'; + }, + label: 'Test Aria Label', + icon: iconRenderer, + iconClass, + caption: 'Test Caption', + className: 'testClass', + mnemonic: 0, }); commands.addCommand('test-toggled', { execute: (args: JSONObject) => { @@ -93,7 +104,7 @@ describe('@lumino/widgets', () => { icon: iconRenderer, className: 'testClass', isToggled: (args: JSONObject) => true, - mnemonic: 6 + mnemonic: 6, }); commands.addCommand('test-disabled', { execute: (args: JSONObject) => { @@ -103,7 +114,7 @@ describe('@lumino/widgets', () => { icon: iconRenderer, className: 'testClass', isEnabled: (args: JSONObject) => false, - mnemonic: 5 + mnemonic: 5, }); commands.addCommand('test-hidden', { execute: (args: JSONObject) => { @@ -112,7 +123,7 @@ describe('@lumino/widgets', () => { label: 'Hidden Label', icon: iconRenderer, className: 'testClass', - isVisible: (args: JSONObject) => false + isVisible: (args: JSONObject) => false, }); commands.addCommand('test-zenith', { execute: (args: JSONObject) => { @@ -120,12 +131,17 @@ describe('@lumino/widgets', () => { }, label: 'Zenith Label', icon: iconRenderer, - className: 'testClass' + className: 'testClass', }); commands.addKeyBinding({ keys: ['Ctrl T'], selector: 'body', - command: 'test' + command: 'test', + }); + commands.addKeyBinding({ + keys: ['Ctrl ,'], + selector: 'body', + command: 'test-aria', }); }); @@ -197,7 +213,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 37 // Left arrow + keyCode: 37, // Left arrow }) ); expect(called).to.equal(true); @@ -213,7 +229,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 39 // Right arrow + keyCode: 39, // Right arrow }) ); expect(called).to.equal(true); @@ -237,7 +253,7 @@ describe('@lumino/widgets', () => { submenu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 39 // Right arrow + keyCode: 39, // Right arrow }) ); expect(called).to.equal(true); @@ -621,7 +637,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 13 // Enter + keyCode: 13, // Enter }) ); expect(executed).to.equal('test'); @@ -633,7 +649,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 27 // Escape + keyCode: 27, // Escape }) ); expect(menu.isAttached).to.equal(false); @@ -650,7 +666,7 @@ describe('@lumino/widgets', () => { submenu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 37 // Left arrow + keyCode: 37, // Left arrow }) ); expect(menu.childMenu).to.equal(null); @@ -664,7 +680,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 38 // Up arrow + keyCode: 38, // Up arrow }) ); expect(menu.activeIndex).to.equal(2); @@ -680,7 +696,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 39 // Right arrow + keyCode: 39, // Right arrow }) ); expect(menu.childMenu).to.equal(submenu); @@ -693,7 +709,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 40 // Down arrow + keyCode: 40, // Down arrow }) ); expect(menu.activeIndex).to.equal(0); @@ -718,7 +734,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 70 // `F` key + keyCode: 70, // `F` key }) ); expect(menu.activeIndex).to.equal(0); @@ -735,7 +751,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 90 // `Z` key + keyCode: 90, // `Z` key }) ); expect(menu.activeIndex).to.equal(4); @@ -758,7 +774,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new MouseEvent('mouseup', { bubbles, - button: 1 + button: 1, }) ); expect(executed).to.equal(''); @@ -775,13 +791,13 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top + clientY: rect.top, }) ); expect(menu.activeIndex).to.equal(0); }); - it('should open a child menu after a timeout', done => { + it('should open a child menu after a timeout', (done) => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); submenu.title.label = 'Test Label'; @@ -793,7 +809,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top + clientY: rect.top, }) ); expect(menu.activeIndex).to.equal(0); @@ -804,7 +820,7 @@ describe('@lumino/widgets', () => { }, 500); }); - it('should close an open sub menu', done => { + it('should close an open sub menu', (done) => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); submenu.title.label = 'Test Label'; @@ -819,7 +835,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top + clientY: rect.top, }) ); expect(menu.activeIndex).to.equal(0); @@ -844,7 +860,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top + clientY: rect.top, }) ); expect(menu.activeIndex).to.equal(0); @@ -852,7 +868,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mouseleave', { bubbles, clientX: rect.left, - clientY: rect.top + clientY: rect.top, }) ); expect(menu.activeIndex).to.equal(-1); @@ -870,7 +886,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousedown', { bubbles, clientX: rect.left, - clientY: rect.top + clientY: rect.top, }) ); expect(menu.isAttached).to.equal(true); @@ -883,7 +899,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new MouseEvent('mousedown', { bubbles, - clientX: -10 + clientX: -10, }) ); expect(menu.isAttached).to.equal(false); @@ -937,7 +953,7 @@ describe('@lumino/widgets', () => { }); describe('#onActivateRequest', () => { - it('should focus the menu', done => { + it('should focus the menu', (done) => { logMenu.open(0, 0); expect(document.activeElement).to.not.equal(logMenu.node); expect(logMenu.methods).to.not.contain('onActivateRequest'); @@ -1006,7 +1022,7 @@ describe('@lumino/widgets', () => { expect(submenu.isAttached).equal(false); }); - it('should remove the menu from its parent and activate the parent', done => { + it('should remove the menu from its parent and activate the parent', (done) => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); @@ -1277,7 +1293,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-item')).to.equal(true); @@ -1300,7 +1316,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-hidden')).to.equal(true); @@ -1311,7 +1327,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-disabled')).to.equal(true); @@ -1322,7 +1338,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-toggled')).to.equal(true); @@ -1333,7 +1349,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: true, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-active')).to.equal(true); @@ -1344,7 +1360,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: true + collapsed: true, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-collapsed')).to.equal(true); @@ -1357,7 +1373,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderIcon({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemIcon')).to.equal(true); @@ -1371,7 +1387,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderLabel({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); let span = 'Test Label'; @@ -1386,7 +1402,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderShortcut({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemShortcut')).to.equal( @@ -1406,7 +1422,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderSubmenu({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemSubmenuIcon')).to.equal( @@ -1422,7 +1438,7 @@ describe('@lumino/widgets', () => { let name = renderer.createItemClass({ item, active: false, - collapsed: false + collapsed: false, }); let expected = 'lm-Menu-item testClass'; expect(name).to.equal(expected); @@ -1430,7 +1446,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: true, - collapsed: false + collapsed: false, }); expected = 'lm-Menu-item lm-mod-active testClass'; expect(name).to.equal(expected); @@ -1438,7 +1454,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: true + collapsed: true, }); expected = 'lm-Menu-item lm-mod-collapsed testClass'; expect(name).to.equal(expected); @@ -1447,7 +1463,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false + collapsed: false, }); expected = 'lm-Menu-item lm-mod-disabled testClass'; expect(name).to.equal(expected); @@ -1456,7 +1472,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false + collapsed: false, }); expected = 'lm-Menu-item lm-mod-toggled testClass'; expect(name).to.equal(expected); @@ -1465,7 +1481,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false + collapsed: false, }); expected = 'lm-Menu-item lm-mod-hidden testClass'; expect(name).to.equal(expected); @@ -1476,7 +1492,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false + collapsed: false, }); expected = 'lm-Menu-item fooClass'; expect(name).to.equal(expected); @@ -1489,7 +1505,7 @@ describe('@lumino/widgets', () => { let dataset = renderer.createItemDataset({ item, active: false, - collapsed: false + collapsed: false, }); expect(dataset).to.deep.equal({ type: 'command', command: 'test' }); @@ -1497,7 +1513,7 @@ describe('@lumino/widgets', () => { dataset = renderer.createItemDataset({ item, active: false, - collapsed: false + collapsed: false, }); expect(dataset).to.deep.equal({ type: 'separator' }); @@ -1506,7 +1522,7 @@ describe('@lumino/widgets', () => { dataset = renderer.createItemDataset({ item, active: false, - collapsed: false + collapsed: false, }); expect(dataset).to.deep.equal({ type: 'submenu' }); }); @@ -1518,7 +1534,7 @@ describe('@lumino/widgets', () => { let name = renderer.createIconClass({ item, active: false, - collapsed: false + collapsed: false, }); let expected = 'lm-Menu-itemIcon foo'; expect(name).to.equal(expected); @@ -1527,7 +1543,7 @@ describe('@lumino/widgets', () => { name = renderer.createIconClass({ item, active: false, - collapsed: false + collapsed: false, }); expected = 'lm-Menu-itemIcon'; expect(name).to.equal(expected); @@ -1538,7 +1554,7 @@ describe('@lumino/widgets', () => { name = renderer.createIconClass({ item, active: false, - collapsed: false + collapsed: false, }); expected = 'lm-Menu-itemIcon bar'; expect(name).to.equal(expected); @@ -1551,7 +1567,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatLabel({ item, active: false, - collapsed: false + collapsed: false, }); let node = VirtualDOM.realize(h.div(child)); let span = 'Test Label'; @@ -1561,7 +1577,7 @@ describe('@lumino/widgets', () => { child = renderer.formatLabel({ item, active: false, - collapsed: false + collapsed: false, }); expect(child).to.equal(''); @@ -1571,7 +1587,7 @@ describe('@lumino/widgets', () => { child = renderer.formatLabel({ item, active: false, - collapsed: false + collapsed: false, }); expect(child).to.equal('Submenu Label'); }); @@ -1583,7 +1599,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatShortcut({ item, active: false, - collapsed: false + collapsed: false, }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 T'); @@ -1591,6 +1607,21 @@ describe('@lumino/widgets', () => { expect(child).to.equal('Ctrl+T'); } }); + describe('#formatShortcutText()', () => { + it('should format the item aria-label', () => { + let item = menu.addItem({ command: 'test' }); + let child = renderer.formatShortcutText({ + item, + active: false, + collapsed: false, + }); + if (Platform.IS_MAC) { + expect(child).to.equal('\u2303 ,'); + } else { + expect(child).to.equal('Ctrl+Comma'); + } + }); + }); }); }); }); From 8a3cd15c65489e424d4ab5bc026b5016f574325b Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:00:17 +0000 Subject: [PATCH 03/15] test refactor add correct command --- packages/widgets/src/menu.ts | 3 +++ packages/widgets/tests/src/menu.spec.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 7029aaa37..d7464ccba 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -1379,6 +1379,9 @@ export namespace Menu { return kb ? CommandRegistry.formatKeystroke(kb.keys) : null; } + /** + * @returns The aria label content to add to the shortcut node. + */ formatShortcutText(data: IRenderData): h.Child { const keyToText: { [key: string]: string } = { ']': 'Closing bracket', diff --git a/packages/widgets/tests/src/menu.spec.ts b/packages/widgets/tests/src/menu.spec.ts index 55dbfb6bf..3e6cddb37 100644 --- a/packages/widgets/tests/src/menu.spec.ts +++ b/packages/widgets/tests/src/menu.spec.ts @@ -1609,7 +1609,7 @@ describe('@lumino/widgets', () => { }); describe('#formatShortcutText()', () => { it('should format the item aria-label', () => { - let item = menu.addItem({ command: 'test' }); + let item = menu.addItem({ command: 'test-aria' }); let child = renderer.formatShortcutText({ item, active: false, From ad4e98d2e7fb1ff3fea138ea71c6cd2ca0c9fa12 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:39:46 +0000 Subject: [PATCH 04/15] formatting --- packages/widgets/src/menu.ts | 16 ++-- packages/widgets/tests/src/menu.spec.ts | 120 ++++++++++++------------ 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index d7464ccba..2aaadccee 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -27,7 +27,7 @@ import { ElementDataset, h, VirtualDOM, - VirtualElement, + VirtualElement } from '@lumino/virtualdom'; import { Widget } from './widget'; @@ -556,7 +556,7 @@ export class Menu extends Widget { collapsed, onfocus: () => { this.activeIndex = i; - }, + } }); } VirtualDOM.render(content, this.contentNode); @@ -708,7 +708,7 @@ export class Menu extends Widget { */ private _evtMouseMove(event: MouseEvent): void { // Hit test the item nodes for the item under the mouse. - let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return ElementExt.hitTest(node, event.clientX, event.clientY); }); @@ -1177,7 +1177,7 @@ export namespace Menu { dataset, tabindex: '0', onfocus: data.onfocus, - ...aria, + ...aria }, this.renderIcon(data), this.renderLabel(data), @@ -1225,7 +1225,7 @@ export namespace Menu { return h.div( { className: 'lm-Menu-itemShortcut', - 'aria-label': `${ariaContent}`, + 'aria-label': `${ariaContent}` }, content ); @@ -1389,7 +1389,7 @@ export namespace Menu { ',': 'Comma', '.': 'Full stop', "'": 'Single quote', - '-': 'Hyphen-minus', + '-': 'Hyphen-minus' }; let kbText = data.item.keyBinding; @@ -1565,7 +1565,7 @@ namespace Private { pageXOffset: window.pageXOffset, pageYOffset: window.pageYOffset, clientWidth: document.documentElement.clientWidth, - clientHeight: document.documentElement.clientHeight, + clientHeight: document.documentElement.clientHeight }; } @@ -1951,7 +1951,7 @@ namespace Private { if (this.type === 'command') { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { + ArrayExt.findLastValue(this._commands.keyBindings, kb => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); diff --git a/packages/widgets/tests/src/menu.spec.ts b/packages/widgets/tests/src/menu.spec.ts index 3e6cddb37..dfaff03af 100644 --- a/packages/widgets/tests/src/menu.spec.ts +++ b/packages/widgets/tests/src/menu.spec.ts @@ -70,7 +70,7 @@ describe('@lumino/widgets', () => { const renderNode = document.createElement('div'); host.classList.add(iconClass); host.appendChild(renderNode); - }, + } }; before(() => { @@ -83,7 +83,7 @@ describe('@lumino/widgets', () => { iconClass, caption: 'Test Caption', className: 'testClass', - mnemonic: 0, + mnemonic: 0 }); commands.addCommand('test-aria', { execute: (args: JSONObject) => { @@ -94,7 +94,7 @@ describe('@lumino/widgets', () => { iconClass, caption: 'Test Caption', className: 'testClass', - mnemonic: 0, + mnemonic: 0 }); commands.addCommand('test-toggled', { execute: (args: JSONObject) => { @@ -104,7 +104,7 @@ describe('@lumino/widgets', () => { icon: iconRenderer, className: 'testClass', isToggled: (args: JSONObject) => true, - mnemonic: 6, + mnemonic: 6 }); commands.addCommand('test-disabled', { execute: (args: JSONObject) => { @@ -114,7 +114,7 @@ describe('@lumino/widgets', () => { icon: iconRenderer, className: 'testClass', isEnabled: (args: JSONObject) => false, - mnemonic: 5, + mnemonic: 5 }); commands.addCommand('test-hidden', { execute: (args: JSONObject) => { @@ -123,7 +123,7 @@ describe('@lumino/widgets', () => { label: 'Hidden Label', icon: iconRenderer, className: 'testClass', - isVisible: (args: JSONObject) => false, + isVisible: (args: JSONObject) => false }); commands.addCommand('test-zenith', { execute: (args: JSONObject) => { @@ -131,17 +131,17 @@ describe('@lumino/widgets', () => { }, label: 'Zenith Label', icon: iconRenderer, - className: 'testClass', + className: 'testClass' }); commands.addKeyBinding({ keys: ['Ctrl T'], selector: 'body', - command: 'test', + command: 'test' }); commands.addKeyBinding({ keys: ['Ctrl ,'], selector: 'body', - command: 'test-aria', + command: 'test-aria' }); }); @@ -213,7 +213,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 37, // Left arrow + keyCode: 37 // Left arrow }) ); expect(called).to.equal(true); @@ -229,7 +229,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 39, // Right arrow + keyCode: 39 // Right arrow }) ); expect(called).to.equal(true); @@ -253,7 +253,7 @@ describe('@lumino/widgets', () => { submenu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 39, // Right arrow + keyCode: 39 // Right arrow }) ); expect(called).to.equal(true); @@ -637,7 +637,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 13, // Enter + keyCode: 13 // Enter }) ); expect(executed).to.equal('test'); @@ -649,7 +649,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 27, // Escape + keyCode: 27 // Escape }) ); expect(menu.isAttached).to.equal(false); @@ -666,7 +666,7 @@ describe('@lumino/widgets', () => { submenu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 37, // Left arrow + keyCode: 37 // Left arrow }) ); expect(menu.childMenu).to.equal(null); @@ -680,7 +680,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 38, // Up arrow + keyCode: 38 // Up arrow }) ); expect(menu.activeIndex).to.equal(2); @@ -696,7 +696,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 39, // Right arrow + keyCode: 39 // Right arrow }) ); expect(menu.childMenu).to.equal(submenu); @@ -709,7 +709,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 40, // Down arrow + keyCode: 40 // Down arrow }) ); expect(menu.activeIndex).to.equal(0); @@ -734,7 +734,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 70, // `F` key + keyCode: 70 // `F` key }) ); expect(menu.activeIndex).to.equal(0); @@ -751,7 +751,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 90, // `Z` key + keyCode: 90 // `Z` key }) ); expect(menu.activeIndex).to.equal(4); @@ -774,7 +774,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new MouseEvent('mouseup', { bubbles, - button: 1, + button: 1 }) ); expect(executed).to.equal(''); @@ -791,13 +791,13 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top, + clientY: rect.top }) ); expect(menu.activeIndex).to.equal(0); }); - it('should open a child menu after a timeout', (done) => { + it('should open a child menu after a timeout', done => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); submenu.title.label = 'Test Label'; @@ -809,7 +809,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top, + clientY: rect.top }) ); expect(menu.activeIndex).to.equal(0); @@ -820,7 +820,7 @@ describe('@lumino/widgets', () => { }, 500); }); - it('should close an open sub menu', (done) => { + it('should close an open sub menu', done => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); submenu.title.label = 'Test Label'; @@ -835,7 +835,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top, + clientY: rect.top }) ); expect(menu.activeIndex).to.equal(0); @@ -860,7 +860,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousemove', { bubbles, clientX: rect.left, - clientY: rect.top, + clientY: rect.top }) ); expect(menu.activeIndex).to.equal(0); @@ -868,7 +868,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mouseleave', { bubbles, clientX: rect.left, - clientY: rect.top, + clientY: rect.top }) ); expect(menu.activeIndex).to.equal(-1); @@ -886,7 +886,7 @@ describe('@lumino/widgets', () => { new MouseEvent('mousedown', { bubbles, clientX: rect.left, - clientY: rect.top, + clientY: rect.top }) ); expect(menu.isAttached).to.equal(true); @@ -899,7 +899,7 @@ describe('@lumino/widgets', () => { menu.node.dispatchEvent( new MouseEvent('mousedown', { bubbles, - clientX: -10, + clientX: -10 }) ); expect(menu.isAttached).to.equal(false); @@ -953,7 +953,7 @@ describe('@lumino/widgets', () => { }); describe('#onActivateRequest', () => { - it('should focus the menu', (done) => { + it('should focus the menu', done => { logMenu.open(0, 0); expect(document.activeElement).to.not.equal(logMenu.node); expect(logMenu.methods).to.not.contain('onActivateRequest'); @@ -1022,7 +1022,7 @@ describe('@lumino/widgets', () => { expect(submenu.isAttached).equal(false); }); - it('should remove the menu from its parent and activate the parent', (done) => { + it('should remove the menu from its parent and activate the parent', done => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); @@ -1293,7 +1293,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-item')).to.equal(true); @@ -1316,7 +1316,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-hidden')).to.equal(true); @@ -1327,7 +1327,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-disabled')).to.equal(true); @@ -1338,7 +1338,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-toggled')).to.equal(true); @@ -1349,7 +1349,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: true, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-active')).to.equal(true); @@ -1360,7 +1360,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, active: false, - collapsed: true, + collapsed: true }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-collapsed')).to.equal(true); @@ -1373,7 +1373,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderIcon({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemIcon')).to.equal(true); @@ -1387,7 +1387,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderLabel({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); let span = 'Test Label'; @@ -1402,7 +1402,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderShortcut({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemShortcut')).to.equal( @@ -1422,7 +1422,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderSubmenu({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemSubmenuIcon')).to.equal( @@ -1438,7 +1438,7 @@ describe('@lumino/widgets', () => { let name = renderer.createItemClass({ item, active: false, - collapsed: false, + collapsed: false }); let expected = 'lm-Menu-item testClass'; expect(name).to.equal(expected); @@ -1446,7 +1446,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: true, - collapsed: false, + collapsed: false }); expected = 'lm-Menu-item lm-mod-active testClass'; expect(name).to.equal(expected); @@ -1454,7 +1454,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: true, + collapsed: true }); expected = 'lm-Menu-item lm-mod-collapsed testClass'; expect(name).to.equal(expected); @@ -1463,7 +1463,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false, + collapsed: false }); expected = 'lm-Menu-item lm-mod-disabled testClass'; expect(name).to.equal(expected); @@ -1472,7 +1472,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false, + collapsed: false }); expected = 'lm-Menu-item lm-mod-toggled testClass'; expect(name).to.equal(expected); @@ -1481,7 +1481,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false, + collapsed: false }); expected = 'lm-Menu-item lm-mod-hidden testClass'; expect(name).to.equal(expected); @@ -1492,7 +1492,7 @@ describe('@lumino/widgets', () => { name = renderer.createItemClass({ item, active: false, - collapsed: false, + collapsed: false }); expected = 'lm-Menu-item fooClass'; expect(name).to.equal(expected); @@ -1505,7 +1505,7 @@ describe('@lumino/widgets', () => { let dataset = renderer.createItemDataset({ item, active: false, - collapsed: false, + collapsed: false }); expect(dataset).to.deep.equal({ type: 'command', command: 'test' }); @@ -1513,7 +1513,7 @@ describe('@lumino/widgets', () => { dataset = renderer.createItemDataset({ item, active: false, - collapsed: false, + collapsed: false }); expect(dataset).to.deep.equal({ type: 'separator' }); @@ -1522,7 +1522,7 @@ describe('@lumino/widgets', () => { dataset = renderer.createItemDataset({ item, active: false, - collapsed: false, + collapsed: false }); expect(dataset).to.deep.equal({ type: 'submenu' }); }); @@ -1534,7 +1534,7 @@ describe('@lumino/widgets', () => { let name = renderer.createIconClass({ item, active: false, - collapsed: false, + collapsed: false }); let expected = 'lm-Menu-itemIcon foo'; expect(name).to.equal(expected); @@ -1543,7 +1543,7 @@ describe('@lumino/widgets', () => { name = renderer.createIconClass({ item, active: false, - collapsed: false, + collapsed: false }); expected = 'lm-Menu-itemIcon'; expect(name).to.equal(expected); @@ -1554,7 +1554,7 @@ describe('@lumino/widgets', () => { name = renderer.createIconClass({ item, active: false, - collapsed: false, + collapsed: false }); expected = 'lm-Menu-itemIcon bar'; expect(name).to.equal(expected); @@ -1567,7 +1567,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatLabel({ item, active: false, - collapsed: false, + collapsed: false }); let node = VirtualDOM.realize(h.div(child)); let span = 'Test Label'; @@ -1577,7 +1577,7 @@ describe('@lumino/widgets', () => { child = renderer.formatLabel({ item, active: false, - collapsed: false, + collapsed: false }); expect(child).to.equal(''); @@ -1587,7 +1587,7 @@ describe('@lumino/widgets', () => { child = renderer.formatLabel({ item, active: false, - collapsed: false, + collapsed: false }); expect(child).to.equal('Submenu Label'); }); @@ -1599,7 +1599,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatShortcut({ item, active: false, - collapsed: false, + collapsed: false }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 T'); @@ -1613,7 +1613,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatShortcutText({ item, active: false, - collapsed: false, + collapsed: false }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 ,'); From e759582638c7aed587f9ad49363c757d26d6719a Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:55:00 +0000 Subject: [PATCH 05/15] function and test for keyboard shortcuts with punctuation in command palette --- packages/widgets/src/commandpalette.ts | 70 ++++++++-- .../widgets/tests/src/commandpalette.spec.ts | 129 ++++++++++++------ 2 files changed, 145 insertions(+), 54 deletions(-) diff --git a/packages/widgets/src/commandpalette.ts b/packages/widgets/src/commandpalette.ts index 4e2d7ef73..187148beb 100644 --- a/packages/widgets/src/commandpalette.ts +++ b/packages/widgets/src/commandpalette.ts @@ -21,7 +21,7 @@ import { ElementDataset, h, VirtualDOM, - VirtualElement + VirtualElement, } from '@lumino/virtualdom'; import { Widget } from './widget'; @@ -138,8 +138,10 @@ export class CommandPalette extends Widget { * @returns The command items added to the palette. */ addItems(items: CommandPalette.IItemOptions[]): CommandPalette.IItem[] { - const newItems = items.map(item => Private.createItem(this.commands, item)); - newItems.forEach(item => this._items.push(item)); + const newItems = items.map((item) => + Private.createItem(this.commands, item) + ); + newItems.forEach((item) => this._items.push(item)); this.refresh(); return newItems; } @@ -375,7 +377,7 @@ export class CommandPalette extends Widget { } // Find the index of the item which was clicked. - let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { return node.contains(event.target as HTMLElement); }); @@ -784,7 +786,7 @@ export namespace CommandPalette { className, dataset, role: 'menuitemcheckbox', - 'aria-checked': `${data.item.isToggled}` + 'aria-checked': `${data.item.isToggled}`, }, this.renderItemIcon(data), this.renderItemContent(data), @@ -795,7 +797,7 @@ export namespace CommandPalette { { className, dataset, - role: 'menuitem' + role: 'menuitem', }, this.renderItemIcon(data), this.renderItemContent(data), @@ -877,7 +879,14 @@ export namespace CommandPalette { */ renderItemShortcut(data: IItemRenderData): VirtualElement { let content = this.formatItemShortcut(data); - return h.div({ className: 'lm-CommandPalette-itemShortcut' }, content); + let ariaContent = this.formatItemAria(data); + return h.div( + { + className: 'lm-CommandPalette-itemShortcut', + 'aria-label': `${ariaContent}`, + }, + content + ); } /** @@ -973,6 +982,41 @@ export namespace CommandPalette { return kb ? CommandRegistry.formatKeystroke(kb.keys) : null; } + /** + * @returns The aria label content to add to the shortcut node. + */ + formatItemAria(data: IItemRenderData): h.Child { + const keyToText: { [key: string]: string } = { + ']': 'Closing bracket', + '[': 'Opening bracket', + ',': 'Comma', + '.': 'Full stop', + "'": 'Single quote', + '-': 'Hyphen-minus', + }; + + let kbText = data.item.keyBinding; + let result = kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; + + let punctuationRegex = /\p{P}/u; + let punctuations = result?.match(punctuationRegex); + if (!punctuations) { + return []; + } + for (const punctuation of punctuations) { + if (result != null && Object.keys(keyToText).includes(punctuation)) { + const individualKeys = result.split('+'); + let index = individualKeys.indexOf(punctuation); + if (index != -1) { + individualKeys[index] = keyToText[punctuation]; + } + const textShortcut = individualKeys.join('+'); + return textShortcut; + } + } + return kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; + } + /** * Create the render content for the item label node. * @@ -1135,7 +1179,7 @@ namespace Private { Label, Category, Split, - Default + Default, } /** @@ -1193,7 +1237,7 @@ namespace Private { categoryIndices: null, labelIndices: null, score: 0, - item + item, }); continue; } @@ -1292,7 +1336,7 @@ namespace Private { categoryIndices: null, labelIndices, score, - item + item, }; } @@ -1303,7 +1347,7 @@ namespace Private { categoryIndices, labelIndices: null, score, - item + item, }; } @@ -1313,7 +1357,7 @@ namespace Private { categoryIndices, labelIndices, score, - item + item, }; } @@ -1545,7 +1589,7 @@ namespace Private { get keyBinding(): CommandRegistry.IKeyBinding | null { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, kb => { + ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); diff --git a/packages/widgets/tests/src/commandpalette.spec.ts b/packages/widgets/tests/src/commandpalette.spec.ts index c57f8205e..287b1dba2 100644 --- a/packages/widgets/tests/src/commandpalette.spec.ts +++ b/packages/widgets/tests/src/commandpalette.spec.ts @@ -34,7 +34,7 @@ const defaultOptions: CommandPalette.IItemOptions = { command: 'test', category: 'Test Category', args: { foo: 'bar' }, - rank: 42 + rank: 42, }; describe('@lumino/widgets', () => { @@ -120,7 +120,7 @@ describe('@lumino/widgets', () => { command: 'test2', category: 'Test Category', args: { foo: 'bar' }, - rank: 100 + rank: 100, }; expect(palette.items.length).to.equal(0); @@ -213,7 +213,7 @@ describe('@lumino/widgets', () => { isEnabled: () => { called = true; return false; - } + }, }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); @@ -230,7 +230,7 @@ describe('@lumino/widgets', () => { isToggled: () => { called = true; return true; - } + }, }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); @@ -247,7 +247,7 @@ describe('@lumino/widgets', () => { isVisible: () => { called = true; return false; - } + }, }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); @@ -262,7 +262,7 @@ describe('@lumino/widgets', () => { keys: ['Ctrl A'], selector: 'body', command: 'test', - args: defaultOptions.args + args: defaultOptions.args, }); let item = palette.addItem(defaultOptions); expect(item.keyBinding!.keys).to.deep.equal(['Ctrl A']); @@ -325,7 +325,7 @@ describe('@lumino/widgets', () => { it('should handle click, keydown, and input events', () => { let palette = new LogPalette({ commands }); Widget.attach(palette, document.body); - ['click', 'keydown', 'input'].forEach(type => { + ['click', 'keydown', 'input'].forEach((type) => { palette.node.dispatchEvent(new Event(type, { bubbles })); expect(palette.events).to.contain(type); }); @@ -378,7 +378,7 @@ describe('@lumino/widgets', () => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 40 // Down arrow + keyCode: 40, // Down arrow }) ); MessageLoop.flush(); @@ -399,7 +399,7 @@ describe('@lumino/widgets', () => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 38 // Up arrow + keyCode: 38, // Up arrow }) ); MessageLoop.flush(); @@ -419,12 +419,12 @@ describe('@lumino/widgets', () => { let node = content.querySelector('.lm-mod-active'); expect(node).to.equal(null); - ['altKey', 'ctrlKey', 'shiftKey', 'metaKey'].forEach(key => { + ['altKey', 'ctrlKey', 'shiftKey', 'metaKey'].forEach((key) => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, [key]: true, - keyCode: 38 // Up arrow + keyCode: 38, // Up arrow }) ); node = content.querySelector( @@ -438,7 +438,7 @@ describe('@lumino/widgets', () => { it('should trigger active item if enter is pressed', () => { let called = false; commands.addCommand('test', { - execute: () => (called = true) + execute: () => (called = true), }); let content = palette.contentNode; @@ -450,13 +450,13 @@ describe('@lumino/widgets', () => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 40 // Down arrow + keyCode: 40, // Down arrow }) ); palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 13 // Enter + keyCode: 13, // Enter }) ); expect(called).to.equal(true); @@ -465,7 +465,7 @@ describe('@lumino/widgets', () => { context('input', () => { it('should filter the list of visible items', () => { - ['A', 'B', 'C', 'D', 'E'].forEach(name => { + ['A', 'B', 'C', 'D', 'E'].forEach((name) => { commands.addCommand(name, { execute: () => {}, label: name }); palette.addItem({ command: name, category: 'test' }); }); @@ -488,14 +488,14 @@ describe('@lumino/widgets', () => { let categories = ['Z', 'Y']; let names = [ ['A1', 'B2', 'C3', 'D4', 'E5'], - ['F1', 'G2', 'H3', 'I4', 'J5'] + ['F1', 'G2', 'H3', 'I4', 'J5'], ]; names.forEach((values, index) => { - values.forEach(command => { + values.forEach((command) => { palette.addItem({ command, category: categories[index] }); commands.addCommand(command, { execute: () => {}, - label: command + label: command, }); }); }); @@ -545,16 +545,30 @@ describe('@lumino/widgets', () => { className: 'testClass', isEnabled: () => enabledFlag, isToggled: () => toggledFlag, - execute: () => {} + execute: () => {}, }); commands.addKeyBinding({ command: 'test', keys: ['Ctrl A'], - selector: 'body' + selector: 'body', }); + commands.addCommand('test-aria', { + label: 'Test Aria', + caption: 'A simple aria-label test', + className: 'testAriaClass', + isEnabled: () => enabledFlag, + isToggled: () => toggledFlag, + execute: () => {}, + }); + commands.addKeyBinding({ + command: 'test-aria', + keys: ['Ctrl ,'], + selector: 'body', + }); + item = palette.addItem({ command: 'test', - category: 'Test Category' + category: 'Test Category', }); }); @@ -562,7 +576,7 @@ describe('@lumino/widgets', () => { it('should render a header node for the palette', () => { let vNode = renderer.renderHeader({ category: 'Test Category', - indices: null + indices: null, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-header')).to.equal( @@ -574,7 +588,7 @@ describe('@lumino/widgets', () => { it('should mark the matching indices', () => { let vNode = renderer.renderHeader({ category: 'Test Category', - indices: [1, 2, 6, 7, 8] + indices: [1, 2, 6, 7, 8], }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-header')).to.equal( @@ -591,7 +605,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: false + active: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-item')).to.equal( @@ -618,7 +632,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: false + active: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-disabled')).to.equal(true); @@ -629,7 +643,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: false + active: false, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-toggled')).to.equal(true); @@ -639,7 +653,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: true + active: true, }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-active')).to.equal(true); @@ -662,7 +676,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItemShortcut({ item, indices: null, - active: false + active: false, }); let node = VirtualDOM.realize(vNode); expect( @@ -681,7 +695,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItemLabel({ item, indices: [1, 2, 3], - active: false + active: false, }); let node = VirtualDOM.realize(vNode); expect( @@ -696,7 +710,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItemCaption({ item, indices: null, - active: false + active: false, }); let node = VirtualDOM.realize(vNode); expect( @@ -711,7 +725,7 @@ describe('@lumino/widgets', () => { let name = renderer.createItemClass({ item, indices: null, - active: false + active: false, }); let expected = 'lm-CommandPalette-item testClass'; expect(name).to.equal(expected); @@ -723,7 +737,7 @@ describe('@lumino/widgets', () => { let name = renderer.createItemClass({ item, indices: null, - active: true + active: true, }); let expected = 'lm-CommandPalette-item lm-mod-disabled lm-mod-toggled lm-mod-active testClass'; @@ -736,7 +750,7 @@ describe('@lumino/widgets', () => { let dataset = renderer.createItemDataset({ item, indices: null, - active: false + active: false, }); expect(dataset).to.deep.equal({ command: 'test' }); }); @@ -746,11 +760,11 @@ describe('@lumino/widgets', () => { it('should format unmatched header content', () => { let child1 = renderer.formatHeader({ category: 'Test Category', - indices: null + indices: null, }); let child2 = renderer.formatHeader({ category: 'Test Category', - indices: [] + indices: [], }); expect(child1).to.equal('Test Category'); expect(child2).to.equal('Test Category'); @@ -759,7 +773,7 @@ describe('@lumino/widgets', () => { it('should format matched header content', () => { let child = renderer.formatHeader({ category: 'Test Category', - indices: [1, 2, 6, 7, 8] + indices: [1, 2, 6, 7, 8], }); let node = VirtualDOM.realize(h.div(child)); expect(node.innerHTML).to.equal( @@ -780,7 +794,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatItemShortcut({ item, indices: null, - active: false + active: false, }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 A'); @@ -795,12 +809,12 @@ describe('@lumino/widgets', () => { let child1 = renderer.formatItemLabel({ item, indices: null, - active: false + active: false, }); let child2 = renderer.formatItemLabel({ item, indices: [], - active: false + active: false, }); expect(child1).to.equal('Test Command'); expect(child2).to.equal('Test Command'); @@ -810,7 +824,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatItemLabel({ item, indices: [1, 2, 3], - active: false + active: false, }); let node = VirtualDOM.realize(h.div(child)); expect(node.innerHTML).to.equal('Test Command'); @@ -822,11 +836,44 @@ describe('@lumino/widgets', () => { let child = renderer.formatItemCaption({ item, indices: null, - active: false + active: false, }); expect(child).to.equal('A simple test command'); }); }); + + describe('#formatItemShortcut()', () => { + it('should format the item shortcut text', () => { + let child = renderer.formatItemShortcut({ + item, + indices: null, + active: false, + }); + if (Platform.IS_MAC) { + expect(child).to.equal('\u2303 A'); + } else { + expect(child).to.equal('Ctrl+A'); + } + }); + }); + describe('#formatItemAria', () => { + it('should format the item aria-label', () => { + let item = palette.addItem({ + command: 'test-aria', + category: 'Test Category', + }); + let child = renderer.formatItemAria({ + item, + indices: null, + active: false, + }); + if (Platform.IS_MAC) { + expect(child).to.equal('\u2303 ,'); + } else { + expect(child).to.equal('Ctrl+Comma'); + } + }); + }); }); }); }); From a4663342964ec99d0030a9b9f16000bf0de7f510 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:55:54 +0000 Subject: [PATCH 06/15] function and test for keyboard shortcuts with punctuation in command palette --- packages/widgets/src/commandpalette.ts | 30 +++--- .../widgets/tests/src/commandpalette.spec.ts | 92 +++++++++---------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/packages/widgets/src/commandpalette.ts b/packages/widgets/src/commandpalette.ts index 187148beb..f8fe702a1 100644 --- a/packages/widgets/src/commandpalette.ts +++ b/packages/widgets/src/commandpalette.ts @@ -21,7 +21,7 @@ import { ElementDataset, h, VirtualDOM, - VirtualElement, + VirtualElement } from '@lumino/virtualdom'; import { Widget } from './widget'; @@ -138,10 +138,8 @@ export class CommandPalette extends Widget { * @returns The command items added to the palette. */ addItems(items: CommandPalette.IItemOptions[]): CommandPalette.IItem[] { - const newItems = items.map((item) => - Private.createItem(this.commands, item) - ); - newItems.forEach((item) => this._items.push(item)); + const newItems = items.map(item => Private.createItem(this.commands, item)); + newItems.forEach(item => this._items.push(item)); this.refresh(); return newItems; } @@ -377,7 +375,7 @@ export class CommandPalette extends Widget { } // Find the index of the item which was clicked. - let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return node.contains(event.target as HTMLElement); }); @@ -786,7 +784,7 @@ export namespace CommandPalette { className, dataset, role: 'menuitemcheckbox', - 'aria-checked': `${data.item.isToggled}`, + 'aria-checked': `${data.item.isToggled}` }, this.renderItemIcon(data), this.renderItemContent(data), @@ -797,7 +795,7 @@ export namespace CommandPalette { { className, dataset, - role: 'menuitem', + role: 'menuitem' }, this.renderItemIcon(data), this.renderItemContent(data), @@ -883,7 +881,7 @@ export namespace CommandPalette { return h.div( { className: 'lm-CommandPalette-itemShortcut', - 'aria-label': `${ariaContent}`, + 'aria-label': `${ariaContent}` }, content ); @@ -992,7 +990,7 @@ export namespace CommandPalette { ',': 'Comma', '.': 'Full stop', "'": 'Single quote', - '-': 'Hyphen-minus', + '-': 'Hyphen-minus' }; let kbText = data.item.keyBinding; @@ -1179,7 +1177,7 @@ namespace Private { Label, Category, Split, - Default, + Default } /** @@ -1237,7 +1235,7 @@ namespace Private { categoryIndices: null, labelIndices: null, score: 0, - item, + item }); continue; } @@ -1336,7 +1334,7 @@ namespace Private { categoryIndices: null, labelIndices, score, - item, + item }; } @@ -1347,7 +1345,7 @@ namespace Private { categoryIndices, labelIndices: null, score, - item, + item }; } @@ -1357,7 +1355,7 @@ namespace Private { categoryIndices, labelIndices, score, - item, + item }; } @@ -1589,7 +1587,7 @@ namespace Private { get keyBinding(): CommandRegistry.IKeyBinding | null { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { + ArrayExt.findLastValue(this._commands.keyBindings, kb => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); diff --git a/packages/widgets/tests/src/commandpalette.spec.ts b/packages/widgets/tests/src/commandpalette.spec.ts index 287b1dba2..97d4a2adb 100644 --- a/packages/widgets/tests/src/commandpalette.spec.ts +++ b/packages/widgets/tests/src/commandpalette.spec.ts @@ -34,7 +34,7 @@ const defaultOptions: CommandPalette.IItemOptions = { command: 'test', category: 'Test Category', args: { foo: 'bar' }, - rank: 42, + rank: 42 }; describe('@lumino/widgets', () => { @@ -120,7 +120,7 @@ describe('@lumino/widgets', () => { command: 'test2', category: 'Test Category', args: { foo: 'bar' }, - rank: 100, + rank: 100 }; expect(palette.items.length).to.equal(0); @@ -213,7 +213,7 @@ describe('@lumino/widgets', () => { isEnabled: () => { called = true; return false; - }, + } }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); @@ -230,7 +230,7 @@ describe('@lumino/widgets', () => { isToggled: () => { called = true; return true; - }, + } }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); @@ -247,7 +247,7 @@ describe('@lumino/widgets', () => { isVisible: () => { called = true; return false; - }, + } }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); @@ -262,7 +262,7 @@ describe('@lumino/widgets', () => { keys: ['Ctrl A'], selector: 'body', command: 'test', - args: defaultOptions.args, + args: defaultOptions.args }); let item = palette.addItem(defaultOptions); expect(item.keyBinding!.keys).to.deep.equal(['Ctrl A']); @@ -325,7 +325,7 @@ describe('@lumino/widgets', () => { it('should handle click, keydown, and input events', () => { let palette = new LogPalette({ commands }); Widget.attach(palette, document.body); - ['click', 'keydown', 'input'].forEach((type) => { + ['click', 'keydown', 'input'].forEach(type => { palette.node.dispatchEvent(new Event(type, { bubbles })); expect(palette.events).to.contain(type); }); @@ -378,7 +378,7 @@ describe('@lumino/widgets', () => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 40, // Down arrow + keyCode: 40 // Down arrow }) ); MessageLoop.flush(); @@ -399,7 +399,7 @@ describe('@lumino/widgets', () => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 38, // Up arrow + keyCode: 38 // Up arrow }) ); MessageLoop.flush(); @@ -419,12 +419,12 @@ describe('@lumino/widgets', () => { let node = content.querySelector('.lm-mod-active'); expect(node).to.equal(null); - ['altKey', 'ctrlKey', 'shiftKey', 'metaKey'].forEach((key) => { + ['altKey', 'ctrlKey', 'shiftKey', 'metaKey'].forEach(key => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, [key]: true, - keyCode: 38, // Up arrow + keyCode: 38 // Up arrow }) ); node = content.querySelector( @@ -438,7 +438,7 @@ describe('@lumino/widgets', () => { it('should trigger active item if enter is pressed', () => { let called = false; commands.addCommand('test', { - execute: () => (called = true), + execute: () => (called = true) }); let content = palette.contentNode; @@ -450,13 +450,13 @@ describe('@lumino/widgets', () => { palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 40, // Down arrow + keyCode: 40 // Down arrow }) ); palette.node.dispatchEvent( new KeyboardEvent('keydown', { bubbles, - keyCode: 13, // Enter + keyCode: 13 // Enter }) ); expect(called).to.equal(true); @@ -465,7 +465,7 @@ describe('@lumino/widgets', () => { context('input', () => { it('should filter the list of visible items', () => { - ['A', 'B', 'C', 'D', 'E'].forEach((name) => { + ['A', 'B', 'C', 'D', 'E'].forEach(name => { commands.addCommand(name, { execute: () => {}, label: name }); palette.addItem({ command: name, category: 'test' }); }); @@ -488,14 +488,14 @@ describe('@lumino/widgets', () => { let categories = ['Z', 'Y']; let names = [ ['A1', 'B2', 'C3', 'D4', 'E5'], - ['F1', 'G2', 'H3', 'I4', 'J5'], + ['F1', 'G2', 'H3', 'I4', 'J5'] ]; names.forEach((values, index) => { - values.forEach((command) => { + values.forEach(command => { palette.addItem({ command, category: categories[index] }); commands.addCommand(command, { execute: () => {}, - label: command, + label: command }); }); }); @@ -545,12 +545,12 @@ describe('@lumino/widgets', () => { className: 'testClass', isEnabled: () => enabledFlag, isToggled: () => toggledFlag, - execute: () => {}, + execute: () => {} }); commands.addKeyBinding({ command: 'test', keys: ['Ctrl A'], - selector: 'body', + selector: 'body' }); commands.addCommand('test-aria', { label: 'Test Aria', @@ -558,17 +558,17 @@ describe('@lumino/widgets', () => { className: 'testAriaClass', isEnabled: () => enabledFlag, isToggled: () => toggledFlag, - execute: () => {}, + execute: () => {} }); commands.addKeyBinding({ command: 'test-aria', keys: ['Ctrl ,'], - selector: 'body', + selector: 'body' }); item = palette.addItem({ command: 'test', - category: 'Test Category', + category: 'Test Category' }); }); @@ -576,7 +576,7 @@ describe('@lumino/widgets', () => { it('should render a header node for the palette', () => { let vNode = renderer.renderHeader({ category: 'Test Category', - indices: null, + indices: null }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-header')).to.equal( @@ -588,7 +588,7 @@ describe('@lumino/widgets', () => { it('should mark the matching indices', () => { let vNode = renderer.renderHeader({ category: 'Test Category', - indices: [1, 2, 6, 7, 8], + indices: [1, 2, 6, 7, 8] }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-header')).to.equal( @@ -605,7 +605,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: false, + active: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-item')).to.equal( @@ -632,7 +632,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: false, + active: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-disabled')).to.equal(true); @@ -643,7 +643,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: false, + active: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-toggled')).to.equal(true); @@ -653,7 +653,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItem({ item, indices: null, - active: true, + active: true }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-active')).to.equal(true); @@ -676,7 +676,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItemShortcut({ item, indices: null, - active: false, + active: false }); let node = VirtualDOM.realize(vNode); expect( @@ -695,7 +695,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItemLabel({ item, indices: [1, 2, 3], - active: false, + active: false }); let node = VirtualDOM.realize(vNode); expect( @@ -710,7 +710,7 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderItemCaption({ item, indices: null, - active: false, + active: false }); let node = VirtualDOM.realize(vNode); expect( @@ -725,7 +725,7 @@ describe('@lumino/widgets', () => { let name = renderer.createItemClass({ item, indices: null, - active: false, + active: false }); let expected = 'lm-CommandPalette-item testClass'; expect(name).to.equal(expected); @@ -737,7 +737,7 @@ describe('@lumino/widgets', () => { let name = renderer.createItemClass({ item, indices: null, - active: true, + active: true }); let expected = 'lm-CommandPalette-item lm-mod-disabled lm-mod-toggled lm-mod-active testClass'; @@ -750,7 +750,7 @@ describe('@lumino/widgets', () => { let dataset = renderer.createItemDataset({ item, indices: null, - active: false, + active: false }); expect(dataset).to.deep.equal({ command: 'test' }); }); @@ -760,11 +760,11 @@ describe('@lumino/widgets', () => { it('should format unmatched header content', () => { let child1 = renderer.formatHeader({ category: 'Test Category', - indices: null, + indices: null }); let child2 = renderer.formatHeader({ category: 'Test Category', - indices: [], + indices: [] }); expect(child1).to.equal('Test Category'); expect(child2).to.equal('Test Category'); @@ -773,7 +773,7 @@ describe('@lumino/widgets', () => { it('should format matched header content', () => { let child = renderer.formatHeader({ category: 'Test Category', - indices: [1, 2, 6, 7, 8], + indices: [1, 2, 6, 7, 8] }); let node = VirtualDOM.realize(h.div(child)); expect(node.innerHTML).to.equal( @@ -794,7 +794,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatItemShortcut({ item, indices: null, - active: false, + active: false }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 A'); @@ -809,12 +809,12 @@ describe('@lumino/widgets', () => { let child1 = renderer.formatItemLabel({ item, indices: null, - active: false, + active: false }); let child2 = renderer.formatItemLabel({ item, indices: [], - active: false, + active: false }); expect(child1).to.equal('Test Command'); expect(child2).to.equal('Test Command'); @@ -824,7 +824,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatItemLabel({ item, indices: [1, 2, 3], - active: false, + active: false }); let node = VirtualDOM.realize(h.div(child)); expect(node.innerHTML).to.equal('Test Command'); @@ -836,7 +836,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatItemCaption({ item, indices: null, - active: false, + active: false }); expect(child).to.equal('A simple test command'); }); @@ -847,7 +847,7 @@ describe('@lumino/widgets', () => { let child = renderer.formatItemShortcut({ item, indices: null, - active: false, + active: false }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 A'); @@ -860,12 +860,12 @@ describe('@lumino/widgets', () => { it('should format the item aria-label', () => { let item = palette.addItem({ command: 'test-aria', - category: 'Test Category', + category: 'Test Category' }); let child = renderer.formatItemAria({ item, indices: null, - active: false, + active: false }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 ,'); From cffd4aa9cd221d77e423b0dc958539c6618c70d2 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:55:58 +0000 Subject: [PATCH 07/15] Api checks --- review/api/widgets.api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/review/api/widgets.api.md b/review/api/widgets.api.md index 7462b947b..678c5b592 100644 --- a/review/api/widgets.api.md +++ b/review/api/widgets.api.md @@ -245,6 +245,8 @@ export namespace CommandPalette { createItemDataset(data: IItemRenderData): ElementDataset; formatEmptyMessage(data: IEmptyMessageRenderData): h.Child; formatHeader(data: IHeaderRenderData): h.Child; + // (undocumented) + formatItemAria(data: IItemRenderData): h.Child; formatItemCaption(data: IItemRenderData): h.Child; formatItemLabel(data: IItemRenderData): h.Child; formatItemShortcut(data: IItemRenderData): h.Child; @@ -763,6 +765,8 @@ export namespace Menu { createItemDataset(data: IRenderData): ElementDataset; formatLabel(data: IRenderData): h.Child; formatShortcut(data: IRenderData): h.Child; + // (undocumented) + formatShortcutText(data: IRenderData): h.Child; renderIcon(data: IRenderData): VirtualElement; renderItem(data: IRenderData): VirtualElement; renderLabel(data: IRenderData): VirtualElement; From ebc90579b9a65960c5f8b65407b040f01e5b436a Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Tue, 2 Jan 2024 16:25:12 +0000 Subject: [PATCH 08/15] Moved repeated logic to be called from command registry --- packages/commands/src/index.ts | 42 ++++++++++++++++++++++++++ packages/widgets/src/commandpalette.ts | 40 ++++++------------------ packages/widgets/src/menu.ts | 34 +++------------------ 3 files changed, 56 insertions(+), 60 deletions(-) diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 55f243362..47b042f06 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -1269,6 +1269,48 @@ export namespace CommandRegistry { mods.push(key); return mods.join(' '); } + + /** + * Format keystroke aria labels. + * + * If a list of keystrokes includes puntuation, it will create an + * aria label and substitue symbol values to text. + * + * @param keystroke The keystrokes to format + * @returns The keystrokes representation + */ + export function formatKeystrokeAriaLabel( + keystroke: readonly string[] + ): string { + const keyToText: { [key: string]: string } = { + ']': 'Closing bracket', + '[': 'Opening bracket', + ',': 'Comma', + '.': 'Full stop', + "'": 'Single quote', + '-': 'Hyphen-minus' + }; + + let result = CommandRegistry.formatKeystroke(keystroke); + + let punctuationRegex = /\p{P}/u; + let punctuations = result?.match(punctuationRegex); + if (!punctuations) { + return result; + } + for (const punctuation of punctuations) { + if (result != null && Object.keys(keyToText).includes(punctuation)) { + const individualKeys = result.split('+'); + let index = individualKeys.indexOf(punctuation); + if (index != -1) { + individualKeys[index] = keyToText[punctuation]; + } + const textShortcut = individualKeys.join('+'); + return textShortcut; + } + } + return result; + } } /** diff --git a/packages/widgets/src/commandpalette.ts b/packages/widgets/src/commandpalette.ts index f8fe702a1..a379c7ba8 100644 --- a/packages/widgets/src/commandpalette.ts +++ b/packages/widgets/src/commandpalette.ts @@ -138,8 +138,10 @@ export class CommandPalette extends Widget { * @returns The command items added to the palette. */ addItems(items: CommandPalette.IItemOptions[]): CommandPalette.IItem[] { - const newItems = items.map(item => Private.createItem(this.commands, item)); - newItems.forEach(item => this._items.push(item)); + const newItems = items.map((item) => + Private.createItem(this.commands, item) + ); + newItems.forEach((item) => this._items.push(item)); this.refresh(); return newItems; } @@ -375,7 +377,7 @@ export class CommandPalette extends Widget { } // Find the index of the item which was clicked. - let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { return node.contains(event.target as HTMLElement); }); @@ -984,35 +986,11 @@ export namespace CommandPalette { * @returns The aria label content to add to the shortcut node. */ formatItemAria(data: IItemRenderData): h.Child { - const keyToText: { [key: string]: string } = { - ']': 'Closing bracket', - '[': 'Opening bracket', - ',': 'Comma', - '.': 'Full stop', - "'": 'Single quote', - '-': 'Hyphen-minus' - }; - let kbText = data.item.keyBinding; - let result = kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; - let punctuationRegex = /\p{P}/u; - let punctuations = result?.match(punctuationRegex); - if (!punctuations) { - return []; - } - for (const punctuation of punctuations) { - if (result != null && Object.keys(keyToText).includes(punctuation)) { - const individualKeys = result.split('+'); - let index = individualKeys.indexOf(punctuation); - if (index != -1) { - individualKeys[index] = keyToText[punctuation]; - } - const textShortcut = individualKeys.join('+'); - return textShortcut; - } - } - return kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; + return kbText + ? CommandRegistry.formatKeystrokeAriaLabel(kbText.keys) + : null; } /** @@ -1587,7 +1565,7 @@ namespace Private { get keyBinding(): CommandRegistry.IKeyBinding | null { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, kb => { + ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 2aaadccee..f7beeacec 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -708,7 +708,7 @@ export class Menu extends Widget { */ private _evtMouseMove(event: MouseEvent): void { // Hit test the item nodes for the item under the mouse. - let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { return ElementExt.hitTest(node, event.clientX, event.clientY); }); @@ -1383,35 +1383,11 @@ export namespace Menu { * @returns The aria label content to add to the shortcut node. */ formatShortcutText(data: IRenderData): h.Child { - const keyToText: { [key: string]: string } = { - ']': 'Closing bracket', - '[': 'Opening bracket', - ',': 'Comma', - '.': 'Full stop', - "'": 'Single quote', - '-': 'Hyphen-minus' - }; - let kbText = data.item.keyBinding; - let result = kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; - let punctuationRegex = /\p{P}/u; - let punctuations = result?.match(punctuationRegex); - if (!punctuations) { - return []; - } - for (const punctuation of punctuations) { - if (result != null && Object.keys(keyToText).includes(punctuation)) { - const individualKeys = result.split('+'); - let index = individualKeys.indexOf(punctuation); - if (index != -1) { - individualKeys[index] = keyToText[punctuation]; - } - const textShortcut = individualKeys.join('+'); - return textShortcut; - } - } - return kbText ? CommandRegistry.formatKeystroke(kbText.keys) : null; + return kbText + ? CommandRegistry.formatKeystrokeAriaLabel(kbText.keys) + : null; } } @@ -1951,7 +1927,7 @@ namespace Private { if (this.type === 'command') { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, kb => { + ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); From 0da36054c433b1e63d000c2bcb8b7c863fe47f5a Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Tue, 2 Jan 2024 16:27:45 +0000 Subject: [PATCH 09/15] Formatting --- packages/widgets/src/commandpalette.ts | 10 ++++------ packages/widgets/src/menu.ts | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/widgets/src/commandpalette.ts b/packages/widgets/src/commandpalette.ts index a379c7ba8..135a0153c 100644 --- a/packages/widgets/src/commandpalette.ts +++ b/packages/widgets/src/commandpalette.ts @@ -138,10 +138,8 @@ export class CommandPalette extends Widget { * @returns The command items added to the palette. */ addItems(items: CommandPalette.IItemOptions[]): CommandPalette.IItem[] { - const newItems = items.map((item) => - Private.createItem(this.commands, item) - ); - newItems.forEach((item) => this._items.push(item)); + const newItems = items.map(item => Private.createItem(this.commands, item)); + newItems.forEach(item => this._items.push(item)); this.refresh(); return newItems; } @@ -377,7 +375,7 @@ export class CommandPalette extends Widget { } // Find the index of the item which was clicked. - let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return node.contains(event.target as HTMLElement); }); @@ -1565,7 +1563,7 @@ namespace Private { get keyBinding(): CommandRegistry.IKeyBinding | null { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { + ArrayExt.findLastValue(this._commands.keyBindings, kb => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index f7beeacec..d97ebbd3d 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -708,7 +708,7 @@ export class Menu extends Widget { */ private _evtMouseMove(event: MouseEvent): void { // Hit test the item nodes for the item under the mouse. - let index = ArrayExt.findFirstIndex(this.contentNode.children, (node) => { + let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return ElementExt.hitTest(node, event.clientX, event.clientY); }); @@ -1927,7 +1927,7 @@ namespace Private { if (this.type === 'command') { let { command, args } = this; return ( - ArrayExt.findLastValue(this._commands.keyBindings, (kb) => { + ArrayExt.findLastValue(this._commands.keyBindings, kb => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); From 3e72e0ee9b1ca5eee9a38a7b376c73605bb4dfa8 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:14:48 +0000 Subject: [PATCH 10/15] API update --- review/api/commands.api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/review/api/commands.api.md b/review/api/commands.api.md index 81ed057ee..7e31324ff 100644 --- a/review/api/commands.api.md +++ b/review/api/commands.api.md @@ -49,6 +49,7 @@ export namespace CommandRegistry { args: ReadonlyJSONObject | null; }; export function formatKeystroke(keystroke: string | readonly string[]): string; + export function formatKeystrokeAriaLabel(keystroke: readonly string[]): string; export interface ICommandChangedArgs { readonly id: string | undefined; readonly type: 'added' | 'removed' | 'changed' | 'many-changed'; From 4f99e9c67d63b07f6312ba4d0301dedec5234574 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:16:12 +0000 Subject: [PATCH 11/15] keyToText passed to the renderer constructor so values can be changed --- packages/commands/src/index.ts | 12 ++++++++---- packages/widgets/src/commandpalette.ts | 9 +++++++-- packages/widgets/src/menu.ts | 8 +++++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 47b042f06..d553dd146 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -1280,9 +1280,10 @@ export namespace CommandRegistry { * @returns The keystrokes representation */ export function formatKeystrokeAriaLabel( - keystroke: readonly string[] + keystroke: readonly string[], + keyToText?: { [key: string]: string } ): string { - const keyToText: { [key: string]: string } = { + const internalKeyToText = keyToText ?? { ']': 'Closing bracket', '[': 'Opening bracket', ',': 'Comma', @@ -1299,11 +1300,14 @@ export namespace CommandRegistry { return result; } for (const punctuation of punctuations) { - if (result != null && Object.keys(keyToText).includes(punctuation)) { + if ( + result != null && + Object.keys(internalKeyToText).includes(punctuation) + ) { const individualKeys = result.split('+'); let index = individualKeys.indexOf(punctuation); if (index != -1) { - individualKeys[index] = keyToText[punctuation]; + individualKeys[index] = internalKeyToText[punctuation]; } const textShortcut = individualKeys.join('+'); return textShortcut; diff --git a/packages/widgets/src/commandpalette.ts b/packages/widgets/src/commandpalette.ts index 135a0153c..bfbc82835 100644 --- a/packages/widgets/src/commandpalette.ts +++ b/packages/widgets/src/commandpalette.ts @@ -39,6 +39,7 @@ export class CommandPalette extends Widget { super({ node: Private.createNode() }); this.addClass('lm-CommandPalette'); this.setFlag(Widget.Flag.DisallowLayout); + this.keyToText = options.renderer?.keyToText; this.commands = options.commands; this.renderer = options.renderer || CommandPalette.defaultRenderer; this.commands.commandChanged.connect(this._onGenericChange, this); @@ -64,6 +65,8 @@ export class CommandPalette extends Widget { */ readonly renderer: CommandPalette.IRenderer; + readonly keyToText: CommandPalette.IRenderer['keyToText']; + /** * The command palette search node. * @@ -750,12 +753,15 @@ export namespace CommandPalette { * @returns A virtual element representing the message. */ renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement; + + keyToText?: { [key: string]: string }; } /** * The default implementation of `IRenderer`. */ export class Renderer implements IRenderer { + keyToText?: { [key: string]: string }; /** * Render the virtual element for a command palette header. * @@ -985,9 +991,8 @@ export namespace CommandPalette { */ formatItemAria(data: IItemRenderData): h.Child { let kbText = data.item.keyBinding; - return kbText - ? CommandRegistry.formatKeystrokeAriaLabel(kbText.keys) + ? CommandRegistry.formatKeystrokeAriaLabel(kbText.keys, this.keyToText) : null; } diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index d97ebbd3d..292313f6b 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -52,6 +52,7 @@ export class Menu extends Widget { super({ node: Private.createNode() }); this.addClass('lm-Menu'); this.setFlag(Widget.Flag.DisallowLayout); + this.keyToText = options.renderer?.keyToText; this.commands = options.commands; this.renderer = options.renderer || Menu.defaultRenderer; } @@ -65,6 +66,8 @@ export class Menu extends Widget { super.dispose(); } + readonly keyToText: Menu.IRenderer['keyToText']; + /** * A signal emitted just before the menu is closed. * @@ -1151,6 +1154,8 @@ export namespace Menu { * @returns A virtual element representing the item. */ renderItem(data: IRenderData): VirtualElement; + + keyToText?: { [key: string]: string }; } /** @@ -1160,6 +1165,7 @@ export namespace Menu { * Subclasses are free to reimplement rendering methods as needed. */ export class Renderer implements IRenderer { + keyToText?: { [key: string]: string }; /** * Render the virtual element for a menu item. * @@ -1386,7 +1392,7 @@ export namespace Menu { let kbText = data.item.keyBinding; return kbText - ? CommandRegistry.formatKeystrokeAriaLabel(kbText.keys) + ? CommandRegistry.formatKeystrokeAriaLabel(kbText.keys, this.keyToText) : null; } } From 3a1ac1542447153885fda3e30f13605f8f8ec270 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:32:22 +0000 Subject: [PATCH 12/15] api update --- review/api/commands.api.md | 4 +++- review/api/widgets.api.md | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/review/api/commands.api.md b/review/api/commands.api.md index 7e31324ff..6f10048b2 100644 --- a/review/api/commands.api.md +++ b/review/api/commands.api.md @@ -49,7 +49,9 @@ export namespace CommandRegistry { args: ReadonlyJSONObject | null; }; export function formatKeystroke(keystroke: string | readonly string[]): string; - export function formatKeystrokeAriaLabel(keystroke: readonly string[]): string; + export function formatKeystrokeAriaLabel(keystroke: readonly string[], keyToText?: { + [key: string]: string; + }): string; export interface ICommandChangedArgs { readonly id: string | undefined; readonly type: 'added' | 'removed' | 'changed' | 'many-changed'; diff --git a/review/api/widgets.api.md b/review/api/widgets.api.md index 678c5b592..59b327813 100644 --- a/review/api/widgets.api.md +++ b/review/api/widgets.api.md @@ -180,6 +180,8 @@ export class CommandPalette extends Widget { handleEvent(event: Event): void; get inputNode(): HTMLInputElement; get items(): ReadonlyArray; + // (undocumented) + readonly keyToText: CommandPalette.IRenderer['keyToText']; protected onActivateRequest(msg: Message): void; protected onAfterDetach(msg: Message): void; protected onAfterShow(msg: Message): void; @@ -235,6 +237,10 @@ export namespace CommandPalette { renderer?: IRenderer; } export interface IRenderer { + // (undocumented) + keyToText?: { + [key: string]: string; + }; renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement; renderHeader(data: IHeaderRenderData): VirtualElement; renderItem(data: IItemRenderData): VirtualElement; @@ -250,6 +256,10 @@ export namespace CommandPalette { formatItemCaption(data: IItemRenderData): h.Child; formatItemLabel(data: IItemRenderData): h.Child; formatItemShortcut(data: IItemRenderData): h.Child; + // (undocumented) + keyToText?: { + [key: string]: string; + }; renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement; renderHeader(data: IHeaderRenderData): VirtualElement; renderItem(data: IItemRenderData): VirtualElement; From 6a8f2e037de68654ee6909a4e588ac8fc074b79b Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Thu, 4 Jan 2024 09:51:48 +0000 Subject: [PATCH 13/15] documentation --- packages/commands/src/index.ts | 1 + packages/widgets/src/commandpalette.ts | 12 +++++++++++- packages/widgets/src/menu.ts | 13 +++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index d553dd146..4c6c4a209 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -1277,6 +1277,7 @@ export namespace CommandRegistry { * aria label and substitue symbol values to text. * * @param keystroke The keystrokes to format + * @param keyToText - Optional object for converting punctuation. * @returns The keystrokes representation */ export function formatKeystrokeAriaLabel( diff --git a/packages/widgets/src/commandpalette.ts b/packages/widgets/src/commandpalette.ts index bfbc82835..fbcc7e80f 100644 --- a/packages/widgets/src/commandpalette.ts +++ b/packages/widgets/src/commandpalette.ts @@ -64,7 +64,9 @@ export class CommandPalette extends Widget { * The renderer used by the command palette. */ readonly renderer: CommandPalette.IRenderer; - + /** + * The optional object used for translation of aria label punctuation. + */ readonly keyToText: CommandPalette.IRenderer['keyToText']; /** @@ -754,6 +756,9 @@ export namespace CommandPalette { */ renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement; + /** + * The optional object used for translation of aria label punctuation. + */ keyToText?: { [key: string]: string }; } @@ -761,6 +766,9 @@ export namespace CommandPalette { * The default implementation of `IRenderer`. */ export class Renderer implements IRenderer { + /** + * The optional object used for translation of aria label punctuation. + */ keyToText?: { [key: string]: string }; /** * Render the virtual element for a command palette header. @@ -987,6 +995,8 @@ export namespace CommandPalette { } /** + * @param data - The data to use for the aria label content. + * * @returns The aria label content to add to the shortcut node. */ formatItemAria(data: IItemRenderData): h.Child { diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 292313f6b..8927c69ab 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -65,7 +65,9 @@ export class Menu extends Widget { this._items.length = 0; super.dispose(); } - + /** + * The optional object used for translation of aria label punctuation. + */ readonly keyToText: Menu.IRenderer['keyToText']; /** @@ -1154,7 +1156,9 @@ export namespace Menu { * @returns A virtual element representing the item. */ renderItem(data: IRenderData): VirtualElement; - + /** + * The optional object used for translation of aria label punctuation. + */ keyToText?: { [key: string]: string }; } @@ -1165,6 +1169,9 @@ export namespace Menu { * Subclasses are free to reimplement rendering methods as needed. */ export class Renderer implements IRenderer { + /** + * The optional object used for translation of aria label punctuation. + */ keyToText?: { [key: string]: string }; /** * Render the virtual element for a menu item. @@ -1386,6 +1393,8 @@ export namespace Menu { } /** + * @param data - The data to use for the aria label content. + * * @returns The aria label content to add to the shortcut node. */ formatShortcutText(data: IRenderData): h.Child { From a747e97746d66d9e6a258e2e7fff436aa81a5f95 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:34:49 +0000 Subject: [PATCH 14/15] api update --- review/api/application.api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/review/api/application.api.md b/review/api/application.api.md index 31b0d2168..3c5637c1a 100644 --- a/review/api/application.api.md +++ b/review/api/application.api.md @@ -17,6 +17,8 @@ export class Application { activatePlugin(id: string): Promise; protected addEventListeners(): void; protected attachShell(id: string): void; + get bubblingKeydown(): boolean; + set bubblingKeydown(value: boolean); readonly commands: CommandRegistry; readonly contextMenu: ContextMenu; deactivatePlugin(id: string): Promise; From f013646c92d174692964fc78b02810a31ed36119 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:49:05 +0000 Subject: [PATCH 15/15] api update --- review/api/application.api.md | 2 -- review/api/widgets.api.md | 10 +++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/review/api/application.api.md b/review/api/application.api.md index 3c5637c1a..31b0d2168 100644 --- a/review/api/application.api.md +++ b/review/api/application.api.md @@ -17,8 +17,6 @@ export class Application { activatePlugin(id: string): Promise; protected addEventListeners(): void; protected attachShell(id: string): void; - get bubblingKeydown(): boolean; - set bubblingKeydown(value: boolean); readonly commands: CommandRegistry; readonly contextMenu: ContextMenu; deactivatePlugin(id: string): Promise; diff --git a/review/api/widgets.api.md b/review/api/widgets.api.md index 59b327813..d0e1af01d 100644 --- a/review/api/widgets.api.md +++ b/review/api/widgets.api.md @@ -180,7 +180,6 @@ export class CommandPalette extends Widget { handleEvent(event: Event): void; get inputNode(): HTMLInputElement; get items(): ReadonlyArray; - // (undocumented) readonly keyToText: CommandPalette.IRenderer['keyToText']; protected onActivateRequest(msg: Message): void; protected onAfterDetach(msg: Message): void; @@ -237,7 +236,6 @@ export namespace CommandPalette { renderer?: IRenderer; } export interface IRenderer { - // (undocumented) keyToText?: { [key: string]: string; }; @@ -256,7 +254,6 @@ export namespace CommandPalette { formatItemCaption(data: IItemRenderData): h.Child; formatItemLabel(data: IItemRenderData): h.Child; formatItemShortcut(data: IItemRenderData): h.Child; - // (undocumented) keyToText?: { [key: string]: string; }; @@ -707,6 +704,7 @@ export class Menu extends Widget { handleEvent(event: Event): void; insertItem(index: number, options: Menu.IItemOptions): Menu.IItem; get items(): ReadonlyArray; + readonly keyToText: Menu.IRenderer['keyToText']; get leafMenu(): Menu; get menuRequested(): ISignal; protected onActivateRequest(msg: Message): void; @@ -765,6 +763,9 @@ export namespace Menu { readonly onfocus?: () => void; } export interface IRenderer { + keyToText?: { + [key: string]: string; + }; renderItem(data: IRenderData): VirtualElement; } export type ItemType = 'command' | 'submenu' | 'separator'; @@ -777,6 +778,9 @@ export namespace Menu { formatShortcut(data: IRenderData): h.Child; // (undocumented) formatShortcutText(data: IRenderData): h.Child; + keyToText?: { + [key: string]: string; + }; renderIcon(data: IRenderData): VirtualElement; renderItem(data: IRenderData): VirtualElement; renderLabel(data: IRenderData): VirtualElement;