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:
Baptiste Devessier
2025-12-24 17:01:10 +01:00
committed by GitHub
parent 6a4974e285
commit 06d0ac13c4
37 changed files with 620 additions and 436 deletions
@@ -3,6 +3,7 @@ import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/uti
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
import { RecordDetailSectionContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailSectionContainer';
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
@@ -44,11 +45,11 @@ type FieldsWidgetProps = {
widget: PageLayoutWidget;
};
export const FieldsWidget = ({ widget: _widget }: FieldsWidgetProps) => {
export const FieldsWidget = ({ widget }: FieldsWidgetProps) => {
const targetRecord = useTargetRecord();
const { isInRightDrawer } = useLayoutRenderingContext();
const instanceId = `fields-widget-${targetRecord.id}-${isInRightDrawer ? 'right-drawer' : ''}`;
const instanceId = `fields-widget-${widget.id}-${targetRecord.id}${isInRightDrawer ? '-right-drawer' : ''}`;
const { recordLoading, isPrefetchLoading } = useRecordShowContainerData({
objectRecordId: targetRecord.id,
@@ -103,93 +104,97 @@ export const FieldsWidget = ({ widget: _widget }: FieldsWidgetProps) => {
}
return (
<StyledContainer>
<RecordFieldListComponentInstanceContext.Provider
value={{
instanceId,
}}
>
{sectionsWithFieldIndices.map((section) => (
<RecordDetailSectionContainer key={section.id} title={section.title}>
<PropertyBox>
{isPrefetchLoading ? (
<PropertyBoxSkeletonLoader />
) : (
<>
{section.fields.map(
({ field: fieldMetadataItem, globalIndex }) => {
return (
<FieldContext.Provider
key={targetRecord.id + fieldMetadataItem.id}
value={{
recordId: targetRecord.id,
maxWidth: 200,
isLabelIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
position: globalIndex,
objectMetadataItem,
showLabel: true,
labelWidth: 90,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
isDisplayModeFixHeight: true,
isRecordFieldReadOnly: isRecordFieldReadOnly({
isRecordReadOnly,
objectPermissions:
getObjectPermissionsFromMapByObjectMetadataId({
objectPermissionsByObjectMetadataId,
objectMetadataId: objectMetadataItem.id,
}),
fieldMetadataItem: {
id: fieldMetadataItem.id,
isUIReadOnly:
fieldMetadataItem.isUIReadOnly ?? false,
},
}),
onMouseEnter: () =>
setRecordFieldListHoverPosition(globalIndex),
anchorId: `${getRecordFieldInputInstanceId({
recordId: targetRecord.id,
fieldName: fieldMetadataItem.name,
prefix: instanceId,
})}`,
}}
>
<RecordFieldComponentInstanceContext.Provider
<RecordFieldsScopeContextProvider value={{ scopeInstanceId: instanceId }}>
<StyledContainer>
<RecordFieldListComponentInstanceContext.Provider
value={{
instanceId,
}}
>
{sectionsWithFieldIndices.map((section) => (
<RecordDetailSectionContainer
key={section.id}
title={section.title}
>
<PropertyBox>
{isPrefetchLoading ? (
<PropertyBoxSkeletonLoader />
) : (
<>
{section.fields.map(
({ field: fieldMetadataItem, globalIndex }) => {
return (
<FieldContext.Provider
key={targetRecord.id + fieldMetadataItem.id}
value={{
instanceId: getRecordFieldInputInstanceId({
recordId: targetRecord.id,
maxWidth: 200,
isLabelIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
position: globalIndex,
objectMetadataItem,
showLabel: true,
labelWidth: 90,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
isDisplayModeFixHeight: true,
isRecordFieldReadOnly: isRecordFieldReadOnly({
isRecordReadOnly,
objectPermissions:
getObjectPermissionsFromMapByObjectMetadataId(
{
objectPermissionsByObjectMetadataId,
objectMetadataId: objectMetadataItem.id,
},
),
fieldMetadataItem: {
id: fieldMetadataItem.id,
isUIReadOnly:
fieldMetadataItem.isUIReadOnly ?? false,
},
}),
onMouseEnter: () =>
setRecordFieldListHoverPosition(globalIndex),
anchorId: `${getRecordFieldInputInstanceId({
recordId: targetRecord.id,
fieldName: fieldMetadataItem.name,
prefix: instanceId,
}),
})}`,
}}
>
<RecordInlineCell
loading={recordLoading}
instanceIdPrefix={instanceId}
/>
</RecordFieldComponentInstanceContext.Provider>
</FieldContext.Provider>
);
},
)}
</>
)}
</PropertyBox>
</RecordDetailSectionContainer>
))}
<RecordFieldComponentInstanceContext.Provider
value={{
instanceId: getRecordFieldInputInstanceId({
recordId: targetRecord.id,
fieldName: fieldMetadataItem.name,
prefix: instanceId,
}),
}}
>
<RecordInlineCell loading={recordLoading} />
</RecordFieldComponentInstanceContext.Provider>
</FieldContext.Provider>
);
},
)}
</>
)}
</PropertyBox>
</RecordDetailSectionContainer>
))}
<FieldsWidgetCellHoveredPortal
objectMetadataItem={objectMetadataItem}
recordId={targetRecord.id}
/>
<FieldsWidgetCellEditModePortal
objectMetadataItem={objectMetadataItem}
recordId={targetRecord.id}
/>
</RecordFieldListComponentInstanceContext.Provider>
</StyledContainer>
<FieldsWidgetCellHoveredPortal
objectMetadataItem={objectMetadataItem}
recordId={targetRecord.id}
/>
<FieldsWidgetCellEditModePortal
objectMetadataItem={objectMetadataItem}
recordId={targetRecord.id}
/>
</RecordFieldListComponentInstanceContext.Provider>
</StyledContainer>
</RecordFieldsScopeContextProvider>
);
};