Introduce focus stack to handle hotkeys (#12166)
# Introduce focus stack to handle hotkeys This PR introduces a focus stack to track the order in which the elements are focused: - Each focused element has a unique focus id - When an element is focused, it is pushed on top of the stack - When an element loses focus, we remove it from the stack This focus stack is then used to determine which hotkeys are available. The previous implementation lead to many regressions because of race conditions, of wrong order of open and close operations and by overwriting previous states. This implementation should be way more robust than the previous one. The new api can be incrementally implemented since it preserves backwards compatibility by writing to the old hotkey scopes states. For now, it has been implemented on the modal components. To test this PR, verify that the shortcuts still work correctly, especially for the modal components.
This commit is contained in:
@ -52,7 +52,9 @@ export const RecordBoardHotkeyEffect = () => {
|
||||
useScopedHotkeys(
|
||||
Key.ArrowLeft,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
});
|
||||
move('left');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
@ -61,7 +63,9 @@ export const RecordBoardHotkeyEffect = () => {
|
||||
useScopedHotkeys(
|
||||
Key.ArrowRight,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
});
|
||||
move('right');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
@ -70,7 +74,9 @@ export const RecordBoardHotkeyEffect = () => {
|
||||
useScopedHotkeys(
|
||||
Key.ArrowUp,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
});
|
||||
move('up');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
@ -79,7 +85,9 @@ export const RecordBoardHotkeyEffect = () => {
|
||||
useScopedHotkeys(
|
||||
Key.ArrowDown,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
});
|
||||
move('down');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
|
||||
@ -79,12 +79,12 @@ export const RecordBoardColumnHeader = () => {
|
||||
|
||||
const handleBoardColumnMenuOpen = () => {
|
||||
setIsBoardColumnMenuOpen(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
RecordBoardColumnHotkeyScope.BoardColumn,
|
||||
{
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: RecordBoardColumnHotkeyScope.BoardColumn,
|
||||
customScopes: {
|
||||
goto: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleBoardColumnMenuClose = () => {
|
||||
|
||||
@ -60,9 +60,9 @@ export const FormFieldInputInnerContainer = forwardRef(
|
||||
onFocus?.(e);
|
||||
|
||||
if (!preventSetHotkeyScope) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
FormFieldInputHotKeyScope.FormFieldInput,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: FormFieldInputHotKeyScope.FormFieldInput,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -119,7 +119,9 @@ export const FormMultiSelectFieldInput = ({
|
||||
editingMode: 'edit',
|
||||
});
|
||||
|
||||
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: hotkeyScope,
|
||||
});
|
||||
};
|
||||
|
||||
const onOptionSelected = (value: FieldMultiSelectValue) => {
|
||||
|
||||
@ -31,9 +31,7 @@ export const useOpenFieldInputEditMode = () => {
|
||||
const { openActivityTargetCellEditMode } =
|
||||
useOpenActivityTargetCellEditMode();
|
||||
|
||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(
|
||||
INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
);
|
||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||
|
||||
const openFieldInput = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
@ -105,10 +103,11 @@ export const useOpenFieldInputEditMode = () => {
|
||||
}
|
||||
}
|
||||
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
DEFAULT_CELL_SCOPE.scope,
|
||||
DEFAULT_CELL_SCOPE.customScopes,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: DEFAULT_CELL_SCOPE.scope,
|
||||
customScopes: DEFAULT_CELL_SCOPE.customScopes,
|
||||
memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
});
|
||||
},
|
||||
[
|
||||
openActivityTargetCellEditMode,
|
||||
|
||||
@ -88,9 +88,9 @@ export const useOpenRelationFromManyFieldInput = () => {
|
||||
forcePickableMorphItems: pickableMorphItems,
|
||||
});
|
||||
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
MultipleRecordPickerHotkeyScope.MultipleRecordPicker,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: MultipleRecordPickerHotkeyScope.MultipleRecordPicker,
|
||||
});
|
||||
},
|
||||
[performSearch, setHotkeyScopeAndMemorizePreviousScope],
|
||||
);
|
||||
|
||||
@ -34,9 +34,9 @@ export const useOpenRelationToOneFieldInput = () => {
|
||||
);
|
||||
}
|
||||
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
SingleRecordPickerHotkeyScope.SingleRecordPicker,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: SingleRecordPickerHotkeyScope.SingleRecordPicker,
|
||||
});
|
||||
},
|
||||
[setHotkeyScopeAndMemorizePreviousScope],
|
||||
);
|
||||
|
||||
@ -35,9 +35,7 @@ export const useInlineCell = (
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
useGoBackToPreviousDropdownFocusId();
|
||||
|
||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(
|
||||
INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
);
|
||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
|
||||
|
||||
const initFieldInputDraftValue = useInitDraftValueV2();
|
||||
|
||||
@ -45,7 +43,7 @@ export const useInlineCell = (
|
||||
onCloseEditMode?.();
|
||||
setIsInlineCellInEditMode(false);
|
||||
|
||||
goBackToPreviousHotkeyScope();
|
||||
goBackToPreviousHotkeyScope(INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY);
|
||||
|
||||
goBackToPreviousDropdownFocusId();
|
||||
};
|
||||
|
||||
@ -31,7 +31,9 @@ export const useMapKeyboardToFocus = (recordTableId?: string) => {
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowUp],
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: TableHotkeyScope.TableFocus,
|
||||
});
|
||||
move('up');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
@ -41,7 +43,9 @@ export const useMapKeyboardToFocus = (recordTableId?: string) => {
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowDown],
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: TableHotkeyScope.TableFocus,
|
||||
});
|
||||
move('down');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
|
||||
@ -42,16 +42,15 @@ export const RecordTitleCellSingleTextDisplayMode = () => {
|
||||
|
||||
const { openInlineCell } = useInlineCell();
|
||||
|
||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(
|
||||
INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
);
|
||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||
|
||||
return (
|
||||
<StyledDiv
|
||||
onClick={() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
TitleInputHotkeyScope.TitleInput,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: TitleInputHotkeyScope.TitleInput,
|
||||
memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
});
|
||||
openInlineCell();
|
||||
}}
|
||||
>
|
||||
|
||||
@ -45,15 +45,14 @@ export const RecordTitleFullNameFieldDisplay = () => {
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(
|
||||
INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
);
|
||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||
return (
|
||||
<StyledDiv
|
||||
onClick={() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
TitleInputHotkeyScope.TitleInput,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: TitleInputHotkeyScope.TitleInput,
|
||||
memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
});
|
||||
openInlineCell();
|
||||
}}
|
||||
>
|
||||
|
||||
@ -18,7 +18,7 @@ export const useRecordTitleCell = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope(INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY);
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const closeRecordTitleCell = useRecoilCallback(
|
||||
({ set }) =>
|
||||
@ -38,7 +38,7 @@ export const useRecordTitleCell = () => {
|
||||
false,
|
||||
);
|
||||
|
||||
goBackToPreviousHotkeyScope();
|
||||
goBackToPreviousHotkeyScope(INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY);
|
||||
|
||||
goBackToPreviousDropdownFocusId();
|
||||
},
|
||||
@ -61,14 +61,16 @@ export const useRecordTitleCell = () => {
|
||||
customEditHotkeyScopeForField?: HotkeyScope;
|
||||
}) => {
|
||||
if (isDefined(customEditHotkeyScopeForField)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
customEditHotkeyScopeForField.scope,
|
||||
customEditHotkeyScopeForField.customScopes,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: customEditHotkeyScopeForField.scope,
|
||||
customScopes: customEditHotkeyScopeForField.customScopes,
|
||||
memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
});
|
||||
} else {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
TitleInputHotkeyScope.TitleInput,
|
||||
);
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: TitleInputHotkeyScope.TitleInput,
|
||||
memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
const recordTitleCellId = getRecordTitleCellId(
|
||||
|
||||
Reference in New Issue
Block a user