refactor: handling keyboard shortcuts (#9242)
* fix: Resolve and go next keyboard shortcuts doesn't work * refactor: use buildHotKeys instead of hasPressedCommandPlusAltAndEKey * feat: install tinykeys * refactor: use tinykeys * test: update buildKeyEvents * fix: remove stray import * feat: handle action list globally * feat: allow configuring `allowOnFocusedInput` * chore: Navigate chat list item * chore: Navigate dashboard * feat: Navigate editor top panel * feat: Toggle file upload * chore: More keyboard shortcuts * chore: Update mention selection mixin * chore: Phone input * chore: Clean up * chore: Clean up * chore: Dropdown and editor * chore: Enter key to send and clean up * chore: Rename mixin * chore: Review fixes * chore: Removed unused shortcut from modal * fix: Specs --------- Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -84,9 +84,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleKeyboardEvent(e) {
|
||||
this.processKeyDownEvent(e);
|
||||
},
|
||||
adjustScroll() {},
|
||||
onHover(index) {
|
||||
this.selectedIndex = index;
|
||||
},
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyboardEvent);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyboardEvent);
|
||||
},
|
||||
mixins: [keyboardEventListenerMixins],
|
||||
methods: {
|
||||
moveSelectionUp() {
|
||||
if (!this.selectedIndex) {
|
||||
@@ -14,6 +9,7 @@ export default {
|
||||
} else {
|
||||
this.selectedIndex -= 1;
|
||||
}
|
||||
this.adjustScroll();
|
||||
},
|
||||
moveSelectionDown() {
|
||||
if (this.selectedIndex === this.items.length - 1) {
|
||||
@@ -21,19 +17,46 @@ export default {
|
||||
} else {
|
||||
this.selectedIndex += 1;
|
||||
}
|
||||
this.adjustScroll();
|
||||
},
|
||||
processKeyDownEvent(e) {
|
||||
const keyPattern = buildHotKeys(e);
|
||||
if (['arrowup', 'ctrl+p'].includes(keyPattern)) {
|
||||
this.moveSelectionUp();
|
||||
e.preventDefault();
|
||||
} else if (['arrowdown', 'ctrl+n'].includes(keyPattern)) {
|
||||
this.moveSelectionDown();
|
||||
e.preventDefault();
|
||||
} else if (keyPattern === 'enter') {
|
||||
this.onSelect();
|
||||
e.preventDefault();
|
||||
}
|
||||
getKeyboardEvents() {
|
||||
return {
|
||||
ArrowUp: {
|
||||
action: e => {
|
||||
this.moveSelectionUp();
|
||||
e.preventDefault();
|
||||
},
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
'Control+KeyP': {
|
||||
action: e => {
|
||||
this.moveSelectionUp();
|
||||
e.preventDefault();
|
||||
},
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
ArrowDown: {
|
||||
action: e => {
|
||||
this.moveSelectionDown();
|
||||
e.preventDefault();
|
||||
},
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
'Control+KeyN': {
|
||||
action: e => {
|
||||
this.moveSelectionDown();
|
||||
e.preventDefault();
|
||||
},
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
Enter: {
|
||||
action: e => {
|
||||
this.onSelect();
|
||||
e.preventDefault();
|
||||
},
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,64 +1,69 @@
|
||||
import mentionSelectionKeyboardMixin from '../mentionSelectionKeyboardMixin';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
const buildComponent = ({ data = {}, methods = {} }) => ({
|
||||
render() {},
|
||||
data() {
|
||||
return data;
|
||||
return { ...data, selectedIndex: 0, items: [1, 2, 3] };
|
||||
},
|
||||
methods,
|
||||
mixins: [mentionSelectionKeyboardMixin],
|
||||
methods: { ...methods, onSelect: jest.fn(), adjustScroll: jest.fn() },
|
||||
mixins: [keyboardEventListenerMixins],
|
||||
});
|
||||
|
||||
describe('mentionSelectionKeyboardMixin', () => {
|
||||
test('register listeners', () => {
|
||||
jest.spyOn(document, 'addEventListener');
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = buildComponent({});
|
||||
shallowMount(Component);
|
||||
// undefined expected as the method is not defined in the component
|
||||
expect(document.addEventListener).toHaveBeenCalledWith(
|
||||
'keydown',
|
||||
undefined
|
||||
);
|
||||
wrapper = shallowMount(Component, { localVue });
|
||||
});
|
||||
|
||||
test('processKeyDownEvent updates index on arrow up', () => {
|
||||
const Component = buildComponent({
|
||||
data: { selectedIndex: 0, items: [1, 2, 3] },
|
||||
});
|
||||
const wrapper = shallowMount(Component);
|
||||
wrapper.vm.processKeyDownEvent({
|
||||
ctrlKey: true,
|
||||
key: 'p',
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
expect(wrapper.vm.selectedIndex).toBe(2);
|
||||
it('ArrowUp and Control+KeyP update selectedIndex correctly', () => {
|
||||
const preventDefault = jest.fn();
|
||||
const keyboardEvents = wrapper.vm.getKeyboardEvents();
|
||||
|
||||
if (keyboardEvents && keyboardEvents.ArrowUp) {
|
||||
keyboardEvents.ArrowUp.action({ preventDefault });
|
||||
expect(wrapper.vm.selectedIndex).toBe(2);
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
}
|
||||
|
||||
wrapper.setData({ selectedIndex: 1 });
|
||||
if (keyboardEvents && keyboardEvents['Control+KeyP']) {
|
||||
keyboardEvents['Control+KeyP'].action({ preventDefault });
|
||||
expect(wrapper.vm.selectedIndex).toBe(0);
|
||||
expect(preventDefault).toHaveBeenCalledTimes(2);
|
||||
}
|
||||
});
|
||||
|
||||
test('processKeyDownEvent updates index on arrow down', () => {
|
||||
const Component = buildComponent({
|
||||
data: { selectedIndex: 0, items: [1, 2, 3] },
|
||||
});
|
||||
const wrapper = shallowMount(Component);
|
||||
wrapper.vm.processKeyDownEvent({
|
||||
key: 'ArrowDown',
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
expect(wrapper.vm.selectedIndex).toBe(1);
|
||||
it('ArrowDown and Control+KeyN update selectedIndex correctly', () => {
|
||||
const preventDefault = jest.fn();
|
||||
const keyboardEvents = wrapper.vm.getKeyboardEvents();
|
||||
|
||||
if (keyboardEvents && keyboardEvents.ArrowDown) {
|
||||
keyboardEvents.ArrowDown.action({ preventDefault });
|
||||
expect(wrapper.vm.selectedIndex).toBe(1);
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
}
|
||||
|
||||
wrapper.setData({ selectedIndex: 1 });
|
||||
if (keyboardEvents && keyboardEvents['Control+KeyN']) {
|
||||
keyboardEvents['Control+KeyN'].action({ preventDefault });
|
||||
expect(wrapper.vm.selectedIndex).toBe(2);
|
||||
expect(preventDefault).toHaveBeenCalledTimes(2);
|
||||
}
|
||||
});
|
||||
|
||||
test('processKeyDownEvent calls select methods on Enter Key', () => {
|
||||
const onSelectMockFn = jest.fn();
|
||||
const Component = buildComponent({
|
||||
data: { selectedIndex: 0, items: [1, 2, 3] },
|
||||
methods: { onSelect: () => onSelectMockFn('enterKey pressed') },
|
||||
});
|
||||
const wrapper = shallowMount(Component);
|
||||
wrapper.vm.processKeyDownEvent({
|
||||
key: 'Enter',
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
expect(onSelectMockFn).toHaveBeenCalledWith('enterKey pressed');
|
||||
wrapper.vm.onSelect();
|
||||
it('Enter key triggers onSelect method', () => {
|
||||
const preventDefault = jest.fn();
|
||||
const keyboardEvents = wrapper.vm.getKeyboardEvents();
|
||||
|
||||
if (keyboardEvents && keyboardEvents.Enter) {
|
||||
keyboardEvents.Enter.action({ preventDefault });
|
||||
expect(wrapper.vm.onSelect).toHaveBeenCalled();
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user