refactor: useKeyboardEvents composable (#9959)

This PR has the following changes

1. Fix tab styles issue caused by adding an additional wrapper for
getting an element ref on `ChatTypeTabs.vue`
2. Refactor `useKeyboardEvents` composable to not require an element
ref. It will use a local abort controller to abort any listener

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
Shivam Mishra
2024-08-22 16:40:55 +05:30
committed by GitHub
parent 776579ba5b
commit dadd572f9d
22 changed files with 56 additions and 160 deletions

View File

@@ -141,7 +141,7 @@ export default {
allowOnFocusedInput: true, allowOnFocusedInput: true,
}, },
}; };
useKeyboardEvents(keyboardEvents, conversationListRef); useKeyboardEvents(keyboardEvents);
return { return {
uiSettings, uiSettings,

View File

@@ -19,7 +19,6 @@ const store = useStore();
const getters = useStoreGetters(); const getters = useStoreGetters();
const { t } = useI18n(); const { t } = useI18n();
const resolveActionsRef = ref(null);
const arrowDownButtonRef = ref(null); const arrowDownButtonRef = ref(null);
const isLoading = ref(false); const isLoading = ref(false);
@@ -131,17 +130,14 @@ const keyboardEvents = {
}, },
}; };
useKeyboardEvents(keyboardEvents, resolveActionsRef); useKeyboardEvents(keyboardEvents);
useEmitter(CMD_REOPEN_CONVERSATION, onCmdOpenConversation); useEmitter(CMD_REOPEN_CONVERSATION, onCmdOpenConversation);
useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation); useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
</script> </script>
<template> <template>
<div <div class="relative flex items-center justify-end resolve-actions">
ref="resolveActionsRef"
class="relative flex items-center justify-end resolve-actions"
>
<div class="button-group"> <div class="button-group">
<woot-button <woot-button
v-if="isOpen" v-if="isOpen"

View File

