diff --git a/CHANGELOG.md b/CHANGELOG.md
index b62469558..5946622d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [1997](https://github.com/microsoft/BotFramework-Emulator/pull/1997)
- [1999](https://github.com/microsoft/BotFramework-Emulator/pull/1999)
- [2000](https://github.com/microsoft/BotFramework-Emulator/pull/2000)
+ - [2001](https://github.com/microsoft/BotFramework-Emulator/pull/2001)
- [2009](https://github.com/microsoft/BotFramework-Emulator/pull/2009)
- [2010](https://github.com/microsoft/BotFramework-Emulator/pull/2010)
diff --git a/packages/app/client/src/utils/eventHandlers.spec.ts b/packages/app/client/src/utils/eventHandlers.spec.ts
index 782de0bad..61d98d571 100644
--- a/packages/app/client/src/utils/eventHandlers.spec.ts
+++ b/packages/app/client/src/utils/eventHandlers.spec.ts
@@ -84,6 +84,19 @@ jest.mock('electron', () => ({
},
}));
+const mockDOM = `
+
+
+
+
+
+
+
+
+`;
+
describe('#globalHandlers', () => {
let commandService: CommandServiceImpl;
@@ -253,4 +266,30 @@ describe('#globalHandlers', () => {
expect(mockRemoteCommandsCalled).toHaveLength(1);
expect(mockRemoteCommandsCalled[0].commandName).toBe(ToggleDevTools);
});
+
+ it('should move focus to first element when Tab is pressed', async () => {
+ const event = new KeyboardEvent('keydown', { key: 'Tab' });
+ Object.defineProperty(process, 'platform', { value: 'darwin' });
+
+ document.body.innerHTML = mockDOM;
+ var mockFirstElement = document.getElementById('navBtn');
+ var mockLastElement = document.getElementById('btn3');
+ mockLastElement.focus();
+ await globalHandlers(event);
+
+ expect(document.activeElement.id).toBe(mockFirstElement.id);
+ });
+
+ it('should move focus to last element when Shift+Tab is pressed', async () => {
+ const event = new KeyboardEvent('keydown', { shiftKey: true, key: 'Tab' });
+ Object.defineProperty(process, 'platform', { value: 'darwin' });
+
+ document.body.innerHTML = mockDOM;
+ var mockFirstElement = document.getElementById('navBtn');
+ var mockLastElement = document.getElementById('btn3');
+ mockFirstElement.focus();
+ await globalHandlers(event);
+
+ expect(document.activeElement.id).toBe(mockLastElement.id);
+ });
});
diff --git a/packages/app/client/src/utils/eventHandlers.ts b/packages/app/client/src/utils/eventHandlers.ts
index ec8447058..faaf04f2c 100644
--- a/packages/app/client/src/utils/eventHandlers.ts
+++ b/packages/app/client/src/utils/eventHandlers.ts
@@ -30,23 +30,45 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-
import { Notification, NotificationType, SharedConstants } from '@bfemulator/app-shared';
import { CommandServiceImpl, CommandServiceInstance } from '@bfemulator/sdk-shared';
import { remote } from 'electron';
+import { isMac } from '../../../../app/main/src/utils/platform';
const maxZoomFactor = 3; // 300%
const minZoomFactor = 0.25; // 25%;
-
class EventHandlers {
@CommandServiceInstance()
public static commandService: CommandServiceImpl;
+ private static getLastChildWithChildren(node) {
+ for (let index = node.children.length; index > 0; index--) {
+ if (node.children[index - 1].children.length > 0 && !node.children[index - 1].hidden) {
+ return node.children[index - 1];
+ }
+ }
+ }
+
+ private static getLastDecendants(node, list = []) {
+ if (node.children.length > 0) {
+ const child = this.getLastChildWithChildren(node);
+ if (child) {
+ list.push(child);
+ this.getLastDecendants(child, list);
+ }
+ }
+
+ const result = [].filter.call(list[list.length - 1].children, element => !element.hasAttribute('disabled'));
+
+ return result;
+ }
+
public static async globalHandles(event: KeyboardEvent): Promise {
// Meta corresponds to 'Command' on Mac
const ctrlOrCmdPressed = event.ctrlKey || event.metaKey;
const shiftPressed = event.shiftKey;
const key = event.key.toLowerCase();
+ const keyCode = event.keyCode;
const {
Commands: {
Electron: { ToggleDevTools },
@@ -54,23 +76,19 @@ class EventHandlers {
Notifications: { Add },
},
} = SharedConstants;
-
let awaitable: Promise;
// Ctrl+O
if (ctrlOrCmdPressed && key === 'o') {
awaitable = EventHandlers.commandService.call(ShowOpenBotDialog);
}
-
// Ctrl+N
if (ctrlOrCmdPressed && key === 'n') {
awaitable = EventHandlers.commandService.call(ShowBotCreationDialog);
}
-
// Ctrl+0
if (ctrlOrCmdPressed && key === '0') {
remote.getCurrentWebContents().setZoomLevel(0);
}
-
// Ctrl+= or Ctrl+Shift+=
if (ctrlOrCmdPressed && (key === '=' || key === '+')) {
const webContents = remote.getCurrentWebContents();
@@ -83,7 +101,6 @@ class EventHandlers {
}
});
}
-
// Ctrl+- or Ctrl+Shift+-
if (ctrlOrCmdPressed && (key === '-' || key === '_')) {
const webContents = remote.getCurrentWebContents();
@@ -96,18 +113,15 @@ class EventHandlers {
}
});
}
-
// F11
if (key === 'f11') {
const currentWindow = remote.getCurrentWindow();
currentWindow.setFullScreen(!currentWindow.isFullScreen());
}
-
// Ctrl+Shift+I
if (ctrlOrCmdPressed && shiftPressed && key === 'i') {
awaitable = EventHandlers.commandService.remoteCall(ToggleDevTools);
}
-
if (awaitable) {
// Prevents the char from showing up if an input is focused
event.preventDefault();
@@ -121,7 +135,25 @@ class EventHandlers {
} as Notification);
}
}
+
+ if (isMac()) {
+ const tabPressed: boolean = key === 'tab';
+ const lastDecendants = EventHandlers.getLastDecendants(document.querySelector('main'));
+ const firstElement = document.querySelector('nav').firstElementChild as HTMLElement;
+ const lastElement = lastDecendants[lastDecendants.length - 1] as HTMLElement;
+ const isFirstElement: boolean = document.activeElement === firstElement;
+ const isLastElement: boolean = document.activeElement === lastElement;
+
+ if (tabPressed) {
+ if (shiftPressed && isFirstElement) {
+ lastElement.focus();
+ event.preventDefault();
+ } else if (!shiftPressed && isLastElement) {
+ firstElement.focus();
+ event.preventDefault();
+ }
+ }
+ }
}
}
-
export const globalHandlers = EventHandlers.globalHandles;