diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/contexts/FieldFocusContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/contexts/FieldFocusContextProvider.tsx index 6b0a8a6fd0..1c61a5665c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/contexts/FieldFocusContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/contexts/FieldFocusContextProvider.tsx @@ -2,6 +2,16 @@ import { useState } from 'react'; import { FieldFocusContext } from '@/object-record/record-field/ui/contexts/FieldFocusContext'; +const STATIC_UNFOCUSED_VALUE = { + isFocused: false, + setIsFocused: () => {}, +}; + +const STATIC_FOCUSED_VALUE = { + isFocused: true, + setIsFocused: () => {}, +}; + export const FieldFocusContextProvider = ({ children }: any) => { const [isFocused, setIsFocused] = useState(false); @@ -16,3 +26,23 @@ export const FieldFocusContextProvider = ({ children }: any) => { ); }; + +export const FieldFocusStaticUnfocusedProvider = ({ + children, +}: { + children: React.ReactNode; +}) => ( + + {children} + +); + +export const FieldFocusStaticFocusedProvider = ({ + children, +}: { + children: React.ReactNode; +}) => ( + + {children} + +); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx index cd41665547..b31a4b78fe 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx @@ -12,9 +12,9 @@ import { recordTableHoverPositionComponentState } from '@/object-record/record-t import { isSomeCellInEditModeComponentSelector } from '@/object-record/record-table/states/selectors/isSomeCellInEditModeComponentSelector'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass'; +import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState'; import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState'; -import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import styled from '@emotion/styled'; import { useCallback, useRef, useState } from 'react'; import { useStore } from 'jotai'; @@ -73,10 +73,11 @@ export const RecordTableContent = ({ const { visibleRecordFields } = useRecordTableContextOrThrow(); - const setRecordTableHoverPosition = useSetAtomComponentState( - recordTableHoverPositionComponentState, - recordTableId, - ); + const recordTableHoverPositionCallbackState = + useAtomComponentStateCallbackState( + recordTableHoverPositionComponentState, + recordTableId, + ); const isSomeCellInEditMode = useAtomComponentSelectorCallbackState( isSomeCellInEditModeComponentSelector, @@ -87,9 +88,42 @@ export const RecordTableContent = ({ const cellInEditMode = store.get(isSomeCellInEditMode); if (!cellInEditMode) { - setRecordTableHoverPosition(null); + store.set(recordTableHoverPositionCallbackState, null); } - }, [store, isSomeCellInEditMode, setRecordTableHoverPosition]); + }, [store, isSomeCellInEditMode, recordTableHoverPositionCallbackState]); + + const handleDelegatedMouseMove = useCallback( + (event: React.MouseEvent) => { + if (store.get(isSomeCellInEditMode)) { + return; + } + + const target = event.target as HTMLElement; + const cellElement = target.closest( + '[data-record-table-col]', + ); + + if (!cellElement) { + return; + } + + const column = Number(cellElement.dataset.recordTableCol); + const row = Number(cellElement.dataset.recordTableRow); + + if (isNaN(column) || isNaN(row)) { + return; + } + + const lastPosition = store.get(recordTableHoverPositionCallbackState); + + if (lastPosition?.column === column && lastPosition?.row === row) { + return; + } + + store.set(recordTableHoverPositionCallbackState, { column, row }); + }, + [store, isSomeCellInEditMode, recordTableHoverPositionCallbackState], + ); return ( @@ -98,6 +132,7 @@ export const RecordTableContent = ({ isDragging={isDragging} visibleRecordFields={visibleRecordFields} id={RECORD_TABLE_HTML_ID} + onMouseMove={handleDelegatedMouseMove} onMouseLeave={handleMouseLeave} hasRecordGroups={hasRecordGroups} > diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx index 31e15b3063..46f00d3e60 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx @@ -1,6 +1,7 @@ import { type ReactNode } from 'react'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { RecordTableContextProvider as RecordTableContextInternalProvider } from '@/object-record/record-table/contexts/RecordTableContext'; import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject'; @@ -32,6 +33,8 @@ export const RecordTableContextProvider = ({ objectNameSingular, }); + const { objectMetadataItems } = useObjectMetadataItems(); + const objectPermissions = useObjectPermissionsForObject( objectMetadataItem.id, ); @@ -57,6 +60,7 @@ export const RecordTableContextProvider = ({ value={{ viewBarId, objectMetadataItem, + objectMetadataItems, recordTableId, objectNameSingular, objectPermissions, diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx index 7f965b6e35..3a3bce3d4f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx @@ -163,6 +163,7 @@ const meta: Meta = { viewBarId: mockPerformance.recordId, // TODO: update performance mocks with new data, and merge with common mocks if possible objectMetadataItem: mockPerformance.objectMetadataItem as any, + objectMetadataItems: [], objectNameSingular: mockPerformance.objectMetadataItem.nameSingular, objectPermissions: { diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts index e8e74540bc..8af4ceb5a2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts @@ -8,6 +8,7 @@ type RecordTableContextValue = { viewBarId: string; objectNameSingular: string; objectMetadataItem: ObjectMetadataItem; + objectMetadataItems: ObjectMetadataItem[]; objectPermissions: ObjectPermission; visibleRecordFields: RecordField[]; onRecordIdentifierClick?: (rowIndex: number, recordId: string) => void; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx index f1c71eaf13..adf975aab7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx @@ -1,11 +1,11 @@ import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay'; -import { FieldFocusContextProvider } from '@/object-record/record-field/ui/contexts/FieldFocusContextProvider'; +import { FieldFocusStaticUnfocusedProvider } from '@/object-record/record-field/ui/contexts/FieldFocusContextProvider'; import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer'; export const RecordTableCell = () => { return ( - + } /> - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx index 866f97c434..0ab91883da 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx @@ -2,11 +2,9 @@ import { styled } from '@linaria/react'; import { useContext, type ReactNode } from 'react'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; -import { useFieldFocus } from '@/object-record/record-field/ui/hooks/useFieldFocus'; import { isFieldIdentifierDisplay } from '@/object-record/record-field/ui/meta-types/display/utils/isFieldIdentifierDisplay'; import { RECORD_CHIP_CLICK_OUTSIDE_ID } from '@/object-record/record-table/constants/RecordChipClickOutsideId'; import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; -import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell'; import { ThemeContext } from 'twenty-ui/theme'; @@ -60,7 +58,6 @@ export const RecordTableCellBaseContainer = ({ fieldDefinition, isLabelIdentifier, } = useContext(FieldContext); - const { setIsFocused } = useFieldFocus(); const { openTableCell } = useOpenRecordTableCellFromCell(); const { theme } = useContext(ThemeContext); @@ -70,26 +67,13 @@ export const RecordTableCellBaseContainer = ({ fieldDefinition, isLabelIdentifier, ); - const { onMoveHoverToCurrentCell } = useRecordTableBodyContextOrThrow(); - - const handleContainerMouseMove = () => { - setIsFocused(true); - onMoveHoverToCurrentCell(cellPosition); - }; - - const handleContainerMouseLeave = () => { - setIsFocused(false); - }; const handleContainerClick = () => { - onMoveHoverToCurrentCell(cellPosition); openTableCell(); }; return ( { const { recordId, isRecordReadOnly } = useRecordTableRowContextOrThrow(); - const { objectMetadataItem, objectPermissions } = + const { objectMetadataItem, objectMetadataItems, objectPermissions } = useRecordTableContextOrThrow(); const { @@ -35,8 +33,6 @@ export const RecordTableCellFieldContextGeneric = ({ fieldDefinitionByFieldMetadataItemId, } = useRecordIndexContextOrThrow(); - const { objectMetadataItems } = useObjectMetadataItems(); - const fieldDefinition = fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId]; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortalContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortalContent.tsx index 2e18b316e2..8f4c0ae299 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortalContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortalContent.tsx @@ -1,5 +1,6 @@ import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; +import { FieldFocusStaticFocusedProvider } from '@/object-record/record-field/ui/contexts/FieldFocusContextProvider'; import { useIsFieldInputOnly } from '@/object-record/record-field/ui/hooks/useIsFieldInputOnly'; import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; @@ -78,15 +79,17 @@ export const RecordTableCellHoveredPortalContent = () => { showInteractiveStyle={showInteractiveStyle} isRecordTableRowActive={isRecordTableRowActive} > - {isFieldInputOnly ? ( - - - - ) : ( - - - - )} + + {isFieldInputOnly ? ( + + + + ) : ( + + + + )} + {showButton && } ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx index a2cfdf4e20..3ed5ba3f25 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx @@ -15,7 +15,6 @@ import { useAtomComponentFamilySelectorValue } from '@/ui/utilities/state/jotai/ import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { forwardRef, type ReactNode } from 'react'; - type RecordTableTrProps = { children: ReactNode; recordId: string; diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedRowTreadmillEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedRowTreadmillEffect.tsx index 3470f75b7d..b7a2fd089f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedRowTreadmillEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedRowTreadmillEffect.tsx @@ -13,8 +13,8 @@ import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScro import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useDebouncedCallback } from 'use-debounce'; -export const SCROLL_SPEED_THRESHOLD_IN_ROWS_PER_SECOND_TO_ACTIVATE_LOW_DETAILS = 120; -export const SCROLL_SPEED_THRESHOLD_IN_ROWS_PER_SECOND_TO_DEACTIVATE_LOW_DETAILS = 30; +export const SCROLL_SPEED_THRESHOLD_IN_ROWS_PER_SECOND_TO_ACTIVATE_LOW_DETAILS = 200; +export const SCROLL_SPEED_THRESHOLD_IN_ROWS_PER_SECOND_TO_DEACTIVATE_LOW_DETAILS = 50; export const TIME_BEFORE_DEACTIVATING_LOW_DETAILS = 20; export const NUMBER_OF_EVENTS_TO_COMPUTE_AVERAGE = 10; diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/constants/TableVirtualizationNumberOfOverscanPages.ts b/packages/twenty-front/src/modules/object-record/record-table/virtualization/constants/TableVirtualizationNumberOfOverscanPages.ts index 43bf957c9d..fb2cbc0f48 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/constants/TableVirtualizationNumberOfOverscanPages.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/constants/TableVirtualizationNumberOfOverscanPages.ts @@ -1 +1 @@ -export const TABLE_VIRTUALIZATION_NUMBER_OF_OVERSCAN_PAGES = 3; +export const TABLE_VIRTUALIZATION_NUMBER_OF_OVERSCAN_PAGES = 5; diff --git a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx index fc204f04a8..dc309c840a 100644 --- a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx @@ -104,6 +104,7 @@ const InternalTableContextProviders = ({ children: React.ReactNode; objectMetadataItem: ObjectMetadataItem; }) => { + const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const currentRecordFields = useAtomComponentStateValue( @@ -174,6 +175,7 @@ const InternalTableContextProviders = ({ value={{ objectNameSingular: objectMetadataItem.nameSingular, objectMetadataItem: objectMetadataItem, + objectMetadataItems: objectMetadataItems, recordTableId: objectMetadataItem.namePlural, viewBarId: 'view-bar', objectPermissions: getObjectPermissionsFromMapByObjectMetadataId({