@@ -1,5 +1,4 @@
<script> <script>
import { ref } from 'vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { getSidebarItems } from './config/default-sidebar'; import { getSidebarItems } from './config/default-sidebar';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
@@ -29,7 +28,6 @@ export default {
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const sidebarRef = ref(null);
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -67,11 +65,10 @@ export default {
action: () => navigateToRoute('agent_list'), action: () => navigateToRoute('agent_list'),
}, },
}; };
useKeyboardEvents(keyboardEvents, sidebarRef); useKeyboardEvents(keyboardEvents);
return { return {
toggleKeyShortcutModal, toggleKeyShortcutModal,
sidebarRef,
}; };
}, },
data() { data() {
@@ -201,7 +198,7 @@ export default {
</script> </script>
<template> <template>
<aside ref="sidebarRef" class="flex h-full"> <aside class="flex h-full">
<PrimarySidebar <PrimarySidebar
:logo-source="globalConfig.logoThumbnail" :logo-source="globalConfig.logoThumbnail"
:installation-name="globalConfig.installationName" :installation-name="globalConfig.installationName"

View File

@@ -23,7 +23,6 @@ export default {
const { isAdmin } = useAdmin(); const { isAdmin } = useAdmin();
const aiAssistanceButtonRef = ref(null);
const initialMessage = ref(''); const initialMessage = ref('');
const initializeMessage = draftMsg => { const initializeMessage = draftMsg => {
@@ -40,13 +39,12 @@ export default {
allowOnFocusedInput: true, allowOnFocusedInput: true,
}, },
}; };
useKeyboardEvents(keyboardEvents, aiAssistanceButtonRef); useKeyboardEvents(keyboardEvents);
return { return {
uiSettings, uiSettings,
updateUISettings, updateUISettings,
isAdmin, isAdmin,
aiAssistanceButtonRef,
initialMessage, initialMessage,
initializeMessage, initializeMessage,
recordAnalytics, recordAnalytics,
@@ -122,7 +120,7 @@ export default {
</script> </script>
<template> <template>
<div ref="aiAssistanceButtonRef"> <div>
<div v-if="isAIIntegrationEnabled" class="relative"> <div v-if="isAIIntegrationEnabled" class="relative">
<AIAssistanceCTAButton <AIAssistanceCTAButton
v-if="shouldShowAIAssistCTAButton" v-if="shouldShowAIAssistCTAButton"

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { computed } from 'vue';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import wootConstants from 'dashboard/constants/globals'; import wootConstants from 'dashboard/constants/globals';
@@ -16,8 +16,6 @@ const props = defineProps({
const emit = defineEmits(['chatTabChange']); const emit = defineEmits(['chatTabChange']);
const chatTypeTabsRef = ref(null);
const activeTabIndex = computed(() => { const activeTabIndex = computed(() => {
return props.items.findIndex(item => item.key === props.activeTab); return props.items.findIndex(item => item.key === props.activeTab);
}); });
@@ -39,24 +37,23 @@ const keyboardEvents = {
}, },
}, },
}; };
useKeyboardEvents(keyboardEvents, chatTypeTabsRef);
useKeyboardEvents(keyboardEvents);
</script> </script>
<template> <template>
<div ref="chatTypeTabsRef"> <woot-tabs
<woot-tabs :index="activeTabIndex"
:index="activeTabIndex" class="w-full px-4 py-0 tab--chat-type"
class="tab--chat-type py-0 px-4 w-full" @change="onTabChange"
@change="onTabChange" >
> <woot-tabs-item
<woot-tabs-item v-for="item in items"
v-for="item in items" :key="item.key"
:key="item.key" :name="item.name"
:name="item.name" :count="item.count"
:count="item.count" />
/> </woot-tabs>
</woot-tabs>
</div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -18,8 +18,6 @@ const props = defineProps({
const emit = defineEmits(['add', 'remove']); const emit = defineEmits(['add', 'remove']);
const labelSelectorWrapRef = ref(null);
const { isAdmin } = useAdmin(); const { isAdmin } = useAdmin();
const showSearchDropdownLabel = ref(false); const showSearchDropdownLabel = ref(false);
@@ -57,15 +55,11 @@ const keyboardEvents = {
}, },
}; };
useKeyboardEvents(keyboardEvents, labelSelectorWrapRef); useKeyboardEvents(keyboardEvents);
</script> </script>
<template> <template>
<div <div v-on-clickaway="closeDropdownLabel" class="relative leading-6">
ref="labelSelectorWrapRef"
v-on-clickaway="closeDropdownLabel"
class="relative leading-6"
>
<AddLabel @add="toggleLabels" /> <AddLabel @add="toggleLabels" />
<woot-label <woot-label
v-for="label in savedLabels" v-for="label in savedLabels"

View File

@@ -1,5 +1,4 @@
<script> <script>
import { ref, watchEffect, computed } from 'vue';
import { useUISettings } from 'dashboard/composables/useUISettings'; import { useUISettings } from 'dashboard/composables/useUISettings';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import FileUpload from 'vue-upload-component'; import FileUpload from 'vue-upload-component';
@@ -117,15 +116,13 @@ export default {
const { setSignatureFlagForInbox, fetchSignatureFlagFromUISettings } = const { setSignatureFlagForInbox, fetchSignatureFlagFromUISettings } =
useUISettings(); useUISettings();
const uploadRef = ref(null);
// TODO: This is really hacky, we need to replace the file picker component with
// a custom one, where the logic and the component markup is isolated.
// Once we have the custom component, we can remove the hacky logic below.
const uploadRefElem = computed(() => uploadRef.value?.$el);
const keyboardEvents = { const keyboardEvents = {
'Alt+KeyA': { 'Alt+KeyA': {
action: () => { action: () => {
// TODO: This is really hacky, we need to replace the file picker component with
// a custom one, where the logic and the component markup is isolated.
// Once we have the custom component, we can remove the hacky logic below.
const uploadTriggerButton = document.querySelector( const uploadTriggerButton = document.querySelector(
'#conversationAttachment' '#conversationAttachment'
); );
@@ -135,14 +132,11 @@ export default {
}, },
}; };
watchEffect(() => { useKeyboardEvents(keyboardEvents);
useKeyboardEvents(keyboardEvents, uploadRefElem);
});
return { return {
setSignatureFlagForInbox, setSignatureFlagForInbox,
fetchSignatureFlagFromUISettings, fetchSignatureFlagFromUISettings,
uploadRef,
}; };
}, },
computed: { computed: {
@@ -267,7 +261,6 @@ export default {
@click="toggleEmojiPicker" @click="toggleEmojiPicker"
/> />
<FileUpload <FileUpload
ref="uploadRef"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')" v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
input-id="conversationAttachment" input-id="conversationAttachment"
:size="4096 * 4096" :size="4096 * 4096"

View File

@@ -1,5 +1,4 @@
<script> <script>
import { ref } from 'vue';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants'; import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
export default { export default {
@@ -23,8 +22,6 @@ export default {
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const replyTopRef = ref(null);
const setReplyMode = mode => { const setReplyMode = mode => {
emit('setReplyMode', mode); emit('setReplyMode', mode);
}; };
@@ -44,12 +41,11 @@ export default {
allowOnFocusedInput: true, allowOnFocusedInput: true,
}, },
}; };
useKeyboardEvents(keyboardEvents, replyTopRef); useKeyboardEvents(keyboardEvents);
return { return {
handleReplyClick, handleReplyClick,
handleNoteClick, handleNoteClick,
replyTopRef,
}; };
}, },
computed: { computed: {
@@ -76,10 +72,7 @@ export default {
</script> </script>
<template> <template>
<div <div class="flex justify-between bg-black-50 dark:bg-slate-800">
ref="replyTopRef"
class="flex justify-between bg-black-50 dark:bg-slate-800"
>
<div class="button-group"> <div class="button-group">
<woot-button <woot-button
variant="clear" variant="clear"

View File

@@ -1,5 +1,4 @@
<script> <script>
import { ref } from 'vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import BackButton from '../BackButton.vue'; import BackButton from '../BackButton.vue';
@@ -43,18 +42,12 @@ export default {
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const conversationHeaderActionsRef = ref(null);
const keyboardEvents = { const keyboardEvents = {
'Alt+KeyO': { 'Alt+KeyO': {
action: () => emit('contactPanelToggle'), action: () => emit('contactPanelToggle'),
}, },
}; };
useKeyboardEvents(keyboardEvents, conversationHeaderActionsRef); useKeyboardEvents(keyboardEvents);
return {
conversationHeaderActionsRef,
};
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
@@ -182,7 +175,6 @@ export default {
</div> </div>
<div <div
ref="conversationHeaderActionsRef"
class="flex items-center gap-2 overflow-hidden text-xs conversation--header--actions text-ellipsis whitespace-nowrap" class="flex items-center gap-2 overflow-hidden text-xs conversation--header--actions text-ellipsis whitespace-nowrap"
> >
<InboxName v-if="hasMultipleInboxes" :inbox="inbox" /> <InboxName v-if="hasMultipleInboxes" :inbox="inbox" />

View File

@@ -52,7 +52,6 @@ export default {
}, },
}, },
setup() { setup() {
const conversationFooterRef = ref(null);
const isPopOutReplyBox = ref(false); const isPopOutReplyBox = ref(false);
const { isEnterprise } = useConfig(); const { isEnterprise } = useConfig();
@@ -70,7 +69,7 @@ export default {
}, },
}; };
useKeyboardEvents(keyboardEvents, conversationFooterRef); useKeyboardEvents(keyboardEvents);
const { const {
isAIIntegrationEnabled, isAIIntegrationEnabled,
@@ -81,7 +80,6 @@ export default {
return { return {
isEnterprise, isEnterprise,
conversationFooterRef,
isPopOutReplyBox, isPopOutReplyBox,
closePopOutReplyBox, closePopOutReplyBox,
showPopOutReplyBox, showPopOutReplyBox,
@@ -529,7 +527,6 @@ export default {
/> />
</ul> </ul>
<div <div
ref="conversationFooterRef"
class="conversation-footer" class="conversation-footer"
:class="{ 'modal-mask': isPopOutReplyBox }" :class="{ 'modal-mask': isPopOutReplyBox }"
> >

View File

@@ -40,7 +40,6 @@ const onSelect = () => {
}; };
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef: tagAgentsRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,

View File

@@ -35,7 +35,6 @@ const ALLOWED_FILE_TYPES = {
const MAX_ZOOM_LEVEL = 2; const MAX_ZOOM_LEVEL = 2;
const MIN_ZOOM_LEVEL = 1; const MIN_ZOOM_LEVEL = 1;
const galleryViewRef = ref(null);
const zoomScale = ref(1); const zoomScale = ref(1);
const activeAttachment = ref({}); const activeAttachment = ref({});
const activeFileType = ref(''); const activeFileType = ref('');
@@ -202,7 +201,7 @@ const keyboardEvents = {
}, },
}, },
}; };
useKeyboardEvents(keyboardEvents, galleryViewRef); useKeyboardEvents(keyboardEvents);
onMounted(() => { onMounted(() => {
setImageAndVideoSrc(props.attachment); setImageAndVideoSrc(props.attachment);
@@ -218,7 +217,6 @@ onMounted(() => {
:on-close="onClose" :on-close="onClose"
> >
<div <div
ref="galleryViewRef"
v-on-clickaway="onClose" v-on-clickaway="onClose"
class="bg-white dark:bg-slate-900 flex flex-col h-[inherit] w-[inherit] overflow-hidden" class="bg-white dark:bg-slate-900 flex flex-col h-[inherit] w-[inherit] overflow-hidden"
@click="onClose" @click="onClose"

View File

@@ -39,7 +39,6 @@ const onSelect = () => {
}; };
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef: mentionsListContainerRef,
items: computed(() => props.items), items: computed(() => props.items),
onSelect, onSelect,
adjustScroll, adjustScroll,

View File

@@ -1,4 +1,3 @@
import { unref } from 'vue';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
describe('useKeyboardEvents', () => { describe('useKeyboardEvents', () => {
@@ -11,15 +10,13 @@ describe('useKeyboardEvents', () => {
}); });
it('should set up listeners on mount and remove them on unmount', async () => { it('should set up listeners on mount and remove them on unmount', async () => {
const el = document.createElement('div');
const elRef = unref({ value: el });
const events = { const events = {
'ALT+KeyL': () => {}, 'ALT+KeyL': () => {},
}; };
const mountedMock = vi.fn(); const mountedMock = vi.fn();
const unmountedMock = vi.fn(); const unmountedMock = vi.fn();
useKeyboardEvents(events, elRef); useKeyboardEvents(events);
mountedMock(); mountedMock();
unmountedMock(); unmountedMock();

View File

@@ -9,7 +9,6 @@ vi.mock('../useKeyboardEvents', () => ({
})); }));
describe('useKeyboardNavigableList', () => { describe('useKeyboardNavigableList', () => {
let elementRef;
let items; let items;
let onSelect; let onSelect;
let adjustScroll; let adjustScroll;
@@ -18,7 +17,6 @@ describe('useKeyboardNavigableList', () => {
const createMockEvent = () => ({ preventDefault: vi.fn() }); const createMockEvent = () => ({ preventDefault: vi.fn() });
beforeEach(() => { beforeEach(() => {
elementRef = ref(null);
items = ref(['item1', 'item2', 'item3']); items = ref(['item1', 'item2', 'item3']);
onSelect = vi.fn(); onSelect = vi.fn();
adjustScroll = vi.fn(); adjustScroll = vi.fn();
@@ -28,7 +26,6 @@ describe('useKeyboardNavigableList', () => {
it('should return moveSelectionUp and moveSelectionDown functions', () => { it('should return moveSelectionUp and moveSelectionDown functions', () => {
const result = useKeyboardNavigableList({ const result = useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -43,7 +40,6 @@ describe('useKeyboardNavigableList', () => {
it('should move selection up correctly', () => { it('should move selection up correctly', () => {
const { moveSelectionUp } = useKeyboardNavigableList({ const { moveSelectionUp } = useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -65,7 +61,6 @@ describe('useKeyboardNavigableList', () => {
it('should move selection down correctly', () => { it('should move selection down correctly', () => {
const { moveSelectionDown } = useKeyboardNavigableList({ const { moveSelectionDown } = useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -87,7 +82,6 @@ describe('useKeyboardNavigableList', () => {
it('should call adjustScroll after moving selection', () => { it('should call adjustScroll after moving selection', () => {
const { moveSelectionUp, moveSelectionDown } = useKeyboardNavigableList({ const { moveSelectionUp, moveSelectionDown } = useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -103,7 +97,6 @@ describe('useKeyboardNavigableList', () => {
it('should include Enter key handler when onSelect is provided', () => { it('should include Enter key handler when onSelect is provided', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -118,7 +111,6 @@ describe('useKeyboardNavigableList', () => {
it('should not include Enter key handler when onSelect is not provided', () => { it('should not include Enter key handler when onSelect is not provided', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
adjustScroll, adjustScroll,
selectedIndex, selectedIndex,
@@ -131,7 +123,6 @@ describe('useKeyboardNavigableList', () => {
it('should not trigger onSelect when items are empty', () => { it('should not trigger onSelect when items are empty', () => {
const { moveSelectionUp, moveSelectionDown } = useKeyboardNavigableList({ const { moveSelectionUp, moveSelectionDown } = useKeyboardNavigableList({
elementRef,
items: ref([]), items: ref([]),
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -145,23 +136,18 @@ describe('useKeyboardNavigableList', () => {
it('should call useKeyboardEvents with correct parameters', () => { it('should call useKeyboardEvents with correct parameters', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
selectedIndex, selectedIndex,
}); });
expect(useKeyboardEvents).toHaveBeenCalledWith( expect(useKeyboardEvents).toHaveBeenCalledWith(expect.any(Object));
expect.any(Object),
elementRef
);
}); });
// Keyboard event handlers // Keyboard event handlers
it('should handle ArrowUp key', () => { it('should handle ArrowUp key', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -178,7 +164,6 @@ describe('useKeyboardNavigableList', () => {
it('should handle Control+KeyP', () => { it('should handle Control+KeyP', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -195,7 +180,6 @@ describe('useKeyboardNavigableList', () => {
it('should handle ArrowDown key', () => { it('should handle ArrowDown key', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -212,7 +196,6 @@ describe('useKeyboardNavigableList', () => {
it('should handle Control+KeyN', () => { it('should handle Control+KeyN', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -229,7 +212,6 @@ describe('useKeyboardNavigableList', () => {
it('should handle Enter key when onSelect is provided', () => { it('should handle Enter key when onSelect is provided', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -245,7 +227,6 @@ describe('useKeyboardNavigableList', () => {
it('should not have Enter key handler when onSelect is not provided', () => { it('should not have Enter key handler when onSelect is not provided', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
adjustScroll, adjustScroll,
selectedIndex, selectedIndex,
@@ -257,7 +238,6 @@ describe('useKeyboardNavigableList', () => {
it('should set allowOnFocusedInput to true for all key handlers', () => { it('should set allowOnFocusedInput to true for all key handlers', () => {
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,

View File

@@ -1,4 +1,3 @@
import { onMounted, onBeforeUnmount, unref } from 'vue';
import { import {
isActiveElementTypeable, isActiveElementTypeable,
isEscape, isEscape,
@@ -7,8 +6,7 @@ import {
} from 'shared/helpers/KeyboardHelpers'; } from 'shared/helpers/KeyboardHelpers';
import { useDetectKeyboardLayout } from 'dashboard/composables/useDetectKeyboardLayout'; import { useDetectKeyboardLayout } from 'dashboard/composables/useDetectKeyboardLayout';
import { createKeybindingsHandler } from 'tinykeys'; import { createKeybindingsHandler } from 'tinykeys';
import { onUnmounted, onMounted } from 'vue';
const keyboardListenerMap = new WeakMap();
/** /**
* Determines if the keyboard event should be ignored based on the element type and handler settings. * Determines if the keyboard event should be ignored based on the element type and handler settings.
@@ -69,49 +67,24 @@ async function wrapEventsInKeybindingsHandler(events) {
return wrappedEvents; return wrappedEvents;
} }
/**
* Sets up keyboard event listeners on the specified element.
* @param {Element} root - The DOM element to attach listeners to.
* @param {Object} events - The events to listen for.
*/
const setupListeners = (root, events) => {
if (root instanceof Element && events) {
const keydownHandler = createKeybindingsHandler(events);
document.addEventListener('keydown', keydownHandler);
keyboardListenerMap.set(root, keydownHandler);
}
};
/**
* Removes keyboard event listeners from the specified element.
* @param {Element} root - The DOM element to remove listeners from.
*/
const removeListeners = root => {
// In the future, let's use the abort controller to remove the listeners
// https://caniuse.com/abortcontroller
if (root instanceof Element) {
const handlerToRemove = keyboardListenerMap.get(root);
document.removeEventListener('keydown', handlerToRemove);
keyboardListenerMap.delete(root);
}
};
/** /**
* Vue composable to handle keyboard events with support for different keyboard layouts. * Vue composable to handle keyboard events with support for different keyboard layouts.
* @param {Object} keyboardEvents - The keyboard events to handle. * @param {Object} keyboardEvents - The keyboard events to handle.
* @param {ref} elRef - A Vue ref to the element to attach the keyboard events to.
*/ */
export function useKeyboardEvents(keyboardEvents, elRef = null) { export async function useKeyboardEvents(keyboardEvents) {
let abortController = new AbortController();
onMounted(async () => { onMounted(async () => {
const el = unref(elRef); if (!keyboardEvents) return;
const getKeyboardEvents = () => keyboardEvents || null; const wrappedEvents = await wrapEventsInKeybindingsHandler(keyboardEvents);
const events = getKeyboardEvents(); const keydownHandler = createKeybindingsHandler(wrappedEvents);
const wrappedEvents = await wrapEventsInKeybindingsHandler(events);
setupListeners(el, wrappedEvents); document.addEventListener('keydown', keydownHandler, {
signal: abortController.signal,
});
}); });
onBeforeUnmount(() => { onUnmounted(() => {
const el = unref(elRef); abortController.abort();
removeListeners(el);
}); });
} }

View File

@@ -84,7 +84,6 @@ const updateSelectionIndex = (currentIndex, itemsLength, direction) => {
* }} An object containing functions to move the selection up and down. * }} An object containing functions to move the selection up and down.
*/ */
export function useKeyboardNavigableList({ export function useKeyboardNavigableList({
elementRef,
items, items,
onSelect, onSelect,
adjustScroll, adjustScroll,
@@ -109,7 +108,7 @@ export function useKeyboardNavigableList({
items items
); );
useKeyboardEvents(keyboardEvents, elementRef); useKeyboardEvents(keyboardEvents);
return { return {
moveSelectionUp, moveSelectionUp,

View File

@@ -5,7 +5,6 @@ import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vu
const emit = defineEmits(['add']); const emit = defineEmits(['add']);
const addNoteRef = ref(null);
const noteContent = ref(''); const noteContent = ref('');
const buttonDisabled = computed(() => noteContent.value === ''); const buttonDisabled = computed(() => noteContent.value === '');
@@ -23,12 +22,11 @@ const keyboardEvents = {
allowOnFocusedInput: true, allowOnFocusedInput: true,
}, },
}; };
useKeyboardEvents(keyboardEvents, addNoteRef); useKeyboardEvents(keyboardEvents);
</script> </script>
<template> <template>
<div <div
ref="addNoteRef"
class="flex flex-col flex-grow p-4 mb-2 overflow-hidden bg-white border border-solid rounded-md shadow-sm border-slate-75 dark:border-slate-700 dark:bg-slate-900 text-slate-700 dark:text-slate-100" class="flex flex-col flex-grow p-4 mb-2 overflow-hidden bg-white border border-solid rounded-md shadow-sm border-slate-75 dark:border-slate-700 dark:bg-slate-900 text-slate-700 dark:text-slate-100"
> >
<WootMessageEditor <WootMessageEditor

View File

@@ -25,7 +25,6 @@ export default {
removeLabelFromConversation, removeLabelFromConversation,
} = useConversationLabels(); } = useConversationLabels();
const conversationLabelBoxRef = ref(null);
const showSearchDropdownLabel = ref(false); const showSearchDropdownLabel = ref(false);
const toggleLabels = () => { const toggleLabels = () => {
@@ -52,7 +51,7 @@ export default {
allowOnFocusedInput: true, allowOnFocusedInput: true,
}, },
}; };
useKeyboardEvents(keyboardEvents, conversationLabelBoxRef); useKeyboardEvents(keyboardEvents);
return { return {
isAdmin, isAdmin,
savedLabels, savedLabels,
@@ -60,7 +59,6 @@ export default {
accountLabels, accountLabels,
addLabelToConversation, addLabelToConversation,
removeLabelFromConversation, removeLabelFromConversation,
conversationLabelBoxRef,
showSearchDropdownLabel, showSearchDropdownLabel,
closeDropdownLabel, closeDropdownLabel,
toggleLabels, toggleLabels,
@@ -81,7 +79,7 @@ export default {
</script> </script>
<template> <template>
<div ref="conversationLabelBoxRef" class="sidebar-labels-wrap"> <div class="sidebar-labels-wrap">
<div <div
v-if="!conversationUiFlags.isFetching" v-if="!conversationUiFlags.isFetching"
class="contact-conversation--list" class="contact-conversation--list"

View File

@@ -11,7 +11,6 @@ defineProps({
const emit = defineEmits(['search', 'close']); const emit = defineEmits(['search', 'close']);
const articleSearchHeaderRef = ref(null);
const searchInputRef = ref(null); const searchInputRef = ref(null);
const searchQuery = ref(''); const searchQuery = ref('');
@@ -41,11 +40,11 @@ const keyboardEvents = {
allowOnFocusedInput: true, allowOnFocusedInput: true,
}, },
}; };
useKeyboardEvents(keyboardEvents, articleSearchHeaderRef); useKeyboardEvents(keyboardEvents);
</script> </script>
<template> <template>
<div ref="articleSearchHeaderRef" class="flex flex-col py-1"> <div class="flex flex-col py-1">
<div class="flex items-center justify-between py-2 mb-1"> <div class="flex items-center justify-between py-2 mb-1">
<h3 class="text-base text-slate-900 dark:text-slate-25"> <h3 class="text-base text-slate-900 dark:text-slate-25">
{{ title }} {{ title }}

View File

@@ -44,7 +44,6 @@ export default {
}; };
useKeyboardNavigableList({ useKeyboardNavigableList({
elementRef: portalSearchSuggestionsRef,
items: computed(() => props.items), items: computed(() => props.items),
adjustScroll, adjustScroll,
selectedIndex, selectedIndex,

View File

@@ -52,7 +52,7 @@ const keyboardEvents = {
}, },
}; };
useKeyboardEvents(keyboardEvents, dropdownMenuRef); useKeyboardEvents(keyboardEvents);
</script> </script>
<template> <template>