Feat/better hotkeys scope (#526)
* Working version * fix * Fixed console log * Fix lint * wip * Fix * Fix * consolelog --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,12 +1,16 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
|
||||
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
|
||||
|
||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
||||
import { useCurrentCellEditMode } from './hooks/useCurrentCellEditMode';
|
||||
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
|
||||
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
|
||||
import { isEditModeScopedState } from './states/isEditModeScopedState';
|
||||
import { useSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
|
||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
|
||||
@ -27,6 +31,7 @@ type OwnProps = {
|
||||
nonEditModeContent: ReactElement;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
editModeVerticalPosition?: 'over' | 'below';
|
||||
editHotkeysScope?: HotkeysScopeStackItem;
|
||||
};
|
||||
|
||||
export function EditableCell({
|
||||
@ -34,39 +39,51 @@ export function EditableCell({
|
||||
editModeVerticalPosition = 'over',
|
||||
editModeContent,
|
||||
nonEditModeContent,
|
||||
editHotkeysScope,
|
||||
}: OwnProps) {
|
||||
const [isEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||
|
||||
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||
const setSoftFocusOnCurrentCell = useSoftFocusOnCurrentCell();
|
||||
|
||||
const { closeEditableCell, openEditableCell } = useEditableCell();
|
||||
const { openEditableCell } = useEditableCell();
|
||||
|
||||
const isSoftFocusActive = useRecoilValue(isSoftFocusActiveState);
|
||||
|
||||
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
|
||||
|
||||
// TODO: we might have silent problematic behavior because of the setTimeout in openEditableCell, investigate
|
||||
// Maybe we could build a switchEditableCell to handle the case where we go from one cell to another.
|
||||
// See https://github.com/twentyhq/twenty/issues/446
|
||||
function handleOnClick() {
|
||||
openEditableCell();
|
||||
setSoftFocusOnCurrentCell();
|
||||
}
|
||||
if (isCurrentCellInEditMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleOnOutsideClick() {
|
||||
closeEditableCell();
|
||||
if (isSoftFocusActive) {
|
||||
openEditableCell();
|
||||
addToHotkeysScopeStack(
|
||||
editHotkeysScope ?? {
|
||||
scope: InternalHotkeysScope.CellEditMode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setSoftFocusOnCurrentCell();
|
||||
}
|
||||
|
||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||
|
||||
return (
|
||||
<CellBaseContainer onClick={handleOnClick}>
|
||||
{isEditMode ? (
|
||||
{isCurrentCellInEditMode ? (
|
||||
<EditableCellEditMode
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeVerticalPosition={editModeVerticalPosition}
|
||||
onOutsideClick={handleOnOutsideClick}
|
||||
>
|
||||
{editModeContent}
|
||||
</EditableCellEditMode>
|
||||
) : hasSoftFocus ? (
|
||||
<EditableCellSoftFocusMode>
|
||||
<EditableCellSoftFocusMode editHotkeysScope={editHotkeysScope}>
|
||||
{nonEditModeContent}
|
||||
</EditableCellSoftFocusMode>
|
||||
) : (
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { ReactElement, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
|
||||
import { overlayBackground } from '@/ui/themes/effects';
|
||||
@ -38,7 +39,6 @@ export function EditableCellEditMode({
|
||||
editModeHorizontalAlign,
|
||||
editModeVerticalPosition,
|
||||
children,
|
||||
onOutsideClick,
|
||||
}: OwnProps) {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
@ -46,61 +46,45 @@ export function EditableCellEditMode({
|
||||
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||
|
||||
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
||||
onOutsideClick?.();
|
||||
closeEditableCell();
|
||||
});
|
||||
|
||||
useHotkeys(
|
||||
useScopedHotkeys(
|
||||
'enter',
|
||||
() => {
|
||||
closeEditableCell();
|
||||
moveDown();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
InternalHotkeysScope.CellEditMode,
|
||||
[closeEditableCell],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
useScopedHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
closeEditableCell();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
InternalHotkeysScope.CellEditMode,
|
||||
[closeEditableCell],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
useScopedHotkeys(
|
||||
'tab',
|
||||
() => {
|
||||
closeEditableCell();
|
||||
moveRight();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
InternalHotkeysScope.CellEditMode,
|
||||
[closeEditableCell, moveRight],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
useScopedHotkeys(
|
||||
'shift+tab',
|
||||
() => {
|
||||
closeEditableCell();
|
||||
moveLeft();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
InternalHotkeysScope.CellEditMode,
|
||||
[closeEditableCell, moveRight],
|
||||
);
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
|
||||
|
||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
||||
@ -10,26 +11,26 @@ import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||
|
||||
export function EditableCellSoftFocusMode({
|
||||
children,
|
||||
}: React.PropsWithChildren<unknown>) {
|
||||
editHotkeysScope,
|
||||
}: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScopeStackItem }>) {
|
||||
const { closeEditableCell, openEditableCell } = useEditableCell();
|
||||
const [captureHotkeyTypeInFocus] = useRecoilState(
|
||||
captureHotkeyTypeInFocusState,
|
||||
);
|
||||
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
|
||||
|
||||
useHotkeys(
|
||||
useScopedHotkeys(
|
||||
'enter',
|
||||
() => {
|
||||
openEditableCell();
|
||||
addToHotkeysScopeStack(
|
||||
editHotkeysScope ?? {
|
||||
scope: InternalHotkeysScope.CellEditMode,
|
||||
},
|
||||
);
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[closeEditableCell],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
useScopedHotkeys(
|
||||
'*',
|
||||
(keyboardEvent) => {
|
||||
const isWritingText =
|
||||
@ -41,14 +42,16 @@ export function EditableCellSoftFocusMode({
|
||||
return;
|
||||
}
|
||||
|
||||
if (captureHotkeyTypeInFocus) {
|
||||
return;
|
||||
}
|
||||
openEditableCell();
|
||||
addToHotkeysScopeStack(
|
||||
editHotkeysScope ?? {
|
||||
scope: InternalHotkeysScope.CellEditMode,
|
||||
},
|
||||
);
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[openEditableCell, addToHotkeysScopeStack, editHotkeysScope],
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: false,
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRemoveHighestHotkeysScopeStackItem } from '@/hotkeys/hooks/useRemoveHighestHotkeysScopeStackItem';
|
||||
import { useCloseCurrentCellInEditMode } from '@/ui/tables/hooks/useClearCellInEditMode';
|
||||
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
|
||||
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
|
||||
|
||||
import { isEditModeScopedState } from '../states/isEditModeScopedState';
|
||||
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
|
||||
|
||||
export function useEditableCell() {
|
||||
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||
const { setCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||
|
||||
const closeEditableCell = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async () => {
|
||||
setIsEditMode(false);
|
||||
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
const removeHighestHotkeysScopedStackItem =
|
||||
useRemoveHighestHotkeysScopeStackItem();
|
||||
|
||||
set(isSomeInputInEditModeState, false);
|
||||
},
|
||||
[setIsEditMode],
|
||||
);
|
||||
function closeEditableCell() {
|
||||
closeCurrentCellInEditMode();
|
||||
removeHighestHotkeysScopedStackItem();
|
||||
}
|
||||
|
||||
const openEditableCell = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
@ -29,11 +29,12 @@ export function useEditableCell() {
|
||||
|
||||
if (!isSomeInputInEditMode) {
|
||||
set(isSomeInputInEditModeState, true);
|
||||
set(isSoftFocusActiveState, false);
|
||||
|
||||
setIsEditMode(true);
|
||||
setCurrentCellInEditMode();
|
||||
}
|
||||
},
|
||||
[setIsEditMode],
|
||||
[setCurrentCellInEditMode],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useMoveEditModeToCellPosition } from '@/ui/tables/hooks/useMoveEditModeToCellPosition';
|
||||
import { isCellInEditModeFamilyState } from '@/ui/tables/states/isCellInEditModeFamilyState';
|
||||
|
||||
import { useCurrentCellPosition } from './useCurrentCellPosition';
|
||||
|
||||
export function useCurrentCellEditMode() {
|
||||
const moveEditModeToCellPosition = useMoveEditModeToCellPosition();
|
||||
|
||||
const currentCellPosition = useCurrentCellPosition();
|
||||
|
||||
const [isCurrentCellInEditMode] = useRecoilState(
|
||||
isCellInEditModeFamilyState(currentCellPosition),
|
||||
);
|
||||
|
||||
const setCurrentCellInEditMode = useCallback(() => {
|
||||
moveEditModeToCellPosition(currentCellPosition);
|
||||
}, [currentCellPosition, moveEditModeToCellPosition]);
|
||||
|
||||
return { isCurrentCellInEditMode, setCurrentCellInEditMode };
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
|
||||
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
import { CellPosition } from '@/ui/tables/types/CellPosition';
|
||||
|
||||
export function useCurrentCellPosition() {
|
||||
const [currentRowNumber] = useRecoilScopedState(
|
||||
currentRowNumberScopedState,
|
||||
RowContext,
|
||||
);
|
||||
|
||||
const [currentColumnNumber] = useRecoilScopedState(
|
||||
currentColumnNumberScopedState,
|
||||
CellContext,
|
||||
);
|
||||
|
||||
const currentCellPosition: CellPosition = useMemo(
|
||||
() => ({
|
||||
column: currentColumnNumber,
|
||||
row: currentRowNumber,
|
||||
}),
|
||||
[currentColumnNumber, currentRowNumber],
|
||||
);
|
||||
|
||||
return currentCellPosition;
|
||||
}
|
||||
@ -1,35 +1,14 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
|
||||
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
|
||||
import { isSoftFocusOnCellFamilyState } from '@/ui/tables/states/isSoftFocusOnCellFamilyState';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
import { TablePosition } from '@/ui/tables/types/TablePosition';
|
||||
|
||||
import { useCurrentCellPosition } from './useCurrentCellPosition';
|
||||
|
||||
export function useIsSoftFocusOnCurrentCell() {
|
||||
const [currentRowNumber] = useRecoilScopedState(
|
||||
currentRowNumberScopedState,
|
||||
RowContext,
|
||||
);
|
||||
|
||||
const [currentColumnNumber] = useRecoilScopedState(
|
||||
currentColumnNumberScopedState,
|
||||
CellContext,
|
||||
);
|
||||
|
||||
const currentTablePosition: TablePosition = useMemo(
|
||||
() => ({
|
||||
column: currentColumnNumber,
|
||||
row: currentRowNumber,
|
||||
}),
|
||||
[currentColumnNumber, currentRowNumber],
|
||||
);
|
||||
const currentCellPosition = useCurrentCellPosition();
|
||||
|
||||
const isSoftFocusOnCell = useRecoilValue(
|
||||
isSoftFocusOnCellFamilyState(currentTablePosition),
|
||||
isSoftFocusOnCellFamilyState(currentCellPosition),
|
||||
);
|
||||
|
||||
return isSoftFocusOnCell;
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useSetSoftFocusPosition } from '@/ui/tables/hooks/useSetSoftFocusPosition';
|
||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
|
||||
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
|
||||
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
import { TablePosition } from '@/ui/tables/types/TablePosition';
|
||||
import { CellPosition } from '@/ui/tables/types/CellPosition';
|
||||
|
||||
export function useSetSoftFocusOnCurrentCell() {
|
||||
export function useSoftFocusOnCurrentCell() {
|
||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||
const [currentRowNumber] = useRecoilScopedState(
|
||||
currentRowNumberScopedState,
|
||||
@ -20,7 +24,7 @@ export function useSetSoftFocusOnCurrentCell() {
|
||||
CellContext,
|
||||
);
|
||||
|
||||
const currentTablePosition: TablePosition = useMemo(
|
||||
const currentTablePosition: CellPosition = useMemo(
|
||||
() => ({
|
||||
column: currentColumnNumber,
|
||||
row: currentRowNumber,
|
||||
@ -28,7 +32,18 @@ export function useSetSoftFocusOnCurrentCell() {
|
||||
[currentColumnNumber, currentRowNumber],
|
||||
);
|
||||
|
||||
const [, setIsSoftFocusActive] = useRecoilState(isSoftFocusActiveState);
|
||||
|
||||
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
|
||||
|
||||
return useCallback(() => {
|
||||
setSoftFocusPosition(currentTablePosition);
|
||||
}, [setSoftFocusPosition, currentTablePosition]);
|
||||
setIsSoftFocusActive(true);
|
||||
addToHotkeysScopeStack({ scope: InternalHotkeysScope.TableSoftFocus });
|
||||
}, [
|
||||
setSoftFocusPosition,
|
||||
currentTablePosition,
|
||||
setIsSoftFocusActive,
|
||||
addToHotkeysScopeStack,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const isEditModeScopedState = atomFamily<boolean, string>({
|
||||
key: 'isEditModeScopedState',
|
||||
default: false,
|
||||
});
|
||||
@ -1,10 +1,11 @@
|
||||
import { ChangeEvent, ReactElement, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { textInputStyle } from '@/ui/themes/effects';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
import { EditableDoubleTextEditMode } from './EditableDoubleTextEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
firstValue: string;
|
||||
secondValue: string;
|
||||
@ -14,57 +15,25 @@ type OwnProps = {
|
||||
onChange: (firstValue: string, secondValue: string) => void;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
& > input:last-child {
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEditInplaceInput = styled.input`
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
width: 45%;
|
||||
|
||||
${textInputStyle}
|
||||
`;
|
||||
|
||||
export function EditableDoubleText({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
secondValuePlaceholder,
|
||||
nonEditModeContent,
|
||||
onChange,
|
||||
nonEditModeContent,
|
||||
}: OwnProps) {
|
||||
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editHotkeysScope={{ scope: InternalHotkeysScope.CellDoubleTextInput }}
|
||||
editModeContent={
|
||||
<StyledContainer>
|
||||
<StyledEditInplaceInput
|
||||
autoFocus
|
||||
placeholder={firstValuePlaceholder}
|
||||
ref={firstValueInputRef}
|
||||
value={firstValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(event.target.value, secondValue);
|
||||
}}
|
||||
/>
|
||||
<StyledEditInplaceInput
|
||||
placeholder={secondValuePlaceholder}
|
||||
ref={firstValueInputRef}
|
||||
value={secondValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(firstValue, event.target.value);
|
||||
}}
|
||||
/>
|
||||
</StyledContainer>
|
||||
<EditableDoubleTextEditMode
|
||||
firstValue={firstValue}
|
||||
secondValue={secondValue}
|
||||
firstValuePlaceholder={firstValuePlaceholder}
|
||||
secondValuePlaceholder={secondValuePlaceholder}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={nonEditModeContent}
|
||||
></EditableCell>
|
||||
|
||||
@ -0,0 +1,129 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
|
||||
import { textInputStyle } from '@/ui/themes/effects';
|
||||
|
||||
import { useEditableCell } from '../hooks/useCloseEditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
firstValue: string;
|
||||
secondValue: string;
|
||||
firstValuePlaceholder: string;
|
||||
secondValuePlaceholder: string;
|
||||
onChange: (firstValue: string, secondValue: string) => void;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
& > input:last-child {
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEditInplaceInput = styled.input`
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
width: 45%;
|
||||
|
||||
${textInputStyle}
|
||||
`;
|
||||
|
||||
export function EditableDoubleTextEditMode({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
secondValuePlaceholder,
|
||||
onChange,
|
||||
}: OwnProps) {
|
||||
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
|
||||
|
||||
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
||||
const secondValueInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||
|
||||
function closeCell() {
|
||||
setFocusPosition('left');
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
closeCell();
|
||||
moveDown();
|
||||
},
|
||||
InternalHotkeysScope.CellDoubleTextInput,
|
||||
[closeCell],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
closeCell();
|
||||
},
|
||||
InternalHotkeysScope.CellDoubleTextInput,
|
||||
[closeCell],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'tab',
|
||||
async (keyboardEvent, hotkeyEvent) => {
|
||||
if (focusPosition === 'left') {
|
||||
setFocusPosition('right');
|
||||
secondValueInputRef.current?.focus();
|
||||
} else {
|
||||
closeCell();
|
||||
moveRight();
|
||||
}
|
||||
},
|
||||
InternalHotkeysScope.CellDoubleTextInput,
|
||||
[closeCell, moveRight, focusPosition],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'shift+tab',
|
||||
() => {
|
||||
if (focusPosition === 'right') {
|
||||
setFocusPosition('left');
|
||||
firstValueInputRef.current?.focus();
|
||||
} else {
|
||||
closeCell();
|
||||
moveLeft();
|
||||
}
|
||||
},
|
||||
InternalHotkeysScope.CellDoubleTextInput,
|
||||
[closeCell, moveRight, focusPosition],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledEditInplaceInput
|
||||
autoFocus
|
||||
placeholder={firstValuePlaceholder}
|
||||
ref={firstValueInputRef}
|
||||
value={firstValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(event.target.value, secondValue);
|
||||
}}
|
||||
/>
|
||||
<StyledEditInplaceInput
|
||||
placeholder={secondValuePlaceholder}
|
||||
ref={secondValueInputRef}
|
||||
value={secondValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(firstValue, event.target.value);
|
||||
}}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@ -13,6 +13,8 @@ import {
|
||||
SortType,
|
||||
} from '@/filters-and-sorts/interfaces/sorts/interface';
|
||||
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useLeaveTableFocus } from '@/ui/tables/hooks/useLeaveTableFocus';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
|
||||
import { currentRowSelectionState } from '../../tables/states/rowSelectionState';
|
||||
@ -103,6 +105,8 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
||||
availableSorts,
|
||||
onSortsUpdate,
|
||||
}: OwnProps<TData, SortField>) {
|
||||
const tableBodyRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const [currentRowSelection, setCurrentRowSelection] = useRecoilState(
|
||||
currentRowSelectionState,
|
||||
);
|
||||
@ -119,6 +123,12 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
||||
getRowId: (row) => row.id,
|
||||
});
|
||||
|
||||
const leaveTableFocus = useLeaveTableFocus();
|
||||
|
||||
useListenClickOutsideArrayOfRef([tableBodyRef], () => {
|
||||
leaveTableFocus();
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledTableWithHeader>
|
||||
<TableHeader
|
||||
@ -127,7 +137,7 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
||||
availableSorts={availableSorts}
|
||||
onSortsUpdate={onSortsUpdate}
|
||||
/>
|
||||
<StyledTableScrollableContainer>
|
||||
<StyledTableScrollableContainer ref={tableBodyRef}>
|
||||
<StyledTable>
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { ReactNode, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { IconChevronDown } from '@/ui/icons/index';
|
||||
import { overlayBackground, textInputStyle } from '@/ui/themes/effects';
|
||||
|
||||
@ -13,7 +14,7 @@ type OwnProps = {
|
||||
isActive: boolean;
|
||||
children?: ReactNode;
|
||||
isUnfolded?: boolean;
|
||||
setIsUnfolded?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onIsUnfoldedChange?: (newIsUnfolded: boolean) => void;
|
||||
resetState?: () => void;
|
||||
};
|
||||
|
||||
@ -158,22 +159,23 @@ function DropdownButton({
|
||||
isActive,
|
||||
children,
|
||||
isUnfolded = false,
|
||||
setIsUnfolded,
|
||||
resetState,
|
||||
onIsUnfoldedChange,
|
||||
}: OwnProps) {
|
||||
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
|
||||
captureHotkeyTypeInFocusState,
|
||||
useScopedHotkeys(
|
||||
[Key.Enter, Key.Escape],
|
||||
() => {
|
||||
onIsUnfoldedChange?.(false);
|
||||
},
|
||||
InternalHotkeysScope.TableHeaderDropdownButton,
|
||||
[onIsUnfoldedChange],
|
||||
);
|
||||
|
||||
const onButtonClick = () => {
|
||||
setIsUnfolded && setIsUnfolded(!isUnfolded);
|
||||
setCaptureHotkeyTypeInFocus((isPreviousUnfolded) => !isPreviousUnfolded);
|
||||
onIsUnfoldedChange?.(!isUnfolded);
|
||||
};
|
||||
|
||||
const onOutsideClick = () => {
|
||||
setCaptureHotkeyTypeInFocus(false);
|
||||
setIsUnfolded && setIsUnfolded(false);
|
||||
resetState && resetState();
|
||||
onIsUnfoldedChange?.(false);
|
||||
};
|
||||
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
@ -5,6 +5,8 @@ import { filterDropdownSearchInputScopedState } from '@/filters-and-sorts/states
|
||||
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/filters-and-sorts/states/isFilterDropdownOperandSelectUnfoldedScopedState';
|
||||
import { selectedOperandInDropdownScopedState } from '@/filters-and-sorts/states/selectedOperandInDropdownScopedState';
|
||||
import { tableFilterDefinitionUsedInDropdownScopedState } from '@/filters-and-sorts/states/tableFilterDefinitionUsedInDropdownScopedState';
|
||||
import { useHotkeysScopeOnBooleanState } from '@/hotkeys/hooks/useHotkeysScopeOnBooleanState';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { TableContext } from '@/ui/tables/states/TableContext';
|
||||
|
||||
@ -21,6 +23,11 @@ import { FilterDropdownTextSearchInput } from './FilterDropdownTextSearchInput';
|
||||
export function FilterDropdownButton() {
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
useHotkeysScopeOnBooleanState(
|
||||
{ scope: InternalHotkeysScope.TableHeaderDropdownButton },
|
||||
isUnfolded,
|
||||
);
|
||||
|
||||
const [
|
||||
isFilterDropdownOperandSelectUnfolded,
|
||||
setIsFilterDropdownOperandSelectUnfolded,
|
||||
@ -64,13 +71,21 @@ export function FilterDropdownButton() {
|
||||
|
||||
const isFilterSelected = (activeTableFilters?.length ?? 0) > 0;
|
||||
|
||||
function handleIsUnfoldedChange(newIsUnfolded: boolean) {
|
||||
if (newIsUnfolded) {
|
||||
setIsUnfolded(true);
|
||||
} else {
|
||||
setIsUnfolded(false);
|
||||
resetState();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
label="Filter"
|
||||
isActive={isFilterSelected}
|
||||
isUnfolded={isUnfolded}
|
||||
setIsUnfolded={setIsUnfolded}
|
||||
resetState={resetState}
|
||||
onIsUnfoldedChange={handleIsUnfoldedChange}
|
||||
>
|
||||
{!tableFilterDefinitionUsedInDropdown ? (
|
||||
<FilterDropdownFilterSelect />
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import {
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
} from '@/filters-and-sorts/interfaces/sorts/interface';
|
||||
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||
|
||||
import DropdownButton from './DropdownButton';
|
||||
|
||||
@ -23,9 +21,6 @@ export function SortDropdownButton<SortField>({
|
||||
onSortSelect,
|
||||
}: OwnProps<SortField>) {
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
|
||||
captureHotkeyTypeInFocusState,
|
||||
);
|
||||
|
||||
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
|
||||
|
||||
@ -41,17 +36,24 @@ export function SortDropdownButton<SortField>({
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setIsOptionUnfolded(false);
|
||||
setCaptureHotkeyTypeInFocus(false);
|
||||
setSelectedSortDirection('asc');
|
||||
}, [setCaptureHotkeyTypeInFocus]);
|
||||
}, []);
|
||||
|
||||
function handleIsUnfoldedChange(newIsUnfolded: boolean) {
|
||||
if (newIsUnfolded) {
|
||||
setIsUnfolded(true);
|
||||
} else {
|
||||
setIsUnfolded(false);
|
||||
resetState();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
label="Sort"
|
||||
isActive={isSortSelected}
|
||||
isUnfolded={isUnfolded}
|
||||
setIsUnfolded={setIsUnfolded}
|
||||
resetState={resetState}
|
||||
onIsUnfoldedChange={handleIsUnfoldedChange}
|
||||
>
|
||||
{isOptionUnfolded
|
||||
? options.map((option, index) => (
|
||||
@ -59,7 +61,6 @@ export function SortDropdownButton<SortField>({
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setSelectedSortDirection(option);
|
||||
setCaptureHotkeyTypeInFocus(false);
|
||||
setIsOptionUnfolded(false);
|
||||
}}
|
||||
>
|
||||
@ -80,7 +81,6 @@ export function SortDropdownButton<SortField>({
|
||||
key={index + 1}
|
||||
onClick={() => {
|
||||
setIsUnfolded(false);
|
||||
setCaptureHotkeyTypeInFocus(false);
|
||||
onSortItemSelect(sort);
|
||||
}}
|
||||
>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||
import { useHotkeysScopeOnBooleanState } from '@/hotkeys/hooks/useHotkeysScopeOnBooleanState';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { isDefined } from '@/utils/type-guards/isDefined';
|
||||
|
||||
import { Panel } from '../../Panel';
|
||||
@ -18,14 +18,14 @@ const StyledRightDrawer = styled.div`
|
||||
`;
|
||||
|
||||
export function RightDrawer() {
|
||||
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
|
||||
captureHotkeyTypeInFocusState,
|
||||
);
|
||||
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||
useEffect(() => {
|
||||
setCaptureHotkeyTypeInFocus(isRightDrawerOpen);
|
||||
}, [isRightDrawerOpen, setCaptureHotkeyTypeInFocus]);
|
||||
|
||||
useHotkeysScopeOnBooleanState(
|
||||
{ scope: InternalHotkeysScope.RightDrawer },
|
||||
isRightDrawerOpen,
|
||||
);
|
||||
|
||||
if (!isRightDrawerOpen || !isDefined(rightDrawerPage)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useDirectHotkeys } from '@/hotkeys/hooks/useDirectHotkeys';
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { IconPlus } from '@/ui/icons/index';
|
||||
|
||||
import NavCollapseButton from '../navbar/NavCollapseButton';
|
||||
@ -50,7 +51,7 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
export function TopBar({ title, icon, onAddButtonClick }: OwnProps) {
|
||||
useDirectHotkeys('c', () => onAddButtonClick && onAddButtonClick());
|
||||
useScopedHotkeys('c', () => onAddButtonClick?.(), InternalHotkeysScope.Table);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
21
front/src/modules/ui/tables/hooks/useClearCellInEditMode.ts
Normal file
21
front/src/modules/ui/tables/hooks/useClearCellInEditMode.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { currentCellInEditModePositionState } from '../states/currentCellInEditModePositionState';
|
||||
import { isCellInEditModeFamilyState } from '../states/isCellInEditModeFamilyState';
|
||||
import { isSomeInputInEditModeState } from '../states/isSomeInputInEditModeState';
|
||||
|
||||
export function useCloseCurrentCellInEditMode() {
|
||||
return useRecoilCallback(({ set, snapshot }) => {
|
||||
return async () => {
|
||||
const currentCellInEditModePosition = await snapshot.getPromise(
|
||||
currentCellInEditModePositionState,
|
||||
);
|
||||
|
||||
set(isCellInEditModeFamilyState(currentCellInEditModePosition), false);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
|
||||
set(isSomeInputInEditModeState, false);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
19
front/src/modules/ui/tables/hooks/useDisableSoftFocus.ts
Normal file
19
front/src/modules/ui/tables/hooks/useDisableSoftFocus.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
|
||||
import { isSoftFocusOnCellFamilyState } from '../states/isSoftFocusOnCellFamilyState';
|
||||
import { softFocusPositionState } from '../states/softFocusPositionState';
|
||||
|
||||
export function useDisableSoftFocus() {
|
||||
return useRecoilCallback(({ set, snapshot }) => {
|
||||
return () => {
|
||||
const currentPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(isSoftFocusActiveState, false);
|
||||
|
||||
set(isSoftFocusOnCellFamilyState(currentPosition), false);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN } from '../constants';
|
||||
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
|
||||
|
||||
import { useResetTableRowSelection } from './useResetTableRowSelection';
|
||||
import { useSetSoftFocusPosition } from './useSetSoftFocusPosition';
|
||||
|
||||
export type TableDimensions = {
|
||||
numberOfRows: number;
|
||||
@ -30,13 +28,4 @@ export function useInitializeEntityTable({
|
||||
numberOfRows,
|
||||
});
|
||||
}, [numberOfRows, numberOfColumns, setTableDimensions]);
|
||||
|
||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||
|
||||
useEffect(() => {
|
||||
setSoftFocusPosition({
|
||||
row: 0,
|
||||
column: TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN,
|
||||
});
|
||||
}, [setSoftFocusPosition]);
|
||||
}
|
||||
|
||||
24
front/src/modules/ui/tables/hooks/useLeaveTableFocus.ts
Normal file
24
front/src/modules/ui/tables/hooks/useLeaveTableFocus.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { useCurrentHotkeysScope } from '@/hotkeys/hooks/useCurrentHotkeysScope';
|
||||
import { useResetHotkeysScopeStack } from '@/hotkeys/hooks/useResetHotkeysScopeStack';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
|
||||
import { useCloseCurrentCellInEditMode } from './useClearCellInEditMode';
|
||||
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
||||
|
||||
export function useLeaveTableFocus() {
|
||||
const resetHotkeysScopeStack = useResetHotkeysScopeStack();
|
||||
const currentHotkeysScope = useCurrentHotkeysScope();
|
||||
|
||||
const disableSoftFocus = useDisableSoftFocus();
|
||||
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
|
||||
|
||||
return async function leaveTableFocus() {
|
||||
if (currentHotkeysScope?.scope === InternalHotkeysScope.Table) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeCurrentCellInEditMode();
|
||||
disableSoftFocus();
|
||||
resetHotkeysScopeStack(InternalHotkeysScope.Table);
|
||||
};
|
||||
}
|
||||
@ -1,72 +1,74 @@
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useRemoveFromHotkeysScopeStack } from '@/hotkeys/hooks/useRemoveFromHotkeysScopeStack';
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
|
||||
import { isSomeInputInEditModeState } from '../states/isSomeInputInEditModeState';
|
||||
|
||||
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
||||
import { useMoveSoftFocus } from './useMoveSoftFocus';
|
||||
|
||||
export function useMapKeyboardToSoftFocus() {
|
||||
const { moveDown, moveLeft, moveRight, moveUp } = useMoveSoftFocus();
|
||||
|
||||
const removeFromHotkeysScopedStack = useRemoveFromHotkeysScopeStack();
|
||||
const disableSoftFocus = useDisableSoftFocus();
|
||||
|
||||
const [isSomeInputInEditMode] = useRecoilState(isSomeInputInEditModeState);
|
||||
|
||||
useHotkeys(
|
||||
'up, shift+enter',
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowUp, `${Key.Shift}+${Key.Enter}`],
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveUp();
|
||||
}
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[moveUp, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'down',
|
||||
useScopedHotkeys(
|
||||
Key.ArrowDown,
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveDown();
|
||||
}
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[moveDown, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['left', 'shift+tab'],
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`],
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveLeft();
|
||||
}
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[moveLeft, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['right', 'tab'],
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowRight, Key.Tab],
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveRight();
|
||||
}
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[moveRight, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
removeFromHotkeysScopedStack(InternalHotkeysScope.TableSoftFocus);
|
||||
disableSoftFocus();
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[removeFromHotkeysScopedStack, disableSoftFocus],
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { currentCellInEditModePositionState } from '../states/currentCellInEditModePositionState';
|
||||
import { isCellInEditModeFamilyState } from '../states/isCellInEditModeFamilyState';
|
||||
import { CellPosition } from '../types/CellPosition';
|
||||
|
||||
export function useMoveEditModeToCellPosition() {
|
||||
return useRecoilCallback(({ set, snapshot }) => {
|
||||
return (newPosition: CellPosition) => {
|
||||
const currentCellInEditModePosition = snapshot
|
||||
.getLoadable(currentCellInEditModePositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(isCellInEditModeFamilyState(currentCellInEditModePosition), false);
|
||||
|
||||
set(currentCellInEditModePositionState, newPosition);
|
||||
|
||||
set(isCellInEditModeFamilyState(newPosition), true);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
@ -1,16 +1,19 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
|
||||
import { isSoftFocusOnCellFamilyState } from '../states/isSoftFocusOnCellFamilyState';
|
||||
import { softFocusPositionState } from '../states/softFocusPositionState';
|
||||
import { TablePosition } from '../types/TablePosition';
|
||||
import { CellPosition } from '../types/CellPosition';
|
||||
|
||||
export function useSetSoftFocusPosition() {
|
||||
return useRecoilCallback(({ set, snapshot }) => {
|
||||
return (newPosition: TablePosition) => {
|
||||
return (newPosition: CellPosition) => {
|
||||
const currentPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(isSoftFocusActiveState, true);
|
||||
|
||||
set(isSoftFocusOnCellFamilyState(currentPosition), false);
|
||||
|
||||
set(softFocusPositionState, newPosition);
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { CellPosition } from '../types/CellPosition';
|
||||
|
||||
export const currentCellInEditModePositionState = atom<CellPosition>({
|
||||
key: 'currentCellInEditModePositionState',
|
||||
default: {
|
||||
row: 0,
|
||||
column: 1,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import { CellPosition } from '../types/CellPosition';
|
||||
|
||||
export const isCellInEditModeFamilyState = atomFamily<boolean, CellPosition>({
|
||||
key: 'isCellInEditModeFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const isSoftFocusActiveState = atom<boolean>({
|
||||
key: 'isSoftFocusActiveState',
|
||||
default: false,
|
||||
});
|
||||
@ -1,8 +1,8 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import { TablePosition } from '../types/TablePosition';
|
||||
import { CellPosition } from '../types/CellPosition';
|
||||
|
||||
export const isSoftFocusOnCellFamilyState = atomFamily<boolean, TablePosition>({
|
||||
export const isSoftFocusOnCellFamilyState = atomFamily<boolean, CellPosition>({
|
||||
key: 'isSoftFocusOnCellFamilyState',
|
||||
default: false,
|
||||
});
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { TablePosition } from '../types/TablePosition';
|
||||
import { CellPosition } from '../types/CellPosition';
|
||||
|
||||
export const softFocusPositionState = atom<TablePosition>({
|
||||
export const softFocusPositionState = atom<CellPosition>({
|
||||
key: 'softFocusPositionState',
|
||||
default: {
|
||||
row: 0,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export type TablePosition = {
|
||||
export type CellPosition = {
|
||||
row: number;
|
||||
column: number;
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import { TablePosition } from '../TablePosition';
|
||||
import { CellPosition } from '../CellPosition';
|
||||
|
||||
export function isTablePosition(value: any): value is TablePosition {
|
||||
export function isTablePosition(value: any): value is CellPosition {
|
||||
return (
|
||||
value && typeof value.row === 'number' && typeof value.column === 'number'
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export interface PositionType {
|
||||
export type PositionType = {
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user