Speed up RecordTableCell by 5x (#5023)
Improved table cell performances by putting all hooks from RecordTableCell in RecordTableContext and RecordTable component, so that each cell now only subscribes to a reference to those hooks' returned function. We couldn't do memoization here since the problem is not to memoize between re-renders but to share the same function reference between hundreds of different components, so a context it the fastest way for this. I had to refactor the hooks a little bit so that they take as arguments what was previously taken from the cell's context.
This commit is contained in:
@ -0,0 +1,57 @@
|
|||||||
|
import { isUndefined } from '@sniptt/guards';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
|
||||||
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
|
import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
||||||
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { computeDraftValueFromFieldValue } from '@/object-record/record-field/utils/computeDraftValueFromFieldValue';
|
||||||
|
import { computeDraftValueFromString } from '@/object-record/record-field/utils/computeDraftValueFromString';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';
|
||||||
|
|
||||||
|
export const useInitDraftValueV2 = <FieldValue>() => {
|
||||||
|
return useRecoilCallback(
|
||||||
|
({ set, snapshot }) =>
|
||||||
|
({
|
||||||
|
value,
|
||||||
|
entityId,
|
||||||
|
fieldDefinition,
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
entityId: string;
|
||||||
|
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||||
|
}) => {
|
||||||
|
const recordFieldInputScopeId = `${entityId}-${fieldDefinition?.metadata?.fieldName}-scope`;
|
||||||
|
|
||||||
|
const getDraftValueSelector = extractComponentSelector<
|
||||||
|
FieldInputDraftValue<FieldValue> | undefined
|
||||||
|
>(recordFieldInputDraftValueComponentSelector, recordFieldInputScopeId);
|
||||||
|
|
||||||
|
const recordFieldValue = snapshot
|
||||||
|
.getLoadable(
|
||||||
|
recordStoreFamilySelector<FieldValue>({
|
||||||
|
recordId: entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (isUndefined(value)) {
|
||||||
|
set(
|
||||||
|
getDraftValueSelector(),
|
||||||
|
computeDraftValueFromFieldValue<FieldValue>({
|
||||||
|
fieldValue: recordFieldValue,
|
||||||
|
fieldDefinition,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
set(
|
||||||
|
getDraftValueSelector(),
|
||||||
|
computeDraftValueFromString<FieldValue>({ value, fieldDefinition }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -7,8 +7,20 @@ import { RecordTableBody } from '@/object-record/record-table/components/RecordT
|
|||||||
import { RecordTableBodyEffect } from '@/object-record/record-table/components/RecordTableBodyEffect';
|
import { RecordTableBodyEffect } from '@/object-record/record-table/components/RecordTableBodyEffect';
|
||||||
import { RecordTableHeader } from '@/object-record/record-table/components/RecordTableHeader';
|
import { RecordTableHeader } from '@/object-record/record-table/components/RecordTableHeader';
|
||||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
|
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
||||||
|
import { useCloseRecordTableCellV2 } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2';
|
||||||
|
import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
|
||||||
|
import {
|
||||||
|
OpenTableCellArgs,
|
||||||
|
useOpenRecordTableCellV2,
|
||||||
|
} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||||
|
import { useTriggerContextMenu } from '@/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu';
|
||||||
|
import { useUpsertRecordV2 } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2';
|
||||||
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
|
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
|
||||||
|
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
|
||||||
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport';
|
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport';
|
||||||
import { RGBA } from '@/ui/theme/constants/Rgba';
|
import { RGBA } from '@/ui/theme/constants/Rgba';
|
||||||
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
|
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
|
||||||
@ -145,6 +157,59 @@ export const RecordTable = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { upsertRecord } = useUpsertRecordV2({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpsertRecord = ({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName,
|
||||||
|
}: {
|
||||||
|
persistField: () => void;
|
||||||
|
entityId: string;
|
||||||
|
fieldName: string;
|
||||||
|
}) => {
|
||||||
|
upsertRecord(persistField, entityId, fieldName, recordTableId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
|
||||||
|
|
||||||
|
const handleOpenTableCell = (args: OpenTableCellArgs) => {
|
||||||
|
openTableCell(args);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { moveFocus } = useRecordTableMoveFocus(recordTableId);
|
||||||
|
|
||||||
|
const handleMoveFocus = (direction: MoveFocusDirection) => {
|
||||||
|
moveFocus(direction);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { closeTableCell } = useCloseRecordTableCellV2(recordTableId);
|
||||||
|
|
||||||
|
const handleCloseTableCell = () => {
|
||||||
|
closeTableCell();
|
||||||
|
};
|
||||||
|
|
||||||
|
const { moveSoftFocusToCell } =
|
||||||
|
useMoveSoftFocusToCellOnHoverV2(recordTableId);
|
||||||
|
|
||||||
|
const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => {
|
||||||
|
moveSoftFocusToCell(cellPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { triggerContextMenu } = useTriggerContextMenu({
|
||||||
|
recordTableId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleContextMenu = (event: React.MouseEvent, recordId: string) => {
|
||||||
|
triggerContextMenu(event, recordId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { handleContainerMouseEnter } = useHandleContainerMouseEnter({
|
||||||
|
recordTableId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordTableScope
|
<RecordTableScope
|
||||||
recordTableScopeId={scopeId}
|
recordTableScopeId={scopeId}
|
||||||
@ -154,6 +219,13 @@ export const RecordTable = ({
|
|||||||
<RecordTableContext.Provider
|
<RecordTableContext.Provider
|
||||||
value={{
|
value={{
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
onUpsertRecord: handleUpsertRecord,
|
||||||
|
onOpenTableCell: handleOpenTableCell,
|
||||||
|
onMoveFocus: handleMoveFocus,
|
||||||
|
onCloseTableCell: handleCloseTableCell,
|
||||||
|
onMoveSoftFocusToCell: handleMoveSoftFocusToCell,
|
||||||
|
onContextMenu: handleContextMenu,
|
||||||
|
onCellMouseEnter: handleContainerMouseEnter,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledTable
|
<StyledTable
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkey
|
|||||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
export const RecordTableCellContainer = () => {
|
export const RecordTableCellFieldContextWrapper = () => {
|
||||||
const { objectMetadataItem } = useContext(RecordTableContext);
|
const { objectMetadataItem } = useContext(RecordTableContext);
|
||||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||||
const { recordId, pathToShowPage } = useContext(RecordTableRowContext);
|
const { recordId, pathToShowPage } = useContext(RecordTableRowContext);
|
||||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||||
import { RecordTableCellContainer } from '@/object-record/record-table/components/RecordTableCellContainer';
|
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/components/RecordTableCellFieldContextWrapper';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
@ -67,7 +67,7 @@ export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => {
|
|||||||
}}
|
}}
|
||||||
key={column.fieldMetadataId}
|
key={column.fieldMetadataId}
|
||||||
>
|
>
|
||||||
<RecordTableCellContainer />
|
<RecordTableCellFieldContextWrapper />
|
||||||
</RecordTableCellContext.Provider>
|
</RecordTableCellContext.Provider>
|
||||||
) : (
|
) : (
|
||||||
<td key={column.fieldMetadataId}></td>
|
<td key={column.fieldMetadataId}></td>
|
||||||
|
|||||||
@ -1,9 +1,28 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { HandleContainerMouseEnterArgs } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
||||||
|
import { OpenTableCellArgs } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||||
|
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
|
||||||
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
|
|
||||||
type RecordTableContextProps = {
|
type RecordTableContextProps = {
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
onUpsertRecord: ({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName,
|
||||||
|
}: {
|
||||||
|
persistField: () => void;
|
||||||
|
entityId: string;
|
||||||
|
fieldName: string;
|
||||||
|
}) => void;
|
||||||
|
onOpenTableCell: (args: OpenTableCellArgs) => void;
|
||||||
|
onMoveFocus: (direction: MoveFocusDirection) => void;
|
||||||
|
onCloseTableCell: () => void;
|
||||||
|
onMoveSoftFocusToCell: (cellPosition: TableCellPosition) => void;
|
||||||
|
onContextMenu: (event: React.MouseEvent, recordId: string) => void;
|
||||||
|
onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableContext = createContext<RecordTableContextProps>(
|
export const RecordTableContext = createContext<RecordTableContextProps>(
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
|
||||||
|
import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState';
|
||||||
|
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
||||||
|
import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
|
||||||
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState';
|
||||||
|
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||||
|
|
||||||
|
export type HandleContainerMouseEnterArgs = {
|
||||||
|
isHovered: boolean;
|
||||||
|
setIsHovered: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
cellPosition: TableCellPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHandleContainerMouseEnter = ({
|
||||||
|
recordTableId,
|
||||||
|
}: {
|
||||||
|
recordTableId: string;
|
||||||
|
}) => {
|
||||||
|
const tableScopeId = getScopeIdFromComponentId(recordTableId);
|
||||||
|
|
||||||
|
const { moveSoftFocusToCell } =
|
||||||
|
useMoveSoftFocusToCellOnHoverV2(recordTableId);
|
||||||
|
|
||||||
|
const handleContainerMouseEnter = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
({
|
||||||
|
isHovered,
|
||||||
|
setIsHovered,
|
||||||
|
cellPosition,
|
||||||
|
}: HandleContainerMouseEnterArgs) => {
|
||||||
|
const currentTableCellInEditModePositionState = extractComponentState(
|
||||||
|
currentTableCellInEditModePositionComponentState,
|
||||||
|
tableScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentTableCellInEditModePosition = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
currentTableCellInEditModePositionState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isTableCellInEditModeFamilyState = extractComponentFamilyState(
|
||||||
|
isTableCellInEditModeComponentFamilyState,
|
||||||
|
tableScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSomeCellInEditMode = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isHovered && !isSomeCellInEditMode) {
|
||||||
|
setIsHovered(true);
|
||||||
|
moveSoftFocusToCell(cellPosition);
|
||||||
|
set(isSoftFocusUsingMouseState, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[tableScopeId, moveSoftFocusToCell],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleContainerMouseEnter,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
|
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
|
||||||
import { useSetSoftFocusPosition } from './internal/useSetSoftFocusPosition';
|
import { useSetSoftFocusPosition } from './internal/useSetSoftFocusPosition';
|
||||||
@ -167,6 +168,23 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const moveFocus = (direction: MoveFocusDirection) => {
|
||||||
|
switch (direction) {
|
||||||
|
case 'up':
|
||||||
|
moveUp();
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
moveDown();
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
moveLeft();
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
moveRight();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scopeId,
|
scopeId,
|
||||||
moveDown,
|
moveDown,
|
||||||
@ -175,5 +193,6 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
|||||||
moveUp,
|
moveUp,
|
||||||
setSoftFocusPosition,
|
setSoftFocusPosition,
|
||||||
selectedRowIdsSelector,
|
selectedRowIdsSelector,
|
||||||
|
moveFocus,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,11 +4,9 @@ import { FieldDisplay } from '@/object-record/record-field/components/FieldDispl
|
|||||||
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
|
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
|
||||||
import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer';
|
import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer';
|
||||||
import { useCloseRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCell';
|
|
||||||
import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord';
|
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
export const RecordTableCell = ({
|
export const RecordTableCell = ({
|
||||||
@ -16,55 +14,76 @@ export const RecordTableCell = ({
|
|||||||
}: {
|
}: {
|
||||||
customHotkeyScope: HotkeyScope;
|
customHotkeyScope: HotkeyScope;
|
||||||
}) => {
|
}) => {
|
||||||
const { closeTableCell } = useCloseRecordTableCell();
|
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
|
||||||
const { upsertRecord } = useUpsertRecord();
|
useContext(RecordTableContext);
|
||||||
|
|
||||||
const { moveLeft, moveRight, moveDown } = useRecordTableMoveFocus();
|
|
||||||
|
|
||||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||||
const { isReadOnly } = useContext(RecordTableRowContext);
|
const { isReadOnly } = useContext(RecordTableRowContext);
|
||||||
|
|
||||||
const handleEnter: FieldInputEvent = (persistField) => {
|
const handleEnter: FieldInputEvent = (persistField) => {
|
||||||
upsertRecord(persistField);
|
onUpsertRecord({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
});
|
||||||
|
|
||||||
closeTableCell();
|
onCloseTableCell();
|
||||||
moveDown();
|
onMoveFocus('down');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit: FieldInputEvent = (persistField) => {
|
const handleSubmit: FieldInputEvent = (persistField) => {
|
||||||
upsertRecord(persistField);
|
onUpsertRecord({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
});
|
||||||
|
|
||||||
closeTableCell();
|
onCloseTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
closeTableCell();
|
onCloseTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickOutside: FieldInputEvent = (persistField) => {
|
const handleClickOutside: FieldInputEvent = (persistField) => {
|
||||||
upsertRecord(persistField);
|
onUpsertRecord({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
});
|
||||||
|
|
||||||
closeTableCell();
|
onCloseTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEscape: FieldInputEvent = (persistField) => {
|
const handleEscape: FieldInputEvent = (persistField) => {
|
||||||
upsertRecord(persistField);
|
onUpsertRecord({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
});
|
||||||
|
|
||||||
closeTableCell();
|
onCloseTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTab: FieldInputEvent = (persistField) => {
|
const handleTab: FieldInputEvent = (persistField) => {
|
||||||
upsertRecord(persistField);
|
onUpsertRecord({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
});
|
||||||
|
|
||||||
closeTableCell();
|
onCloseTableCell();
|
||||||
moveRight();
|
onMoveFocus('right');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShiftTab: FieldInputEvent = (persistField) => {
|
const handleShiftTab: FieldInputEvent = (persistField) => {
|
||||||
upsertRecord(persistField);
|
onUpsertRecord({
|
||||||
|
persistField,
|
||||||
|
entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
});
|
||||||
|
|
||||||
closeTableCell();
|
onCloseTableCell();
|
||||||
moveLeft();
|
onMoveFocus('left');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,28 +1,26 @@
|
|||||||
import { ReactElement, useContext, useState } from 'react';
|
import React, { ReactElement, useContext, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { IconArrowUpRight } from 'twenty-ui';
|
import { IconArrowUpRight } from 'twenty-ui';
|
||||||
|
|
||||||
import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon';
|
import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon';
|
||||||
import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty';
|
import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty';
|
||||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
|
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { useGetIsSomeCellInEditModeState } from '@/object-record/record-table/hooks/internal/useGetIsSomeCellInEditMode';
|
import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition';
|
||||||
import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
|
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||||
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
||||||
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
import { isSoftFocusOnTableCellComponentFamilyState } from '@/object-record/record-table/states/isSoftFocusOnTableCellComponentFamilyState';
|
||||||
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
|
||||||
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
|
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
|
||||||
|
import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState';
|
||||||
|
|
||||||
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
|
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode';
|
|
||||||
import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell';
|
|
||||||
import { useMoveSoftFocusToCurrentCellOnHover } from '../hooks/useMoveSoftFocusToCurrentCellOnHover';
|
|
||||||
import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell';
|
|
||||||
|
|
||||||
import { RecordTableCellButton } from './RecordTableCellButton';
|
import { RecordTableCellButton } from './RecordTableCellButton';
|
||||||
import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode';
|
import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode';
|
||||||
@ -64,65 +62,60 @@ export const RecordTableCellContainer = ({
|
|||||||
nonEditModeContent,
|
nonEditModeContent,
|
||||||
editHotkeyScope,
|
editHotkeyScope,
|
||||||
}: RecordTableCellContainerProps) => {
|
}: RecordTableCellContainerProps) => {
|
||||||
|
const { columnIndex } = useContext(RecordTableCellContext);
|
||||||
|
const { isReadOnly, isSelected, recordId } = useContext(
|
||||||
|
RecordTableRowContext,
|
||||||
|
);
|
||||||
|
const { onMoveSoftFocusToCell, onContextMenu, onCellMouseEnter } =
|
||||||
|
useContext(RecordTableContext);
|
||||||
|
|
||||||
|
const cellPosition = useCurrentTableCellPosition();
|
||||||
|
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
const isSomeCellInEditModeState = useGetIsSomeCellInEditModeState();
|
|
||||||
|
|
||||||
const setIsSoftFocusUsingMouseState = useSetRecoilState(
|
const tableScopeId = useAvailableScopeIdOrThrow(
|
||||||
isSoftFocusUsingMouseState,
|
RecordTableScopeInternalContext,
|
||||||
|
getScopeIdOrUndefinedFromComponentId(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const moveSoftFocusToCurrentCellOnHover =
|
const isTableCellInEditModeFamilyState = extractComponentFamilyState(
|
||||||
useMoveSoftFocusToCurrentCellOnHover();
|
isTableCellInEditModeComponentFamilyState,
|
||||||
|
tableScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
const hasSoftFocus = useIsSoftFocusOnCurrentTableCell();
|
const isSoftFocusOnTableCellFamilyState = extractComponentFamilyState(
|
||||||
const setSoftFocusOnCurrentTableCell = useSetSoftFocusOnCurrentTableCell();
|
isSoftFocusOnTableCellComponentFamilyState,
|
||||||
|
tableScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
const { openTableCell } = useOpenRecordTableCell();
|
const isCurrentTableCellInEditMode = useRecoilValue(
|
||||||
|
isTableCellInEditModeFamilyState(cellPosition),
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasSoftFocus = useRecoilValue(
|
||||||
|
isSoftFocusOnTableCellFamilyState(cellPosition),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isEmpty = useIsFieldEmpty();
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
setSoftFocusOnCurrentTableCell();
|
onMoveSoftFocusToCell(cellPosition);
|
||||||
openTableCell();
|
openTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const { isSelected, isReadOnly } = useContext(RecordTableRowContext);
|
|
||||||
|
|
||||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
|
||||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
|
||||||
|
|
||||||
const { setCurrentRowSelected } = useSetCurrentRowSelected();
|
|
||||||
|
|
||||||
const handleContextMenu = (event: React.MouseEvent) => {
|
const handleContextMenu = (event: React.MouseEvent) => {
|
||||||
event.preventDefault();
|
onContextMenu(event, recordId);
|
||||||
setCurrentRowSelected(true);
|
|
||||||
setContextMenuPosition({
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY,
|
|
||||||
});
|
|
||||||
setContextMenuOpenState(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContainerMouseEnter = useRecoilCallback(
|
const handleContainerMouseEnter = () => {
|
||||||
({ snapshot }) =>
|
onCellMouseEnter({
|
||||||
() => {
|
cellPosition,
|
||||||
const isSomeCellInEditMode = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
isSomeCellInEditModeState(),
|
|
||||||
);
|
|
||||||
if (!isHovered && !isSomeCellInEditMode) {
|
|
||||||
setIsHovered(true);
|
|
||||||
moveSoftFocusToCurrentCellOnHover();
|
|
||||||
setIsSoftFocusUsingMouseState(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
isHovered,
|
isHovered,
|
||||||
isSomeCellInEditModeState,
|
setIsHovered,
|
||||||
moveSoftFocusToCurrentCellOnHover,
|
});
|
||||||
setIsSoftFocusUsingMouseState,
|
};
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleContainerMouseLeave = () => {
|
const handleContainerMouseLeave = () => {
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
@ -130,9 +123,6 @@ export const RecordTableCellContainer = ({
|
|||||||
|
|
||||||
const editModeContentOnly = useIsFieldInputOnly();
|
const editModeContentOnly = useIsFieldInputOnly();
|
||||||
|
|
||||||
const isEmpty = useIsFieldEmpty();
|
|
||||||
|
|
||||||
const { columnIndex } = useContext(RecordTableCellContext);
|
|
||||||
const isFirstColumn = columnIndex === 0;
|
const isFirstColumn = columnIndex === 0;
|
||||||
const customButtonIcon = useGetButtonIcon();
|
const customButtonIcon = useGetButtonIcon();
|
||||||
const buttonIcon = isFirstColumn ? IconArrowUpRight : customButtonIcon;
|
const buttonIcon = isFirstColumn ? IconArrowUpRight : customButtonIcon;
|
||||||
@ -148,7 +138,7 @@ export const RecordTableCellContainer = ({
|
|||||||
return (
|
return (
|
||||||
<StyledTd
|
<StyledTd
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onContextMenu={(event) => handleContextMenu(event)}
|
onContextMenu={handleContextMenu}
|
||||||
isInEditMode={isCurrentTableCellInEditMode}
|
isInEditMode={isCurrentTableCellInEditMode}
|
||||||
>
|
>
|
||||||
<CellHotkeyScopeContext.Provider
|
<CellHotkeyScopeContext.Provider
|
||||||
@ -180,9 +170,11 @@ export const RecordTableCellContainer = ({
|
|||||||
Icon={buttonIcon}
|
Icon={buttonIcon}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<RecordTableCellDisplayMode>
|
{!isEmpty && (
|
||||||
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
<RecordTableCellDisplayMode>
|
||||||
</RecordTableCellDisplayMode>
|
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
||||||
|
</RecordTableCellDisplayMode>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</StyledCellBaseContainer>
|
</StyledCellBaseContainer>
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
import { useContext } from 'react';
|
||||||
import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
|
|
||||||
|
|
||||||
import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell';
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
|
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition';
|
||||||
|
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||||
|
|
||||||
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
||||||
|
|
||||||
export const RecordTableCellDisplayMode = ({
|
export const RecordTableCellDisplayMode = ({
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<unknown>) => {
|
}: React.PropsWithChildren<unknown>) => {
|
||||||
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentTableCell();
|
const cellPosition = useCurrentTableCellPosition();
|
||||||
|
const { onMoveSoftFocusToCell } = useContext(RecordTableContext);
|
||||||
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
|
|
||||||
const isFieldInputOnly = useIsFieldInputOnly();
|
const isFieldInputOnly = useIsFieldInputOnly();
|
||||||
|
|
||||||
const { openTableCell } = useOpenRecordTableCell();
|
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
setSoftFocusOnCurrentCell();
|
onMoveSoftFocusToCell(cellPosition);
|
||||||
|
|
||||||
if (!isFieldInputOnly) {
|
if (!isFieldInputOnly) {
|
||||||
openTableCell();
|
openTableCell();
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useClearField } from '@/object-record/record-field/hooks/useClearField'
|
|||||||
import { useIsFieldClearable } from '@/object-record/record-field/hooks/useIsFieldClearable';
|
import { useIsFieldClearable } from '@/object-record/record-field/hooks/useIsFieldClearable';
|
||||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput';
|
import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput';
|
||||||
import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
|
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||||
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
||||||
@ -20,7 +20,7 @@ type RecordTableCellSoftFocusModeProps = PropsWithChildren<unknown>;
|
|||||||
export const RecordTableCellSoftFocusMode = ({
|
export const RecordTableCellSoftFocusMode = ({
|
||||||
children,
|
children,
|
||||||
}: RecordTableCellSoftFocusModeProps) => {
|
}: RecordTableCellSoftFocusModeProps) => {
|
||||||
const { openTableCell } = useOpenRecordTableCell();
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
|
|
||||||
const isFieldInputOnly = useIsFieldInputOnly();
|
const isFieldInputOnly = useIsFieldInputOnly();
|
||||||
|
|
||||||
@ -82,9 +82,7 @@ export const RecordTableCellSoftFocusMode = ({
|
|||||||
keyboardEvent.stopPropagation();
|
keyboardEvent.stopPropagation();
|
||||||
keyboardEvent.stopImmediatePropagation();
|
keyboardEvent.stopImmediatePropagation();
|
||||||
|
|
||||||
openTableCell({
|
openTableCell(keyboardEvent.key);
|
||||||
initialValue: keyboardEvent.key,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TableHotkeyScope.TableSoftFocus,
|
TableHotkeyScope.TableSoftFocus,
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { useResetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
|
||||||
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
|
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||||
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
|
|
||||||
|
import { useCloseCurrentTableCellInEditMode } from '../../hooks/internal/useCloseCurrentTableCellInEditMode';
|
||||||
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
|
export const useCloseRecordTableCellV2 = (recordTableScopeId: string) => {
|
||||||
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||||
|
const { pendingRecordIdState } = useRecordTableStates(recordTableScopeId);
|
||||||
|
|
||||||
|
const { toggleClickOutsideListener } = useClickOutsideListener(
|
||||||
|
SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeCurrentTableCellInEditMode =
|
||||||
|
useCloseCurrentTableCellInEditMode(recordTableScopeId);
|
||||||
|
|
||||||
|
const resetRecordTablePendingRecordId =
|
||||||
|
useResetRecoilState(pendingRecordIdState);
|
||||||
|
|
||||||
|
const closeTableCell = async () => {
|
||||||
|
toggleClickOutsideListener(true);
|
||||||
|
setDragSelectionStartEnabled(true);
|
||||||
|
closeCurrentTableCellInEditMode();
|
||||||
|
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
||||||
|
resetRecordTablePendingRecordId();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeTableCell,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
|
import { useSetSoftFocus } from '@/object-record/record-table/record-table-cell/hooks/useSetSoftFocus';
|
||||||
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
|
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||||
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
|
||||||
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
|
export const useMoveSoftFocusToCellOnHoverV2 = (recordTableId: string) => {
|
||||||
|
const setSoftFocus = useSetSoftFocus(recordTableId);
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentTableCellInEditModePositionState,
|
||||||
|
isTableCellInEditModeFamilyState,
|
||||||
|
} = useRecordTableStates(recordTableId);
|
||||||
|
|
||||||
|
const moveSoftFocusToCell = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
(cellPosition: TableCellPosition) => {
|
||||||
|
const currentTableCellInEditModePosition = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
currentTableCellInEditModePositionState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSomeCellInEditMode = snapshot
|
||||||
|
.getLoadable(
|
||||||
|
isTableCellInEditModeFamilyState(
|
||||||
|
currentTableCellInEditModePosition,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const currentHotkeyScope = snapshot
|
||||||
|
.getLoadable(currentHotkeyScopeState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentHotkeyScope.scope !== TableHotkeyScope.TableSoftFocus &&
|
||||||
|
currentHotkeyScope.scope !== TableHotkeyScope.CellEditMode &&
|
||||||
|
currentHotkeyScope.scope !== TableHotkeyScope.Table
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSomeCellInEditMode) {
|
||||||
|
setSoftFocus(cellPosition);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
currentTableCellInEditModePositionState,
|
||||||
|
isTableCellInEditModeFamilyState,
|
||||||
|
setSoftFocus,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { moveSoftFocusToCell };
|
||||||
|
};
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
|
||||||
|
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
|
import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition';
|
||||||
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
|
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
||||||
|
scope: TableHotkeyScope.CellEditMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OpenTableCellArgs = {
|
||||||
|
initialValue?: string;
|
||||||
|
cellPosition: TableCellPosition;
|
||||||
|
isReadOnly: boolean;
|
||||||
|
pathToShowPage: string;
|
||||||
|
customCellHotkeyScope: HotkeyScope | null;
|
||||||
|
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||||
|
entityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOpenRecordTableCellFromCell = () => {
|
||||||
|
const { onOpenTableCell } = useContext(RecordTableContext);
|
||||||
|
const cellPosition = useCurrentTableCellPosition();
|
||||||
|
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
|
||||||
|
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
const { isReadOnly, pathToShowPage } = useContext(RecordTableRowContext);
|
||||||
|
|
||||||
|
const openTableCell = (initialValue?: string) => {
|
||||||
|
onOpenTableCell({
|
||||||
|
cellPosition,
|
||||||
|
customCellHotkeyScope,
|
||||||
|
entityId,
|
||||||
|
fieldDefinition,
|
||||||
|
isReadOnly,
|
||||||
|
pathToShowPage,
|
||||||
|
initialValue,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
openTableCell,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,125 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2';
|
||||||
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
|
||||||
|
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
|
||||||
|
import { useMoveEditModeToTableCellPosition } from '@/object-record/record-table/hooks/internal/useMoveEditModeToCellPosition';
|
||||||
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
|
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||||
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
|
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
||||||
|
scope: TableHotkeyScope.CellEditMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OpenTableCellArgs = {
|
||||||
|
initialValue?: string;
|
||||||
|
cellPosition: TableCellPosition;
|
||||||
|
isReadOnly: boolean;
|
||||||
|
pathToShowPage: string;
|
||||||
|
customCellHotkeyScope: HotkeyScope | null;
|
||||||
|
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||||
|
entityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||||
|
const moveEditModeToTableCellPosition =
|
||||||
|
useMoveEditModeToTableCellPosition(tableScopeId);
|
||||||
|
|
||||||
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const leaveTableFocus = useLeaveTableFocus(tableScopeId);
|
||||||
|
const { toggleClickOutsideListener } = useClickOutsideListener(
|
||||||
|
SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const initDraftValue = useInitDraftValueV2();
|
||||||
|
|
||||||
|
const openTableCell = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
({
|
||||||
|
initialValue,
|
||||||
|
cellPosition,
|
||||||
|
isReadOnly,
|
||||||
|
pathToShowPage,
|
||||||
|
customCellHotkeyScope,
|
||||||
|
fieldDefinition,
|
||||||
|
entityId,
|
||||||
|
}: OpenTableCellArgs) => {
|
||||||
|
if (isReadOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFirstColumnCell = cellPosition.column === 0;
|
||||||
|
|
||||||
|
const fieldValue = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
recordStoreFamilySelector({
|
||||||
|
recordId: entityId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isEmpty = isFieldValueEmpty({
|
||||||
|
fieldDefinition,
|
||||||
|
fieldValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isFirstColumnCell && !isEmpty) {
|
||||||
|
leaveTableFocus();
|
||||||
|
navigate(pathToShowPage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDragSelectionStartEnabled(false);
|
||||||
|
|
||||||
|
moveEditModeToTableCellPosition(cellPosition);
|
||||||
|
|
||||||
|
initDraftValue({
|
||||||
|
value: initialValue,
|
||||||
|
entityId,
|
||||||
|
fieldDefinition,
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleClickOutsideListener(false);
|
||||||
|
|
||||||
|
if (isDefined(customCellHotkeyScope)) {
|
||||||
|
setHotkeyScope(
|
||||||
|
customCellHotkeyScope.scope,
|
||||||
|
customCellHotkeyScope.customScopes,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setHotkeyScope(
|
||||||
|
DEFAULT_CELL_SCOPE.scope,
|
||||||
|
DEFAULT_CELL_SCOPE.customScopes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
setDragSelectionStartEnabled,
|
||||||
|
toggleClickOutsideListener,
|
||||||
|
leaveTableFocus,
|
||||||
|
navigate,
|
||||||
|
setHotkeyScope,
|
||||||
|
initDraftValue,
|
||||||
|
moveEditModeToTableCellPosition,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
openTableCell,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -7,10 +7,10 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope
|
|||||||
|
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
export const useSetSoftFocus = () => {
|
export const useSetSoftFocus = (recordTableId?: string) => {
|
||||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
const setSoftFocusPosition = useSetSoftFocusPosition(recordTableId);
|
||||||
|
|
||||||
const { isSoftFocusActiveState } = useRecordTableStates();
|
const { isSoftFocusActiveState } = useRecordTableStates(recordTableId);
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||||
|
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
||||||
|
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
||||||
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState';
|
||||||
|
|
||||||
|
export const useTriggerContextMenu = ({
|
||||||
|
recordTableId,
|
||||||
|
}: {
|
||||||
|
recordTableId: string;
|
||||||
|
}) => {
|
||||||
|
const triggerContextMenu = useRecoilCallback(
|
||||||
|
({ set, snapshot }) =>
|
||||||
|
(event: React.MouseEvent, recordId: string) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const tableScopeId = getScopeIdFromComponentId(recordTableId);
|
||||||
|
|
||||||
|
set(contextMenuPositionState, {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
});
|
||||||
|
set(contextMenuIsOpenState, true);
|
||||||
|
|
||||||
|
const isRowSelectedFamilyState = extractComponentFamilyState(
|
||||||
|
isRowSelectedComponentFamilyState,
|
||||||
|
tableScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRowSelected = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
isRowSelectedFamilyState(recordId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isRowSelected !== true) {
|
||||||
|
set(isRowSelectedFamilyState(recordId), true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[recordTableId],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { triggerContextMenu };
|
||||||
|
};
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
|
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
|
||||||
|
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||||
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';
|
||||||
|
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const useUpsertRecordV2 = ({
|
||||||
|
objectNameSingular,
|
||||||
|
}: {
|
||||||
|
objectNameSingular: string;
|
||||||
|
}) => {
|
||||||
|
const { createOneRecord } = useCreateOneRecord({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const upsertRecord = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
(
|
||||||
|
persistField: () => void,
|
||||||
|
entityId: string,
|
||||||
|
fieldName: string,
|
||||||
|
tableScopeId: string,
|
||||||
|
) => {
|
||||||
|
const recordTablePendingRecordIdState = extractComponentState(
|
||||||
|
recordTablePendingRecordIdComponentState,
|
||||||
|
tableScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordTablePendingRecordId = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
recordTablePendingRecordIdState,
|
||||||
|
);
|
||||||
|
const fieldScopeId = `${entityId}-${fieldName}`;
|
||||||
|
|
||||||
|
const draftValueSelector = extractComponentSelector(
|
||||||
|
recordFieldInputDraftValueComponentSelector,
|
||||||
|
fieldScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const draftValue = getSnapshotValue(snapshot, draftValueSelector());
|
||||||
|
|
||||||
|
if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) {
|
||||||
|
createOneRecord({
|
||||||
|
id: recordTablePendingRecordId,
|
||||||
|
name: draftValue,
|
||||||
|
position: 'first',
|
||||||
|
});
|
||||||
|
} else if (!recordTablePendingRecordId) {
|
||||||
|
persistField();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[createOneRecord],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { upsertRecord };
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export type MoveFocusDirection = 'up' | 'down' | 'left' | 'right';
|
||||||
Reference in New Issue
Block a user