Keeps Escape focused on closing active dropdowns first Avoids accidental session aborts from menu interactions
598 lines
20 KiB
TypeScript
598 lines
20 KiB
TypeScript
import React from 'react';
|
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
|
import { useSelectionStore } from '@/sync/selection-store';
|
|
import * as sessionActions from '@/sync/session-actions';
|
|
import { useUIStore } from '@/stores/useUIStore';
|
|
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
|
import { useAssistantStatus } from '@/hooks/useAssistantStatus';
|
|
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
|
|
import { useConfigStore } from '@/stores/useConfigStore';
|
|
import { canUseElectronDesktopIPC, invokeDesktop, isVSCodeRuntime } from '@/lib/desktop';
|
|
import { showOpenCodeStatus } from '@/lib/openCodeStatus';
|
|
import { eventMatchesShortcut, getEffectiveShortcutCombo, normalizeCombo } from '@/lib/shortcuts';
|
|
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
|
import { useProjectsStore } from '@/stores/useProjectsStore';
|
|
import { getCycledPrimaryAgentName } from '@/components/chat/mobileControlsUtils';
|
|
|
|
export const useKeyboardShortcuts = () => {
|
|
const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft);
|
|
const armAbortPrompt = useSessionUIStore((s) => s.armAbortPrompt);
|
|
const clearAbortPrompt = useSessionUIStore((s) => s.clearAbortPrompt);
|
|
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
|
const abortCurrentOperation = sessionActions.abortCurrentOperation;;
|
|
const toggleCommandPalette = useUIStore((s) => s.toggleCommandPalette);
|
|
const toggleHelpDialog = useUIStore((s) => s.toggleHelpDialog);
|
|
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
|
|
const toggleRightSidebar = useUIStore((s) => s.toggleRightSidebar);
|
|
const setRightSidebarOpen = useUIStore((s) => s.setRightSidebarOpen);
|
|
const setRightSidebarTab = useUIStore((s) => s.setRightSidebarTab);
|
|
const toggleBottomTerminal = useUIStore((s) => s.toggleBottomTerminal);
|
|
const setBottomTerminalExpanded = useUIStore((s) => s.setBottomTerminalExpanded);
|
|
const isMobile = useUIStore((s) => s.isMobile);
|
|
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
|
const setActiveMainTab = useUIStore((s) => s.setActiveMainTab);
|
|
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
|
const setModelSelectorOpen = useUIStore((s) => s.setModelSelectorOpen);
|
|
const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen);
|
|
const toggleExpandedInput = useUIStore((s) => s.toggleExpandedInput);
|
|
const shortcutOverrides = useUIStore((s) => s.shortcutOverrides);
|
|
const currentDirectory = useDirectoryStore((s) => s.currentDirectory);
|
|
const activeProject = useProjectsStore((s) => s.getActiveProject());
|
|
const { themeMode, setThemeMode } = useThemeSystem();
|
|
const { working } = useAssistantStatus();
|
|
const abortPrimedUntilRef = React.useRef<number | null>(null);
|
|
const abortPrimedTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
const themeModeRef = React.useRef(themeMode);
|
|
|
|
React.useEffect(() => {
|
|
themeModeRef.current = themeMode;
|
|
}, [themeMode]);
|
|
|
|
const resetAbortPriming = React.useCallback(() => {
|
|
if (abortPrimedTimeoutRef.current) {
|
|
clearTimeout(abortPrimedTimeoutRef.current);
|
|
abortPrimedTimeoutRef.current = null;
|
|
}
|
|
abortPrimedUntilRef.current = null;
|
|
clearAbortPrompt();
|
|
}, [clearAbortPrompt]);
|
|
|
|
React.useEffect(() => {
|
|
const combo = (actionId: string) => getEffectiveShortcutCombo(actionId, shortcutOverrides);
|
|
const isTerminalEventTarget = (target: EventTarget | null) => {
|
|
if (!(target instanceof Element)) {
|
|
return false;
|
|
}
|
|
|
|
return Boolean(
|
|
target.closest('.terminal-viewport-container') ||
|
|
target.getAttribute('data-terminal-hidden-input') === 'true'
|
|
);
|
|
};
|
|
|
|
const dropdownTargetSelector = [
|
|
'[data-slot="dropdown-menu-content"]',
|
|
'[data-slot="select-content"]',
|
|
'[role="combobox"]',
|
|
'[role="listbox"]',
|
|
'[role="menu"]',
|
|
'[role="menuitem"]',
|
|
'[role="option"]',
|
|
'[data-radix-popper-content-wrapper]',
|
|
].join(',');
|
|
|
|
const isDropdownEventTarget = (target: EventTarget | null) => {
|
|
return target instanceof Element && Boolean(target.closest(dropdownTargetSelector));
|
|
};
|
|
|
|
const hasOpenDropdown = () => {
|
|
const openDropdowns = document.querySelectorAll<HTMLElement>(
|
|
'[data-slot="dropdown-menu-content"], [data-slot="select-content"], [role="listbox"], [role="menu"], [data-radix-popper-content-wrapper]'
|
|
);
|
|
return Array.from(openDropdowns).some((element) => element.getClientRects().length > 0);
|
|
};
|
|
|
|
const handleTerminalShortcutCapture = (e: KeyboardEvent) => {
|
|
if (!isTerminalEventTarget(e.target)) {
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('toggle_terminal'))) {
|
|
const { isMobile } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
toggleBottomTerminal();
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('toggle_terminal_expanded'))) {
|
|
const { isMobile, isBottomTerminalExpanded } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
setBottomTerminalExpanded(!isBottomTerminalExpanded);
|
|
return;
|
|
}
|
|
};
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (isTerminalEventTarget(e.target)) {
|
|
return;
|
|
}
|
|
|
|
const isChatInputTarget = (target: EventTarget | null) => {
|
|
return target instanceof HTMLTextAreaElement && target.getAttribute('data-chat-input') === 'true';
|
|
};
|
|
|
|
if (eventMatchesShortcut(e, combo('open_command_palette'))) {
|
|
e.preventDefault();
|
|
toggleCommandPalette();
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('open_timeline_dialog'))) {
|
|
e.preventDefault();
|
|
setTimelineDialogOpen(true);
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('open_status'))) {
|
|
e.preventDefault();
|
|
void showOpenCodeStatus();
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('open_help'))) {
|
|
e.preventDefault();
|
|
toggleHelpDialog();
|
|
return;
|
|
}
|
|
|
|
if (canUseElectronDesktopIPC() && eventMatchesShortcut(e, combo('new_mini_chat'))) {
|
|
e.preventDefault();
|
|
void invokeDesktop('desktop_open_draft_mini_chat_window', {
|
|
directory: currentDirectory || activeProject?.path || '',
|
|
projectId: activeProject?.id ?? null,
|
|
}).catch((error) => {
|
|
console.warn('[keyboard-shortcuts] failed to open draft mini chat window', error);
|
|
});
|
|
return;
|
|
}
|
|
|
|
const matchedNewSessionShortcut = eventMatchesShortcut(e, combo('new_chat'));
|
|
const matchedWorktreeShortcut = eventMatchesShortcut(e, combo('new_chat_worktree'));
|
|
|
|
if (matchedNewSessionShortcut || matchedWorktreeShortcut) {
|
|
e.preventDefault();
|
|
|
|
setActiveMainTab('chat');
|
|
setSessionSwitcherOpen(false);
|
|
|
|
if (!isVSCodeRuntime() && matchedWorktreeShortcut) {
|
|
createWorktreeSession();
|
|
return;
|
|
}
|
|
|
|
openNewSessionDraft();
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('cycle_theme'))) {
|
|
e.preventDefault();
|
|
const modes: Array<'light' | 'dark' | 'system'> = ['light', 'dark', 'system'];
|
|
const activeElement = document.activeElement as HTMLElement | null;
|
|
const currentIndex = modes.indexOf(themeModeRef.current);
|
|
const nextIndex = (currentIndex + 1) % modes.length;
|
|
setThemeMode(modes[nextIndex]);
|
|
requestAnimationFrame(() => {
|
|
if (typeof document === 'undefined' || typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
if (!document.hasFocus()) {
|
|
window.focus();
|
|
}
|
|
if (activeElement && document.contains(activeElement)) {
|
|
activeElement.focus({ preventScroll: true });
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('open_settings'))) {
|
|
e.preventDefault();
|
|
const { isSettingsDialogOpen } = useUIStore.getState();
|
|
setSettingsDialogOpen(!isSettingsDialogOpen);
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('toggle_sidebar'))) {
|
|
e.preventDefault();
|
|
const { isMobile, isSessionSwitcherOpen } = useUIStore.getState();
|
|
if (isMobile) {
|
|
setSessionSwitcherOpen(!isSessionSwitcherOpen);
|
|
} else {
|
|
toggleSidebar();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('focus_input'))) {
|
|
e.preventDefault();
|
|
const textarea = document.querySelector<HTMLTextAreaElement>('textarea[data-chat-input="true"]');
|
|
textarea?.focus();
|
|
return;
|
|
}
|
|
|
|
const cycleAgentCombo = combo('cycle_agent');
|
|
const cycleAgentBackwardCombo = cycleAgentCombo && !cycleAgentCombo.includes('shift')
|
|
? normalizeCombo(`shift+${cycleAgentCombo}`)
|
|
: '';
|
|
const cycleAgentDirection = cycleAgentBackwardCombo && eventMatchesShortcut(e, cycleAgentBackwardCombo)
|
|
? -1
|
|
: eventMatchesShortcut(e, cycleAgentCombo)
|
|
? 1
|
|
: 0;
|
|
|
|
if (cycleAgentDirection !== 0) {
|
|
const {
|
|
isSettingsDialogOpen,
|
|
isCommandPaletteOpen,
|
|
isHelpDialogOpen,
|
|
isSessionSwitcherOpen,
|
|
isAboutDialogOpen,
|
|
activeMainTab,
|
|
} = useUIStore.getState();
|
|
|
|
const hasOverlay = isSettingsDialogOpen || isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
|
if (hasOverlay || activeMainTab !== 'chat' || !isChatInputTarget(e.target)) {
|
|
return;
|
|
}
|
|
|
|
const configState = useConfigStore.getState();
|
|
const nextAgentName = getCycledPrimaryAgentName(
|
|
configState.getVisibleAgents(),
|
|
configState.currentAgentName,
|
|
cycleAgentDirection,
|
|
);
|
|
|
|
if (!nextAgentName) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
configState.setAgent(nextAgentName);
|
|
useUIStore.getState().addRecentAgent(nextAgentName);
|
|
|
|
const sessionId = useSessionUIStore.getState().currentSessionId;
|
|
if (sessionId) {
|
|
useSelectionStore.getState().saveSessionAgentSelection(sessionId, nextAgentName);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('toggle_right_sidebar'))) {
|
|
const { isMobile } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
toggleRightSidebar();
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('open_right_sidebar_git'))) {
|
|
const { isMobile } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
setRightSidebarOpen(true);
|
|
setRightSidebarTab('git');
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('open_right_sidebar_files'))) {
|
|
const { isMobile } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
setRightSidebarOpen(true);
|
|
setRightSidebarTab('files');
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('cycle_right_sidebar_tab'))) {
|
|
const { isMobile, rightSidebarTab } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
|
|
const tabs = ['git', 'files', 'context'] as const;
|
|
const currentIndex = tabs.indexOf(rightSidebarTab);
|
|
const nextTab = tabs[(currentIndex + 1) % tabs.length];
|
|
|
|
e.preventDefault();
|
|
setRightSidebarOpen(true);
|
|
setRightSidebarTab(nextTab);
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('toggle_terminal'))) {
|
|
const { isMobile } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
toggleBottomTerminal();
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('toggle_terminal_expanded'))) {
|
|
const { isMobile, isBottomTerminalExpanded } = useUIStore.getState();
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
setBottomTerminalExpanded(!isBottomTerminalExpanded);
|
|
return;
|
|
}
|
|
|
|
// Cmd/Ctrl+Shift+M: Open model selector (same conditions as double-ESC: chat tab, no overlays)
|
|
if (eventMatchesShortcut(e, combo('open_model_selector'))) {
|
|
const {
|
|
isSettingsDialogOpen,
|
|
isCommandPaletteOpen,
|
|
isHelpDialogOpen,
|
|
isSessionSwitcherOpen,
|
|
isAboutDialogOpen,
|
|
activeMainTab,
|
|
isModelSelectorOpen,
|
|
} = useUIStore.getState();
|
|
|
|
// Skip if settings open
|
|
if (isSettingsDialogOpen) {
|
|
return;
|
|
}
|
|
|
|
// Skip if any overlay open or not on chat tab
|
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
|
const isChatActive = activeMainTab === 'chat';
|
|
|
|
if (hasOverlay || !isChatActive) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
setModelSelectorOpen(!isModelSelectorOpen);
|
|
return;
|
|
}
|
|
|
|
// Cmd/Ctrl+Shift+T: Cycle thinking variant (same gating as Shift+M)
|
|
if (eventMatchesShortcut(e, combo('cycle_thinking_variant'))) {
|
|
const {
|
|
isSettingsDialogOpen,
|
|
isCommandPaletteOpen,
|
|
isHelpDialogOpen,
|
|
isSessionSwitcherOpen,
|
|
isAboutDialogOpen,
|
|
activeMainTab,
|
|
} = useUIStore.getState();
|
|
|
|
if (isSettingsDialogOpen) {
|
|
return;
|
|
}
|
|
|
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
|
const isChatActive = activeMainTab === 'chat';
|
|
|
|
if (hasOverlay || !isChatActive) {
|
|
return;
|
|
}
|
|
|
|
const configState = useConfigStore.getState();
|
|
const variants = configState.getCurrentModelVariants();
|
|
if (variants.length === 0) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
configState.cycleCurrentVariant();
|
|
|
|
const nextVariant = useConfigStore.getState().currentVariant;
|
|
const sessionId = useSessionUIStore.getState().currentSessionId;
|
|
const agentName = useConfigStore.getState().currentAgentName;
|
|
const providerId = useConfigStore.getState().currentProviderId;
|
|
const modelId = useConfigStore.getState().currentModelId;
|
|
|
|
if (sessionId && agentName && providerId && modelId) {
|
|
useSelectionStore.getState().saveAgentModelVariantForSession(sessionId, agentName, providerId, modelId, nextVariant);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Ctrl+] / Ctrl+[: Cycle through starred models (same gating as Shift+M)
|
|
if (
|
|
eventMatchesShortcut(e, combo('cycle_favorite_model_forward')) ||
|
|
eventMatchesShortcut(e, combo('cycle_favorite_model_backward'))
|
|
) {
|
|
const {
|
|
isSettingsDialogOpen,
|
|
isCommandPaletteOpen,
|
|
isHelpDialogOpen,
|
|
isSessionSwitcherOpen,
|
|
isAboutDialogOpen,
|
|
activeMainTab,
|
|
favoriteModels,
|
|
addRecentModel,
|
|
} = useUIStore.getState();
|
|
|
|
if (isSettingsDialogOpen) {
|
|
return;
|
|
}
|
|
|
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
|
const isChatActive = activeMainTab === 'chat';
|
|
|
|
if (hasOverlay || !isChatActive || favoriteModels.length === 0) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
const { currentProviderId, currentModelId, setProvider, setModel } = useConfigStore.getState();
|
|
const len = favoriteModels.length;
|
|
const currentIdx = favoriteModels.findIndex(
|
|
(f) => f.providerID === currentProviderId && f.modelID === currentModelId,
|
|
);
|
|
const delta = eventMatchesShortcut(e, combo('cycle_favorite_model_forward')) ? 1 : -1;
|
|
const next = favoriteModels[(currentIdx + delta + len) % len];
|
|
|
|
setProvider(next.providerID);
|
|
setModel(next.modelID);
|
|
addRecentModel(next.providerID, next.modelID);
|
|
return;
|
|
}
|
|
|
|
if (eventMatchesShortcut(e, combo('expand_input'))) {
|
|
if (isMobile) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
toggleExpandedInput();
|
|
return;
|
|
}
|
|
|
|
if (e.key === 'Escape') {
|
|
const target = e.target as Element | null;
|
|
const isInsideDialog = Boolean(target?.closest('[role="dialog"]'));
|
|
const isSettingsMounted = Boolean(document.querySelector('[data-settings-view="true"]'));
|
|
const isInsideTerminal = Boolean(
|
|
target?.closest('.terminal-viewport-container') ||
|
|
target?.getAttribute('data-terminal-hidden-input') === 'true'
|
|
);
|
|
const hasDropdownInteraction = isDropdownEventTarget(target) || hasOpenDropdown();
|
|
|
|
const {
|
|
isSettingsDialogOpen,
|
|
isCommandPaletteOpen,
|
|
isHelpDialogOpen,
|
|
isSessionSwitcherOpen,
|
|
isAboutDialogOpen,
|
|
isMultiRunLauncherOpen,
|
|
isImagePreviewOpen,
|
|
activeMainTab,
|
|
} = useUIStore.getState();
|
|
|
|
if (isInsideDialog || isInsideTerminal || hasDropdownInteraction) {
|
|
resetAbortPriming();
|
|
return;
|
|
}
|
|
|
|
// If settings is open, close it
|
|
if (isSettingsDialogOpen) {
|
|
e.preventDefault();
|
|
setSettingsDialogOpen(false);
|
|
resetAbortPriming();
|
|
return;
|
|
}
|
|
|
|
if (isSettingsMounted) {
|
|
resetAbortPriming();
|
|
return;
|
|
}
|
|
|
|
// Check if any overlay is open or not on chat tab - don't process abort
|
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen || isMultiRunLauncherOpen || isImagePreviewOpen;
|
|
const isChatActive = activeMainTab === 'chat';
|
|
|
|
if (hasOverlay || !isChatActive) {
|
|
resetAbortPriming();
|
|
return;
|
|
}
|
|
|
|
// Double-ESC abort logic - only when on chat tab with no overlays
|
|
const sessionId = currentSessionId;
|
|
const canAbortNow = working.canAbort && Boolean(sessionId);
|
|
if (!canAbortNow) {
|
|
resetAbortPriming();
|
|
return;
|
|
}
|
|
|
|
const now = Date.now();
|
|
const primedUntil = abortPrimedUntilRef.current;
|
|
|
|
if (primedUntil && now < primedUntil) {
|
|
e.preventDefault();
|
|
resetAbortPriming();
|
|
void abortCurrentOperation(sessionId ?? '');
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
const expiresAt = armAbortPrompt(3000) ?? now + 3000;
|
|
abortPrimedUntilRef.current = expiresAt;
|
|
|
|
if (abortPrimedTimeoutRef.current) {
|
|
clearTimeout(abortPrimedTimeoutRef.current);
|
|
}
|
|
|
|
const delay = Math.max(expiresAt - now, 0);
|
|
abortPrimedTimeoutRef.current = setTimeout(() => {
|
|
if (abortPrimedUntilRef.current && Date.now() >= abortPrimedUntilRef.current) {
|
|
resetAbortPriming();
|
|
}
|
|
}, delay || 0);
|
|
return;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('keydown', handleTerminalShortcutCapture, true);
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
|
|
return () => {
|
|
window.removeEventListener('keydown', handleTerminalShortcutCapture, true);
|
|
window.removeEventListener('keydown', handleKeyDown);
|
|
};
|
|
}, [
|
|
openNewSessionDraft,
|
|
abortCurrentOperation,
|
|
toggleCommandPalette,
|
|
toggleHelpDialog,
|
|
toggleSidebar,
|
|
toggleRightSidebar,
|
|
setRightSidebarOpen,
|
|
setRightSidebarTab,
|
|
toggleBottomTerminal,
|
|
setBottomTerminalExpanded,
|
|
isMobile,
|
|
setSessionSwitcherOpen,
|
|
setActiveMainTab,
|
|
setSettingsDialogOpen,
|
|
setModelSelectorOpen,
|
|
setTimelineDialogOpen,
|
|
toggleExpandedInput,
|
|
setThemeMode,
|
|
working,
|
|
armAbortPrompt,
|
|
resetAbortPriming,
|
|
currentSessionId,
|
|
currentDirectory,
|
|
activeProject?.id,
|
|
activeProject?.path,
|
|
shortcutOverrides,
|
|
]);
|
|
|
|
React.useEffect(() => {
|
|
return () => {
|
|
resetAbortPriming();
|
|
};
|
|
}, [resetAbortPriming]);
|
|
};
|