Ensure record fields uniqueness across widgets (#16781)
This PR dissociates record inputs so that several inputs for the same record field can live on the same page. ## Simple values demo https://github.com/user-attachments/assets/a8c224d8-4cd6-4fe3-9cf8-01f6bdd2fca9 ## Relations demo https://github.com/user-attachments/assets/87267a8b-8ce5-41ce-8a78-cba2384828bb ## Doesn't break Record Table https://github.com/user-attachments/assets/bcdd2b05-7cb4-4ed2-9093-58be3e04e43e ## Doesn't break Show Page https://github.com/user-attachments/assets/f9ab9e14-012c-451e-a35f-01a165523f95
This commit is contained in:
committed by
GitHub
parent
6a4974e285
commit
06d0ac13c4
+32
-24
@@ -14,12 +14,14 @@ import { RecordBoardCardBody } from '@/object-record/record-board/record-board-c
|
||||
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
|
||||
import { RecordBoardCardMultiDragStack } from '@/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragStack';
|
||||
import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId';
|
||||
import { RECORD_BOARD_CARD_INPUT_ID_PREFIX } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix';
|
||||
import { RecordBoardCardComponentInstanceContext } from '@/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext';
|
||||
import { recordBoardCardIsExpandedComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardIsExpandedComponentState';
|
||||
import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext';
|
||||
import { RecordCard } from '@/object-record/record-card/components/RecordCard';
|
||||
import { isRecordIdPrimaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState';
|
||||
import { isRecordIdSecondaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState';
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
@@ -151,34 +153,40 @@ export const RecordBoardCard = () => {
|
||||
instanceId: `record-board-card-${recordId}`,
|
||||
}}
|
||||
>
|
||||
<StyledBoardCardWrapper
|
||||
data-click-outside-id={RECORD_BOARD_CARD_CLICK_OUTSIDE_ID}
|
||||
onContextMenu={handleContextMenuOpen}
|
||||
<RecordFieldsScopeContextProvider
|
||||
value={{ scopeInstanceId: RECORD_BOARD_CARD_INPUT_ID_PREFIX }}
|
||||
>
|
||||
<StyledCardContainer isPrimaryMultiDrag={isRecordIdPrimaryDragMultiple}>
|
||||
{isRecordIdPrimaryDragMultiple && <RecordBoardCardMultiDragStack />}
|
||||
<RecordCard
|
||||
data-selected={isCurrentCardSelected}
|
||||
data-focused={isCurrentCardFocused}
|
||||
data-active={isCurrentCardActive}
|
||||
onMouseLeave={onMouseLeaveBoard}
|
||||
onClick={handleCardClick}
|
||||
<StyledBoardCardWrapper
|
||||
data-click-outside-id={RECORD_BOARD_CARD_CLICK_OUTSIDE_ID}
|
||||
onContextMenu={handleContextMenuOpen}
|
||||
>
|
||||
<StyledCardContainer
|
||||
isPrimaryMultiDrag={isRecordIdPrimaryDragMultiple}
|
||||
isSecondaryDragged={isRecordIdSecondaryDragMultiple}
|
||||
isDragging={isDraggingThisCard}
|
||||
>
|
||||
<RecordBoardCardHeader />
|
||||
<AnimatedEaseInOut
|
||||
isOpen={isCardExpanded || !isCompactModeActive}
|
||||
initial={false}
|
||||
{isRecordIdPrimaryDragMultiple && <RecordBoardCardMultiDragStack />}
|
||||
<RecordCard
|
||||
data-selected={isCurrentCardSelected}
|
||||
data-focused={isCurrentCardFocused}
|
||||
data-active={isCurrentCardActive}
|
||||
onMouseLeave={onMouseLeaveBoard}
|
||||
onClick={handleCardClick}
|
||||
isPrimaryMultiDrag={isRecordIdPrimaryDragMultiple}
|
||||
isSecondaryDragged={isRecordIdSecondaryDragMultiple}
|
||||
isDragging={isDraggingThisCard}
|
||||
>
|
||||
<RecordBoardCardBody />
|
||||
</AnimatedEaseInOut>
|
||||
</RecordCard>
|
||||
</StyledCardContainer>
|
||||
<RecordBoardCardCellHoveredPortal />
|
||||
<RecordBoardCardCellEditModePortal />
|
||||
</StyledBoardCardWrapper>
|
||||
<RecordBoardCardHeader />
|
||||
<AnimatedEaseInOut
|
||||
isOpen={isCardExpanded || !isCompactModeActive}
|
||||
initial={false}
|
||||
>
|
||||
<RecordBoardCardBody />
|
||||
</AnimatedEaseInOut>
|
||||
</RecordCard>
|
||||
</StyledCardContainer>
|
||||
<RecordBoardCardCellHoveredPortal />
|
||||
<RecordBoardCardCellEditModePortal />
|
||||
</StyledBoardCardWrapper>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
</RecordBoardCardComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
+22
-16
@@ -6,10 +6,12 @@ import { RecordCalendarCardCellHoveredPortal } from '@/object-record/record-cale
|
||||
import { RecordCalendarCardBody } from '@/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardBody';
|
||||
import { RecordCalendarCardHeader } from '@/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader';
|
||||
import { RECORD_CALENDAR_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-calendar/record-calendar-card/constants/RecordCalendarCardClickOutsideId';
|
||||
import { RECORD_CALENDAR_CARD_INPUT_ID_PREFIX } from '@/object-record/record-calendar/record-calendar-card/constants/RecordCalendarCardInputIdPrefix';
|
||||
import { RecordCalendarCardComponentInstanceContext } from '@/object-record/record-calendar/record-calendar-card/states/contexts/RecordCalendarCardComponentInstanceContext';
|
||||
import { isRecordCalendarCardSelectedComponentFamilyState } from '@/object-record/record-calendar/record-calendar-card/states/isRecordCalendarCardSelectedComponentFamilyState';
|
||||
import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext';
|
||||
import { RecordCard } from '@/object-record/record-card/components/RecordCard';
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState';
|
||||
@@ -78,22 +80,26 @@ export const RecordCalendarCard = ({ recordId }: RecordCalendarCardProps) => {
|
||||
instanceId: recordId,
|
||||
}}
|
||||
>
|
||||
<StyledContainer onContextMenu={handleContextMenuOpen}>
|
||||
<StyledRecordCard
|
||||
data-selected={isCurrentCardSelected}
|
||||
data-click-outside-id={RECORD_CALENDAR_CARD_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
<RecordCalendarCardHeader recordId={recordId} />
|
||||
<AnimatedEaseInOut isOpen={!isCompactModeActive} initial={false}>
|
||||
<RecordCalendarCardBody
|
||||
recordId={recordId}
|
||||
isRecordReadOnly={false}
|
||||
/>
|
||||
</AnimatedEaseInOut>
|
||||
</StyledRecordCard>
|
||||
<RecordCalendarCardCellHoveredPortal recordId={recordId} />
|
||||
<RecordCalendarCardCellEditModePortal recordId={recordId} />
|
||||
</StyledContainer>
|
||||
<RecordFieldsScopeContextProvider
|
||||
value={{ scopeInstanceId: RECORD_CALENDAR_CARD_INPUT_ID_PREFIX }}
|
||||
>
|
||||
<StyledContainer onContextMenu={handleContextMenuOpen}>
|
||||
<StyledRecordCard
|
||||
data-selected={isCurrentCardSelected}
|
||||
data-click-outside-id={RECORD_CALENDAR_CARD_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
<RecordCalendarCardHeader recordId={recordId} />
|
||||
<AnimatedEaseInOut isOpen={!isCompactModeActive} initial={false}>
|
||||
<RecordCalendarCardBody
|
||||
recordId={recordId}
|
||||
isRecordReadOnly={false}
|
||||
/>
|
||||
</AnimatedEaseInOut>
|
||||
</StyledRecordCard>
|
||||
<RecordCalendarCardCellHoveredPortal recordId={recordId} />
|
||||
<RecordCalendarCardCellEditModePortal recordId={recordId} />
|
||||
</StyledContainer>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
</RecordCalendarCardComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { createRequiredContext } from '~/utils/createRequiredContext';
|
||||
|
||||
export type RecordFieldsScopeContextValue = {
|
||||
scopeInstanceId: string;
|
||||
};
|
||||
|
||||
export const [
|
||||
RecordFieldsScopeContextProvider,
|
||||
useRecordFieldsScopeContextOrThrow,
|
||||
] = createRequiredContext<RecordFieldsScopeContextValue>(
|
||||
'RecordFieldsScopeContext',
|
||||
);
|
||||
+3
@@ -1,6 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { RecordDetailSectionContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailSectionContainer';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import {
|
||||
@@ -26,6 +27,7 @@ type RecordDetailMorphRelationSectionProps = {
|
||||
export const RecordDetailMorphRelationSection = ({
|
||||
loading,
|
||||
}: RecordDetailMorphRelationSectionProps) => {
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const metadata = fieldDefinition.metadata as FieldMorphRelationMetadata;
|
||||
@@ -44,6 +46,7 @@ export const RecordDetailMorphRelationSection = ({
|
||||
const dropdownId = getRecordFieldCardRelationPickerDropdownId({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
const isDropdownOpen = useRecoilComponentValue(
|
||||
|
||||
+3
@@ -1,6 +1,7 @@
|
||||
import { type ReactNode, useCallback, useContext } from 'react';
|
||||
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { useGetMorphRelationRelatedRecordsWithObjectNameSingular } from '@/object-record/record-field-list/record-detail-section/relation/components/hooks/useGetMorphRelationRelatedRecordsWithObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
@@ -29,6 +30,7 @@ type RecordDetailMorphRelationSectionDropdownManyToOneProps = {
|
||||
export const RecordDetailMorphRelationSectionDropdownManyToOne = ({
|
||||
dropdownTriggerClickableComponent,
|
||||
}: RecordDetailMorphRelationSectionDropdownManyToOneProps) => {
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(
|
||||
@@ -58,6 +60,7 @@ export const RecordDetailMorphRelationSectionDropdownManyToOne = ({
|
||||
const dropdownId = getRecordFieldCardRelationPickerDropdownId({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
+3
@@ -1,6 +1,7 @@
|
||||
import { type ReactNode, useCallback, useContext } from 'react';
|
||||
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { useGetMorphRelationRelatedRecordsWithObjectNameSingular } from '@/object-record/record-field-list/record-detail-section/relation/components/hooks/useGetMorphRelationRelatedRecordsWithObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useUpdateMorphRelationOneToManyFieldInput } from '@/object-record/record-field/ui/meta-types/input/hooks/useUpdateMorphRelationOneToManyFieldInput';
|
||||
@@ -31,6 +32,7 @@ type RecordDetailMorphRelationSectionDropdownOneToManyProps = {
|
||||
export const RecordDetailMorphRelationSectionDropdownOneToMany = ({
|
||||
dropdownTriggerClickableComponent,
|
||||
}: RecordDetailMorphRelationSectionDropdownOneToManyProps) => {
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(
|
||||
@@ -61,6 +63,7 @@ export const RecordDetailMorphRelationSectionDropdownOneToMany = ({
|
||||
const dropdownId = getRecordFieldCardRelationPickerDropdownId({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
+23
-11
@@ -12,6 +12,10 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { RecordFieldList } from '@/object-record/record-field-list/components/RecordFieldList';
|
||||
import {
|
||||
RecordFieldsScopeContextProvider,
|
||||
useRecordFieldsScopeContextOrThrow,
|
||||
} from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { RecordDetailRecordsListItemContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListItemContainer';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
@@ -30,13 +34,13 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
computeMorphRelationFieldName,
|
||||
CustomError,
|
||||
} from 'twenty-shared/utils';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconDotsVertical,
|
||||
@@ -99,6 +103,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
relationObjectMetadataNameSingular,
|
||||
relationFieldMetadataId,
|
||||
}: RecordDetailRelationRecordsListItemProps) => {
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
const {
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
@@ -162,6 +167,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
const dropdownId = getRecordFieldCardRelationPickerDropdownId({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
const setSingleRecordPickerSelectedId = useSetRecoilComponentState(
|
||||
singleRecordPickerSelectedIdComponentState,
|
||||
@@ -284,15 +290,21 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
)}
|
||||
</StyledListItem>
|
||||
<AnimatedEaseInOut isOpen={isExpanded}>
|
||||
<RecordFieldList
|
||||
instanceId={`record-detail-relation-${relationRecord.id}-${isInRightDrawer ? 'right-drawer' : ''}`}
|
||||
objectNameSingular={relationObjectMetadataNameSingular}
|
||||
objectRecordId={relationRecord.id}
|
||||
showDuplicatesSection={false}
|
||||
showRelationSections={false}
|
||||
excludeCreatedAtAndUpdatedAt={true}
|
||||
excludeFieldMetadataIds={[relationFieldMetadataId]}
|
||||
/>
|
||||
<RecordFieldsScopeContextProvider
|
||||
value={{
|
||||
scopeInstanceId: `record-detail-relation-${relationRecord.id}${isInRightDrawer ? '-right-drawer' : ''}`,
|
||||
}}
|
||||
>
|
||||
<RecordFieldList
|
||||
instanceId={`record-detail-relation-${relationRecord.id}-${isInRightDrawer ? 'right-drawer' : ''}`}
|
||||
objectNameSingular={relationObjectMetadataNameSingular}
|
||||
objectRecordId={relationRecord.id}
|
||||
showDuplicatesSection={false}
|
||||
showRelationSections={false}
|
||||
excludeCreatedAtAndUpdatedAt={true}
|
||||
excludeFieldMetadataIds={[relationFieldMetadataId]}
|
||||
/>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
</AnimatedEaseInOut>
|
||||
{createPortal(
|
||||
<ConfirmationModal
|
||||
|
||||
+3
@@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { RecordDetailSectionContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailSectionContainer';
|
||||
import { RecordDetailRelationRecordsList } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsList';
|
||||
import { RecordDetailRelationSectionDropdown } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdown';
|
||||
@@ -46,6 +47,7 @@ export const RecordDetailRelationSection = ({
|
||||
}: RecordDetailRelationSectionProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const {
|
||||
@@ -95,6 +97,7 @@ export const RecordDetailRelationSection = ({
|
||||
const dropdownId = getRecordFieldCardRelationPickerDropdownId({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
const isDropdownOpen = useRecoilComponentValue(
|
||||
|
||||
+3
@@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getFieldMetadataItemById } from '@/object-metadata/utils/getFieldMetadataItemById';
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/ui/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||
import { useUpdateRelationOneToManyFieldInput } from '@/object-record/record-field/ui/meta-types/input/hooks/useUpdateRelationOneToManyFieldInput';
|
||||
@@ -33,6 +34,7 @@ type RecordDetailRelationSectionDropdownToManyProps = {
|
||||
export const RecordDetailRelationSectionDropdownToMany = ({
|
||||
dropdownTriggerClickableComponent,
|
||||
}: RecordDetailRelationSectionDropdownToManyProps) => {
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
const { fieldMetadataId } = fieldDefinition;
|
||||
const {
|
||||
@@ -78,6 +80,7 @@ export const RecordDetailRelationSectionDropdownToMany = ({
|
||||
const dropdownId = getRecordFieldCardRelationPickerDropdownId({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
+3
@@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/ui/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||
@@ -36,6 +37,7 @@ type RecordDetailRelationSectionDropdownToOneProps = {
|
||||
export const RecordDetailRelationSectionDropdownToOne = ({
|
||||
dropdownTriggerClickableComponent,
|
||||
}: RecordDetailRelationSectionDropdownToOneProps) => {
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
assertFieldMetadata(
|
||||
FieldMetadataType.RELATION,
|
||||
@@ -89,6 +91,7 @@ export const RecordDetailRelationSectionDropdownToOne = ({
|
||||
const dropdownId = getRecordFieldCardRelationPickerDropdownId({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
+6
-1
@@ -10,6 +10,7 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { RightDrawerDecorator } from '~/testing/decorators/RightDrawerDecorator';
|
||||
@@ -49,7 +50,11 @@ const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
<RecordFieldsScopeContextProvider
|
||||
value={{ scopeInstanceId: 'mock-instance-id' }}
|
||||
>
|
||||
<Story />
|
||||
</RecordFieldsScopeContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
),
|
||||
|
||||
+10
-5
@@ -5,6 +5,7 @@ import { useEffect } from 'react';
|
||||
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { getFieldInputEventContextProviderWithJestMocks } from '@/object-record/record-field/ui/meta-types/input/components/__stories__/utils/getFieldInputEventContextProviderWithJestMocks';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
@@ -90,11 +91,15 @@ const DateFieldInputWithContext = ({
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<FieldInputEventContextProviderWithJestMocks>
|
||||
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
||||
<DateFieldValueSetterEffect value={value} />
|
||||
<DateFieldValueGater />
|
||||
</FieldInputEventContextProviderWithJestMocks>
|
||||
<RecordFieldsScopeContextProvider
|
||||
value={{ scopeInstanceId: instanceId }}
|
||||
>
|
||||
<FieldInputEventContextProviderWithJestMocks>
|
||||
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
||||
<DateFieldValueSetterEffect value={value} />
|
||||
<DateFieldValueGater />
|
||||
</FieldInputEventContextProviderWithJestMocks>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
</FieldContext.Provider>
|
||||
<div data-testid="data-field-input-click-outside-div"></div>
|
||||
</RecordFieldComponentInstanceContext.Provider>
|
||||
|
||||
+10
-5
@@ -6,6 +6,7 @@ import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePush
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { getFieldInputEventContextProviderWithJestMocks } from '@/object-record/record-field/ui/meta-types/input/components/__stories__/utils/getFieldInputEventContextProviderWithJestMocks';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
@@ -96,11 +97,15 @@ const NumberFieldInputWithContext = ({
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<FieldInputEventContextProviderWithJestMocks>
|
||||
{isReady && <StorybookFieldInputDropdownFocusIdSetterEffect />}
|
||||
<NumberFieldValueSetterEffect value={value} />
|
||||
<NumberFieldInput />
|
||||
</FieldInputEventContextProviderWithJestMocks>
|
||||
<RecordFieldsScopeContextProvider
|
||||
value={{ scopeInstanceId: RECORD_TABLE_CELL_INPUT_ID_PREFIX }}
|
||||
>
|
||||
<FieldInputEventContextProviderWithJestMocks>
|
||||
{isReady && <StorybookFieldInputDropdownFocusIdSetterEffect />}
|
||||
<NumberFieldValueSetterEffect value={value} />
|
||||
<NumberFieldInput />
|
||||
</FieldInputEventContextProviderWithJestMocks>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
</FieldContext.Provider>
|
||||
{isReady && <div data-testid="is-ready-marker" />}
|
||||
<div data-testid="data-field-input-click-outside-div" />
|
||||
|
||||
+9
-5
@@ -9,6 +9,7 @@ import { useGetButtonIcon } from '@/object-record/record-field/ui/hooks/useGetBu
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/ui/hooks/useIsFieldInputOnly';
|
||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/ui/hooks/useOpenFieldInputEditMode';
|
||||
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import {
|
||||
FieldInputEventContext,
|
||||
type FieldInputClickOutsideEvent,
|
||||
@@ -44,6 +45,7 @@ export const RecordInlineCell = ({
|
||||
onCloseEditMode: onCloseEditModeFromContext,
|
||||
isRecordFieldReadOnly: isReadOnly,
|
||||
} = useContext(FieldContext);
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
|
||||
const { openFieldInput, closeFieldInput } = useOpenFieldInputEditMode();
|
||||
|
||||
@@ -142,11 +144,12 @@ export const RecordInlineCell = ({
|
||||
.getLoadable(activeDropdownFocusIdState)
|
||||
.getValue();
|
||||
|
||||
const expectedDropdownFocusId = getDropdownFocusIdForRecordField(
|
||||
const expectedDropdownFocusId = getDropdownFocusIdForRecordField({
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'inline-cell',
|
||||
);
|
||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||
componentType: 'inline-cell',
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
if (currentDropdownFocusId !== expectedDropdownFocusId) {
|
||||
return;
|
||||
@@ -162,9 +165,10 @@ export const RecordInlineCell = ({
|
||||
closeInlineCell();
|
||||
},
|
||||
[
|
||||
closeInlineCell,
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
scopeInstanceId,
|
||||
closeInlineCell,
|
||||
persistFieldFromFieldInputContext,
|
||||
],
|
||||
);
|
||||
|
||||
+2
-1
@@ -101,8 +101,9 @@ export const RecordInlineCellAnchoredPortal = ({
|
||||
>
|
||||
<RecordInlineCellAnchoredPortalContext>
|
||||
{children}
|
||||
|
||||
<RecordInlineCellCloseOnCommandMenuOpeningEffect />
|
||||
</RecordInlineCellAnchoredPortalContext>
|
||||
<RecordInlineCellCloseOnCommandMenuOpeningEffect />
|
||||
</RecordFieldComponentInstanceContext.Provider>,
|
||||
anchorElement,
|
||||
)}
|
||||
|
||||
+10
-7
@@ -2,6 +2,7 @@ import { useContext } from 'react';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { useInitDraftValue } from '@/object-record/record-field/ui/hooks/useInitDraftValue';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||
@@ -21,6 +22,7 @@ export const useInlineCell = (
|
||||
);
|
||||
|
||||
const { onOpenEditMode, onCloseEditMode } = useRecordInlineCellContext();
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
@@ -43,13 +45,14 @@ export const useInlineCell = (
|
||||
fieldComponentInstanceId: recordFieldComponentInstanceId,
|
||||
});
|
||||
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
||||
getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'inline-cell',
|
||||
),
|
||||
);
|
||||
const dropdownId = getDropdownFocusIdForRecordField({
|
||||
recordId,
|
||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||
componentType: 'inline-cell',
|
||||
instanceId: scopeInstanceId,
|
||||
});
|
||||
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(dropdownId);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+11
-6
@@ -1,4 +1,5 @@
|
||||
import { RecordFieldList } from '@/object-record/record-field-list/components/RecordFieldList';
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { type FieldCardConfiguration } from '@/object-record/record-show/types/CardConfiguration';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
|
||||
@@ -11,12 +12,16 @@ export const FieldsCard = ({ configuration }: FieldsCardProps) => {
|
||||
const targetRecord = useTargetRecord();
|
||||
const { isInRightDrawer } = useLayoutRenderingContext();
|
||||
|
||||
const instanceId = `fields-card-${targetRecord.id}${isInRightDrawer ? '-right-drawer' : ''}`;
|
||||
|
||||
return (
|
||||
<RecordFieldList
|
||||
instanceId={`fields-card-${targetRecord.id}-${isInRightDrawer ? 'right-drawer' : ''}`}
|
||||
objectNameSingular={targetRecord.targetObjectNameSingular}
|
||||
objectRecordId={targetRecord.id}
|
||||
showDuplicatesSection={configuration?.showDuplicatesSection ?? true}
|
||||
/>
|
||||
<RecordFieldsScopeContextProvider value={{ scopeInstanceId: instanceId }}>
|
||||
<RecordFieldList
|
||||
instanceId={instanceId}
|
||||
objectNameSingular={targetRecord.targetObjectNameSingular}
|
||||
objectRecordId={targetRecord.id}
|
||||
showDuplicatesSection={configuration?.showDuplicatesSection ?? true}
|
||||
/>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-1
@@ -4,9 +4,11 @@ import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldM
|
||||
type GetRecordFieldCardRelationPickerDropdownIdArgs = {
|
||||
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||
recordId: string;
|
||||
instanceId: string;
|
||||
};
|
||||
export const getRecordFieldCardRelationPickerDropdownId = ({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceId,
|
||||
}: GetRecordFieldCardRelationPickerDropdownIdArgs) =>
|
||||
`record-field-card-relation-picker-${fieldDefinition.fieldMetadataId}-${recordId}`;
|
||||
`record-field-card-relation-picker:${instanceId}:${fieldDefinition.fieldMetadataId}:${recordId}`;
|
||||
|
||||
+22
-16
@@ -4,8 +4,10 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { RecordTableContextProvider as RecordTableContextInternalProvider } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { RECORD_TABLE_CELL_INPUT_ID_PREFIX } from '@/object-record/record-table/constants/RecordTableCellInputIdPrefix';
|
||||
import { RECORD_TABLE_COLUMN_MIN_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnMinWidth';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
@@ -46,22 +48,26 @@ export const RecordTableContextProvider = ({
|
||||
: 'MOUSE_DOWN';
|
||||
|
||||
return (
|
||||
<RecordTableContextInternalProvider
|
||||
value={{
|
||||
viewBarId,
|
||||
objectMetadataItem,
|
||||
recordTableId,
|
||||
objectNameSingular,
|
||||
objectPermissions,
|
||||
visibleRecordFields: visibleRecordFields.map((field) => ({
|
||||
...field,
|
||||
size: Math.max(field.size, RECORD_TABLE_COLUMN_MIN_WIDTH),
|
||||
})),
|
||||
onRecordIdentifierClick,
|
||||
triggerEvent,
|
||||
}}
|
||||
<RecordFieldsScopeContextProvider
|
||||
value={{ scopeInstanceId: RECORD_TABLE_CELL_INPUT_ID_PREFIX }}
|
||||
>
|
||||
{children}
|
||||
</RecordTableContextInternalProvider>
|
||||
<RecordTableContextInternalProvider
|
||||
value={{
|
||||
viewBarId,
|
||||
objectMetadataItem,
|
||||
recordTableId,
|
||||
objectNameSingular,
|
||||
objectPermissions,
|
||||
visibleRecordFields: visibleRecordFields.map((field) => ({
|
||||
...field,
|
||||
size: Math.max(field.size, RECORD_TABLE_COLUMN_MIN_WIDTH),
|
||||
})),
|
||||
onRecordIdentifierClick,
|
||||
triggerEvent,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecordTableContextInternalProvider>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
+10
-5
@@ -21,6 +21,7 @@ import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropd
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
|
||||
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
|
||||
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
|
||||
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
|
||||
@@ -41,6 +42,8 @@ export type OpenTableCellArgs = {
|
||||
};
|
||||
|
||||
export const useOpenRecordTableCell = (recordTableId: string) => {
|
||||
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
|
||||
|
||||
const clickOutsideListenerIsActivatedState = useRecoilComponentCallbackState(
|
||||
clickOutsideListenerIsActivatedComponentState,
|
||||
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
|
||||
@@ -157,11 +160,12 @@ export const useOpenRecordTableCell = (recordTableId: string) => {
|
||||
toggleClickOutside(false);
|
||||
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
||||
getDropdownFocusIdForRecordField(
|
||||
getDropdownFocusIdForRecordField({
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'table-cell',
|
||||
),
|
||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||
componentType: 'table-cell',
|
||||
instanceId: scopeInstanceId,
|
||||
}),
|
||||
);
|
||||
},
|
||||
[
|
||||
@@ -175,10 +179,11 @@ export const useOpenRecordTableCell = (recordTableId: string) => {
|
||||
initDraftValue,
|
||||
toggleClickOutside,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
scopeInstanceId,
|
||||
leaveTableFocus,
|
||||
openRecordFromIndexView,
|
||||
activateRecordTableRow,
|
||||
unfocusRecordTableRow,
|
||||
openRecordFromIndexView,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+12
-6
@@ -1,7 +1,13 @@
|
||||
export const getDropdownFocusIdForRecordField = (
|
||||
recordId: string,
|
||||
fieldMetadataId: string,
|
||||
componentType: 'table-cell' | 'inline-cell',
|
||||
) => {
|
||||
return `dropdown-${componentType}-record-${recordId}-field-${fieldMetadataId}`;
|
||||
export const getDropdownFocusIdForRecordField = ({
|
||||
recordId,
|
||||
fieldMetadataId,
|
||||
componentType,
|
||||
instanceId,
|
||||
}: {
|
||||
recordId: string;
|
||||
fieldMetadataId: string;
|
||||
componentType: 'table-cell' | 'inline-cell';
|
||||
instanceId: string;
|
||||
}) => {
|
||||
return `dropdown-${instanceId}-${componentType}-record-${recordId}-field-${fieldMetadataId}`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user