slice-machine-ui 2.17.0 → 2.17.1-alpha.jp-cr-ui-nested-ct-remove-section.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,7 @@ import {
22
22
  } from "@prismicio/editor-ui";
23
23
  import {
24
24
  CustomType,
25
+ DynamicWidget,
25
26
  Group,
26
27
  Link,
27
28
  LinkConfig,
@@ -260,11 +261,11 @@ export function ContentRelationshipFieldPicker(
260
261
  function ContentRelationshipFieldPickerContent(
261
262
  props: ContentRelationshipFieldPickerProps,
262
263
  ) {
263
- const { value, onChange } = props;
264
- const { allCustomTypes, pickedCustomTypes } = useCustomTypes(value);
264
+ const { value: linkCustomtypes, onChange } = props;
265
+ const { allCustomTypes, pickedCustomTypes } = useCustomTypes(linkCustomtypes);
265
266
 
266
- const fieldCheckMap = value
267
- ? convertLinkCustomtypesToFieldCheckMap(value)
267
+ const fieldCheckMap = linkCustomtypes
268
+ ? convertLinkCustomtypesToFieldCheckMap({ linkCustomtypes, allCustomTypes })
268
269
  : {};
269
270
 
270
271
  function onCustomTypesChange(id: string, newCustomType: PickerCustomType) {
@@ -274,22 +275,24 @@ function ContentRelationshipFieldPickerContent(
274
275
  // represent new types added without any picked fields.
275
276
  onChange(
276
277
  mergeAndConvertCheckMapToLinkCustomtypes({
277
- existingLinkCustomtypes: value,
278
- previousPickerCustomtypes: fieldCheckMap,
279
- customTypeId: id,
278
+ fieldCheckMap,
280
279
  newCustomType,
280
+ linkCustomtypes,
281
+ customTypeId: id,
281
282
  }),
282
283
  );
283
284
  }
284
285
 
285
286
  function addCustomType(id: string) {
286
- const newFields = value ? [...value, id] : [id];
287
+ const newFields = linkCustomtypes ? [...linkCustomtypes, id] : [id];
287
288
  onChange(newFields);
288
289
  }
289
290
 
290
291
  function removeCustomType(id: string) {
291
- if (value) {
292
- onChange(value.filter((existingCt) => getId(existingCt) !== id));
292
+ if (linkCustomtypes) {
293
+ onChange(
294
+ linkCustomtypes.filter((existingCt) => getId(existingCt) !== id),
295
+ );
293
296
  }
294
297
  }
295
298
 
@@ -409,8 +412,9 @@ function ContentRelationshipFieldPickerContent(
409
412
  rel="noopener noreferrer"
410
413
  style={{ color: "inherit", textDecoration: "underline" }}
411
414
  >
412
- Please provide your feedback here.
415
+ Please provide your feedback here
413
416
  </a>
417
+ .
414
418
  </Text>
415
419
  </Box>
416
420
  </Box>
@@ -463,20 +467,20 @@ function AddTypeButton(props: AddTypeButtonProps) {
463
467
  </Button>
464
468
  );
465
469
 
466
- const disabledButton = (
467
- <Box>
468
- <Tooltip
469
- content="No type available"
470
- side="bottom"
471
- align="start"
472
- disableHoverableContent
473
- >
474
- {triggerButton}
475
- </Tooltip>
476
- </Box>
477
- );
478
-
479
- if (allCustomTypes.length === 0) return disabledButton;
470
+ if (allCustomTypes.length === 0) {
471
+ return (
472
+ <Box>
473
+ <Tooltip
474
+ content="No type available"
475
+ side="bottom"
476
+ align="start"
477
+ disableHoverableContent
478
+ >
479
+ {triggerButton}
480
+ </Tooltip>
481
+ </Box>
482
+ );
483
+ }
480
484
 
481
485
  return (
482
486
  <Box>
@@ -529,7 +533,7 @@ function TreeViewCustomType(props: TreeViewCustomTypeProps) {
529
533
  } = props;
530
534
 
531
535
  const renderedFields = getCustomTypeStaticFields(customType).map(
532
- ({ fieldId, field }) => {
536
+ ([fieldId, field]) => {
533
537
  // Group field
534
538
 
535
539
  if (field.type === "Group") {
@@ -661,93 +665,89 @@ function TreeViewContentRelationshipField(
661
665
  allCustomTypes,
662
666
  );
663
667
 
664
- if (resolvedCustomTypes.length === 0) return null;
668
+ if (resolvedCustomTypes.length !== 1) return null;
665
669
 
666
- return (
667
- <TreeViewSection
668
- title={fieldId}
669
- subtitle={getPickedFieldsLabel(
670
- countPickedFields(crFieldsCheckMap).pickedFields,
671
- )}
672
- >
673
- {resolvedCustomTypes.map((customType) => {
674
- if (typeof customType === "string") return null;
670
+ const [customType] = resolvedCustomTypes;
671
+
672
+ if (typeof customType === "string") return null;
673
+
674
+ const onNestedCustomTypeChange = (
675
+ newNestedCustomTypeFields: PickerNestedCustomTypeValue,
676
+ ) => {
677
+ onCrFieldChange({
678
+ ...crFieldsCheckMap,
679
+ [customType.id]: newNestedCustomTypeFields,
680
+ });
681
+ };
682
+
683
+ const nestedCtFieldsCheckMap = crFieldsCheckMap[customType.id] ?? {};
675
684
 
676
- const onNestedCustomTypeChange = (
677
- newNestedCustomTypeFields: PickerNestedCustomTypeValue,
685
+ const renderedFields = getCustomTypeStaticFields(customType).map(
686
+ ([fieldId, field]) => {
687
+ // Group field
688
+
689
+ if (field.type === "Group") {
690
+ const onGroupFieldsChange = (
691
+ newGroupFields: PickerLeafGroupFieldValue,
678
692
  ) => {
679
- onCrFieldChange({
680
- ...crFieldsCheckMap,
681
- [customType.id]: newNestedCustomTypeFields,
693
+ onNestedCustomTypeChange({
694
+ ...nestedCtFieldsCheckMap,
695
+ [fieldId]: { type: "group", value: newGroupFields },
682
696
  });
683
697
  };
684
698
 
685
- const nestedCtFieldsCheckMap = crFieldsCheckMap[customType.id] ?? {};
686
-
687
- const renderedFields = getCustomTypeStaticFields(customType).map(
688
- ({ fieldId, field }) => {
689
- // Group field
690
-
691
- if (field.type === "Group") {
692
- const onGroupFieldsChange = (
693
- newGroupFields: PickerLeafGroupFieldValue,
694
- ) => {
695
- onNestedCustomTypeChange({
696
- ...nestedCtFieldsCheckMap,
697
- [fieldId]: { type: "group", value: newGroupFields },
698
- });
699
- };
700
-
701
- const groupFieldCheckMap = nestedCtFieldsCheckMap[fieldId] ?? {};
702
-
703
- return (
704
- <TreeViewLeafGroupField
705
- key={fieldId}
706
- group={field}
707
- groupId={fieldId}
708
- onChange={onGroupFieldsChange}
709
- fieldCheckMap={
710
- groupFieldCheckMap.type === "group"
711
- ? groupFieldCheckMap.value
712
- : {}
713
- }
714
- />
715
- );
699
+ const groupFieldCheckMap = nestedCtFieldsCheckMap[fieldId] ?? {};
700
+
701
+ return (
702
+ <TreeViewLeafGroupField
703
+ key={fieldId}
704
+ group={field}
705
+ groupId={fieldId}
706
+ onChange={onGroupFieldsChange}
707
+ fieldCheckMap={
708
+ groupFieldCheckMap.type === "group"
709
+ ? groupFieldCheckMap.value
710
+ : {}
716
711
  }
712
+ />
713
+ );
714
+ }
717
715
 
718
- // Regular field
716
+ // Regular field
719
717
 
720
- const onCheckedChange = (newChecked: boolean) => {
721
- onNestedCustomTypeChange({
722
- ...nestedCtFieldsCheckMap,
723
- [fieldId]: { type: "checkbox", value: newChecked },
724
- });
725
- };
726
-
727
- return (
728
- <TreeViewCheckbox
729
- key={fieldId}
730
- title={fieldId}
731
- checked={nestedCtFieldsCheckMap[fieldId]?.value === true}
732
- onCheckedChange={onCheckedChange}
733
- />
734
- );
735
- },
736
- );
718
+ const onCheckedChange = (newChecked: boolean) => {
719
+ onNestedCustomTypeChange({
720
+ ...nestedCtFieldsCheckMap,
721
+ [fieldId]: { type: "checkbox", value: newChecked },
722
+ });
723
+ };
737
724
 
738
- return (
739
- <TreeViewSection
740
- key={customType.id}
741
- title={customType.id}
742
- subtitle={getPickedFieldsLabel(
743
- countPickedFields(nestedCtFieldsCheckMap).pickedFields,
744
- )}
745
- badge={getTypeFormatLabel(customType.format)}
746
- >
747
- {renderedFields.length > 0 ? renderedFields : <NoFieldsAvailable />}
748
- </TreeViewSection>
749
- );
750
- })}
725
+ return (
726
+ <TreeViewCheckbox
727
+ key={fieldId}
728
+ title={fieldId}
729
+ checked={nestedCtFieldsCheckMap[fieldId]?.value === true}
730
+ onCheckedChange={onCheckedChange}
731
+ />
732
+ );
733
+ },
734
+ );
735
+
736
+ return (
737
+ <TreeViewSection
738
+ key={customType.id}
739
+ // @ts-expect-error - TODO: Fix this when we are able to release editor packages
740
+ title={
741
+ <Text>
742
+ {fieldId} <Text color="grey11">→ {customType.id}</Text>
743
+ </Text>
744
+ }
745
+ subtitle={getPickedFieldsLabel(
746
+ countPickedFields(nestedCtFieldsCheckMap).pickedFields,
747
+ )}
748
+ badge={getTypeFormatLabel(customType.format)}
749
+ >
750
+ {renderedFields.length > 0 ? renderedFields : <NoFieldsAvailable />}
751
751
  </TreeViewSection>
752
752
  );
753
753
  }
@@ -901,7 +901,7 @@ function getTypeFormatLabel(format: CustomType["format"]) {
901
901
  }
902
902
 
903
903
  /** Retrieves all existing page & custom types. */
904
- function useCustomTypes(value: LinkCustomtypes | undefined): {
904
+ function useCustomTypes(linkCustomtypes: LinkCustomtypes | undefined): {
905
905
  /** Every existing custom type, used to discover nested custom types down the tree and the add type dropdown. */
906
906
  allCustomTypes: CustomType[];
907
907
  /** The custom types that are already picked. */
@@ -913,14 +913,14 @@ function useCustomTypes(value: LinkCustomtypes | undefined): {
913
913
  void revalidateGetCustomTypes();
914
914
  }, []);
915
915
 
916
- if (!value) {
916
+ if (!linkCustomtypes) {
917
917
  return {
918
918
  allCustomTypes,
919
919
  pickedCustomTypes: [],
920
920
  };
921
921
  }
922
922
 
923
- const pickedCustomTypes = value.flatMap(
923
+ const pickedCustomTypes = linkCustomtypes.flatMap(
924
924
  (pickedCt) => allCustomTypes.find((ct) => ct.id === getId(pickedCt)) ?? [],
925
925
  );
926
926
 
@@ -932,10 +932,10 @@ function useCustomTypes(value: LinkCustomtypes | undefined): {
932
932
 
933
933
  function resolveContentRelationshipCustomTypes(
934
934
  linkCustomtypes: LinkCustomtypes,
935
- localCustomTypes: CustomType[],
935
+ allCustomTypes: CustomType[],
936
936
  ): CustomType[] {
937
937
  return linkCustomtypes.flatMap((linkCustomtype) => {
938
- return localCustomTypes.find((ct) => ct.id === getId(linkCustomtype)) ?? [];
938
+ return allCustomTypes.find((ct) => ct.id === getId(linkCustomtype)) ?? [];
939
939
  });
940
940
  }
941
941
 
@@ -943,92 +943,281 @@ function resolveContentRelationshipCustomTypes(
943
943
  * Converts a Link config `customtypes` ({@link LinkCustomtypes}) structure into
944
944
  * picker fields check map ({@link PickerCustomTypes}).
945
945
  */
946
- export function convertLinkCustomtypesToFieldCheckMap(
947
- customTypes: LinkCustomtypes,
948
- ): PickerCustomTypes {
949
- return customTypes.reduce<PickerCustomTypes>((customTypes, customType) => {
950
- if (typeof customType === "string") return customTypes;
951
-
952
- customTypes[customType.id] = customType.fields.reduce<PickerCustomType>(
953
- (customTypeFields, field) => {
954
- if (typeof field === "string") {
946
+ export function convertLinkCustomtypesToFieldCheckMap(args: {
947
+ linkCustomtypes: LinkCustomtypes;
948
+ allCustomTypes?: CustomType[];
949
+ }): PickerCustomTypes {
950
+ const { linkCustomtypes, allCustomTypes } = args;
951
+
952
+ // If allCustomTypes is undefined, avoid checking if the fields exist.
953
+ const shouldValidate = allCustomTypes !== undefined;
954
+
955
+ const checkMap = linkCustomtypes.reduce<PickerCustomTypes>(
956
+ (customTypes, customType) => {
957
+ if (typeof customType === "string") return customTypes;
958
+
959
+ let ctFlatFieldMap: Record<string, NestableWidget | Group> = {};
960
+
961
+ if (shouldValidate) {
962
+ const existingCt = allCustomTypes.find((c) => c.id === customType.id);
963
+ // Exit early if the custom type doesn't exist
964
+ if (!existingCt) return customTypes;
965
+
966
+ ctFlatFieldMap = getCustomTypeStaticFieldsMap(existingCt);
967
+ }
968
+
969
+ const customTypeFields = customType.fields.reduce<PickerCustomType>(
970
+ (fields, field) => {
971
+ // Check if the field exists (only if validating)
972
+ const existingField = ctFlatFieldMap[getId(field)];
973
+ if (shouldValidate && existingField === undefined) return fields;
974
+
955
975
  // Regular field
956
- customTypeFields[field] = { type: "checkbox", value: true };
957
- } else if ("fields" in field && field.fields !== undefined) {
976
+ if (typeof field === "string") {
977
+ // Check if the field matched the existing one in the custom type (only if validating)
978
+ if (
979
+ shouldValidate &&
980
+ existingField !== undefined &&
981
+ existingField.type === "Group"
982
+ ) {
983
+ return fields;
984
+ }
985
+
986
+ fields[field] = { type: "checkbox", value: true };
987
+ return fields;
988
+ }
989
+
958
990
  // Group field
959
- customTypeFields[field.id] = createGroupFieldCheckMap(field);
960
- } else if ("customtypes" in field && field.customtypes !== undefined) {
991
+ if ("fields" in field && field.fields !== undefined) {
992
+ // Check if the field matched the existing one in the custom type (only if validating)
993
+ if (
994
+ shouldValidate &&
995
+ existingField !== undefined &&
996
+ existingField.type !== "Group"
997
+ ) {
998
+ return fields;
999
+ }
1000
+
1001
+ const groupFieldCheckMap = createGroupFieldCheckMap({
1002
+ group: field,
1003
+ allCustomTypes,
1004
+ ctFlatFieldMap,
1005
+ });
1006
+
1007
+ if (groupFieldCheckMap) {
1008
+ fields[field.id] = groupFieldCheckMap;
1009
+ }
1010
+
1011
+ return fields;
1012
+ }
1013
+
961
1014
  // Content relationship field
962
- customTypeFields[field.id] =
963
- createContentRelationshipFieldCheckMap(field);
964
- }
1015
+ if ("customtypes" in field && field.customtypes !== undefined) {
1016
+ // Check if the field matched the existing one in the custom type (only if validating)
1017
+ if (
1018
+ shouldValidate &&
1019
+ existingField !== undefined &&
1020
+ !isContentRelationshipField(existingField)
1021
+ ) {
1022
+ return fields;
1023
+ }
965
1024
 
966
- return customTypeFields;
967
- },
968
- {},
969
- );
970
- return customTypes;
971
- }, {});
1025
+ const crFieldCheckMap = createContentRelationshipFieldCheckMap({
1026
+ field,
1027
+ allCustomTypes,
1028
+ });
1029
+
1030
+ if (crFieldCheckMap) {
1031
+ fields[field.id] = crFieldCheckMap;
1032
+ }
1033
+
1034
+ return fields;
1035
+ }
1036
+
1037
+ return fields;
1038
+ },
1039
+ {},
1040
+ );
1041
+
1042
+ if (Object.keys(customTypeFields).length > 0) {
1043
+ customTypes[customType.id] = customTypeFields;
1044
+ }
1045
+
1046
+ return customTypes;
1047
+ },
1048
+ {},
1049
+ );
1050
+
1051
+ return checkMap;
972
1052
  }
973
1053
 
974
- function createGroupFieldCheckMap(
975
- group: LinkCustomtypesGroupFieldValue,
976
- ): PickerFirstLevelGroupField {
977
- return {
978
- type: "group",
979
- value: group.fields.reduce<PickerFirstLevelGroupFieldValue>(
980
- (fields, field) => {
981
- if (typeof field === "string") {
982
- // Regular field
983
- fields[field] = { type: "checkbox", value: true };
984
- } else if ("customtypes" in field && field.customtypes !== undefined) {
985
- // Content relationship field
986
- fields[field.id] = createContentRelationshipFieldCheckMap(field);
1054
+ function createGroupFieldCheckMap(args: {
1055
+ group: LinkCustomtypesGroupFieldValue;
1056
+ allCustomTypes?: CustomType[];
1057
+ ctFlatFieldMap: Record<string, NestableWidget | Group>;
1058
+ }): PickerFirstLevelGroupField | undefined {
1059
+ const { group, ctFlatFieldMap, allCustomTypes } = args;
1060
+
1061
+ // If allCustomTypes is undefined, avoid checking if the fields exist.
1062
+ const shouldValidate = allCustomTypes !== undefined;
1063
+
1064
+ const fieldEntries = group.fields.reduce<PickerFirstLevelGroupFieldValue>(
1065
+ (fields, field) => {
1066
+ // Check if the field exists (only if validating)
1067
+ const existingField = getGroupFieldFromMap(
1068
+ ctFlatFieldMap,
1069
+ group.id,
1070
+ getId(field),
1071
+ );
1072
+ if (shouldValidate && !existingField) return fields;
1073
+
1074
+ // Regular field
1075
+ if (typeof field === "string") {
1076
+ // Check if the field matched the existing one in the custom type (only if validating)
1077
+ if (
1078
+ shouldValidate &&
1079
+ existingField !== undefined &&
1080
+ existingField.type === "Group"
1081
+ ) {
1082
+ return fields;
987
1083
  }
988
1084
 
1085
+ fields[field] = { type: "checkbox", value: true };
989
1086
  return fields;
990
- },
991
- {},
992
- ),
1087
+ }
1088
+
1089
+ // Content relationship field
1090
+ if ("customtypes" in field && field.customtypes !== undefined) {
1091
+ // Check if the field matched the existing one in the custom type (only if validating)
1092
+ if (
1093
+ shouldValidate &&
1094
+ existingField !== undefined &&
1095
+ !isContentRelationshipField(existingField)
1096
+ ) {
1097
+ return fields;
1098
+ }
1099
+
1100
+ const crFieldCheckMap = createContentRelationshipFieldCheckMap({
1101
+ field,
1102
+ allCustomTypes,
1103
+ });
1104
+
1105
+ if (crFieldCheckMap) {
1106
+ fields[field.id] = crFieldCheckMap;
1107
+ }
1108
+
1109
+ return fields;
1110
+ }
1111
+
1112
+ return fields;
1113
+ },
1114
+ {},
1115
+ );
1116
+
1117
+ if (Object.keys(fieldEntries).length === 0) return undefined;
1118
+
1119
+ return {
1120
+ type: "group",
1121
+ value: fieldEntries,
993
1122
  };
994
1123
  }
995
1124
 
996
- function createContentRelationshipFieldCheckMap(
997
- field: LinkCustomtypesContentRelationshipFieldValue,
998
- ): PickerContentRelationshipField {
999
- const crField: PickerContentRelationshipField = {
1000
- type: "contentRelationship",
1001
- value: {},
1002
- };
1003
- const crFieldCustomTypes = crField.value;
1125
+ function createContentRelationshipFieldCheckMap(args: {
1126
+ field: LinkCustomtypesContentRelationshipFieldValue;
1127
+ allCustomTypes?: CustomType[];
1128
+ }): PickerContentRelationshipField | undefined {
1129
+ const { field, allCustomTypes } = args;
1004
1130
 
1005
- for (const customType of field.customtypes) {
1006
- if (typeof customType === "string") continue;
1131
+ // If allCustomTypes is undefined, avoid checking if the fields exists.
1132
+ const shouldValidate = allCustomTypes !== undefined;
1007
1133
 
1008
- crFieldCustomTypes[customType.id] ??= {};
1009
- const customTypeFields = crFieldCustomTypes[customType.id];
1134
+ const fieldEntries =
1135
+ field.customtypes.reduce<PickerContentRelationshipFieldValue>(
1136
+ (customTypes, customType) => {
1137
+ if (typeof customType === "string") return customTypes;
1010
1138
 
1011
- for (const nestedField of customType.fields) {
1012
- if (typeof nestedField === "string") {
1013
- // Regular field
1014
- customTypeFields[nestedField] = { type: "checkbox", value: true };
1015
- } else {
1016
- // Group field
1017
- const groupFieldsEntries = nestedField.fields.map(
1018
- (field) => [field, { type: "checkbox", value: true }] as const,
1139
+ let ctFlatFieldMap: Record<string, NestableWidget | Group> = {};
1140
+
1141
+ if (shouldValidate) {
1142
+ const existingCt = allCustomTypes.find((c) => c.id === customType.id);
1143
+ // Exit early if the custom type doesn't exist
1144
+ if (!existingCt) return customTypes;
1145
+
1146
+ ctFlatFieldMap = getCustomTypeStaticFieldsMap(existingCt);
1147
+ }
1148
+
1149
+ const ctFields = customType.fields.reduce<PickerNestedCustomTypeValue>(
1150
+ (nestedFields, nestedField) => {
1151
+ // Regular field
1152
+ if (typeof nestedField === "string") {
1153
+ const existingField = ctFlatFieldMap[nestedField];
1154
+
1155
+ // Check if the field matched the existing one in the custom type (only if validating)
1156
+ if (
1157
+ shouldValidate &&
1158
+ (existingField === undefined || existingField.type === "Group")
1159
+ ) {
1160
+ return nestedFields;
1161
+ }
1162
+
1163
+ nestedFields[nestedField] = { type: "checkbox", value: true };
1164
+ return nestedFields;
1165
+ }
1166
+
1167
+ if ("fields" in nestedField && nestedField.fields !== undefined) {
1168
+ // Group field
1169
+ const groupFields =
1170
+ nestedField.fields.reduce<PickerLeafGroupFieldValue>(
1171
+ (groupFields, groupField) => {
1172
+ const existingField = getGroupFieldFromMap(
1173
+ ctFlatFieldMap,
1174
+ nestedField.id,
1175
+ groupField,
1176
+ );
1177
+
1178
+ // Check if the field matched the existing one in the custom type (only if validating)
1179
+ if (
1180
+ shouldValidate &&
1181
+ (existingField === undefined ||
1182
+ existingField.type === "Group")
1183
+ ) {
1184
+ return groupFields;
1185
+ }
1186
+
1187
+ groupFields[groupField] = { type: "checkbox", value: true };
1188
+ return groupFields;
1189
+ },
1190
+ {},
1191
+ );
1192
+
1193
+ if (Object.keys(groupFields).length > 0) {
1194
+ nestedFields[nestedField.id] = {
1195
+ type: "group",
1196
+ value: groupFields,
1197
+ };
1198
+ }
1199
+ }
1200
+
1201
+ return nestedFields;
1202
+ },
1203
+ {},
1019
1204
  );
1020
1205
 
1021
- if (groupFieldsEntries.length > 0) {
1022
- customTypeFields[nestedField.id] = {
1023
- type: "group",
1024
- value: Object.fromEntries(groupFieldsEntries),
1025
- };
1206
+ if (Object.keys(ctFields).length > 0) {
1207
+ customTypes[customType.id] = ctFields;
1026
1208
  }
1027
- }
1028
- }
1029
- }
1030
1209
 
1031
- return crField;
1210
+ return customTypes;
1211
+ },
1212
+ {},
1213
+ );
1214
+
1215
+ if (Object.keys(fieldEntries).length === 0) return undefined;
1216
+
1217
+ return {
1218
+ type: "contentRelationship",
1219
+ value: fieldEntries,
1220
+ };
1032
1221
  }
1033
1222
 
1034
1223
  /**
@@ -1037,27 +1226,22 @@ function createContentRelationshipFieldCheckMap(
1037
1226
  * made correctly and that the order is preserved.
1038
1227
  */
1039
1228
  function mergeAndConvertCheckMapToLinkCustomtypes(args: {
1040
- existingLinkCustomtypes: LinkCustomtypes | undefined;
1041
- previousPickerCustomtypes: PickerCustomTypes;
1229
+ linkCustomtypes: LinkCustomtypes | undefined;
1230
+ fieldCheckMap: PickerCustomTypes;
1042
1231
  newCustomType: PickerCustomType;
1043
1232
  customTypeId: string;
1044
1233
  }): LinkCustomtypes {
1045
- const {
1046
- existingLinkCustomtypes,
1047
- previousPickerCustomtypes,
1048
- newCustomType,
1049
- customTypeId,
1050
- } = args;
1234
+ const { linkCustomtypes, fieldCheckMap, newCustomType, customTypeId } = args;
1051
1235
 
1052
1236
  const result: NonReadonly<LinkCustomtypes> = [];
1053
1237
  const pickerLinkCustomtypes = convertFieldCheckMapToLinkCustomtypes({
1054
- ...previousPickerCustomtypes,
1238
+ ...fieldCheckMap,
1055
1239
  [customTypeId]: newCustomType,
1056
1240
  });
1057
1241
 
1058
- if (!existingLinkCustomtypes) return pickerLinkCustomtypes;
1242
+ if (!linkCustomtypes) return pickerLinkCustomtypes;
1059
1243
 
1060
- for (const existingLinkCt of existingLinkCustomtypes) {
1244
+ for (const existingLinkCt of linkCustomtypes) {
1061
1245
  const existingPickerLinkCt = pickerLinkCustomtypes.find((ct) => {
1062
1246
  return getId(ct) === getId(existingLinkCt);
1063
1247
  });
@@ -1229,6 +1413,10 @@ export function countPickedFields(
1229
1413
  );
1230
1414
  }
1231
1415
 
1416
+ function isContentRelationshipField(field: DynamicWidget): field is Link {
1417
+ return field.type === "Link" && field.config?.select === "document";
1418
+ }
1419
+
1232
1420
  /**
1233
1421
  * Check if the field is a Content Relationship Link with a **single** custom
1234
1422
  * type. CRs with multiple custom types are not currently supported (legacy).
@@ -1237,34 +1425,65 @@ function isContentRelationshipFieldWithSingleCustomtype(
1237
1425
  field: NestableWidget | Group,
1238
1426
  ): field is Link {
1239
1427
  return !!(
1240
- field.type === "Link" &&
1241
- field.config?.select === "document" &&
1428
+ isContentRelationshipField(field) &&
1242
1429
  field.config?.customtypes &&
1243
1430
  field.config.customtypes.length === 1
1244
1431
  );
1245
1432
  }
1246
1433
 
1247
- function getCustomTypeStaticFields(customType: CustomType) {
1434
+ /**
1435
+ * Flattens all custom type tabs and fields into an array of [fieldId, field] tuples.
1436
+ * Also filters out invalid fields.
1437
+ */
1438
+ function getCustomTypeStaticFields(
1439
+ customType: CustomType,
1440
+ ): [fieldId: string, field: NestableWidget | Group][] {
1248
1441
  return Object.values(customType.json).flatMap((tabFields) => {
1249
- return Object.entries(tabFields).flatMap(([fieldId, field]) => {
1250
- if (
1251
- field.type !== "Slices" &&
1252
- field.type !== "Choice" &&
1253
- // Filter out uid fields because it's a special field returned by the
1254
- // API and is not part of the data object in the document.
1255
- // We also filter by key "uid", because (as of the time of writing
1256
- // this), creating any field with that API id will result in it being
1257
- // used for metadata.
1258
- (field.type !== "UID" || fieldId !== "uid")
1259
- ) {
1260
- return { fieldId, field: field as NestableWidget | Group };
1261
- }
1262
-
1263
- return [];
1264
- });
1442
+ return Object.entries(tabFields).flatMap<[string, NestableWidget | Group]>(
1443
+ ([fieldId, field]) => {
1444
+ return isValidField(fieldId, field) ? [[fieldId, field]] : [];
1445
+ },
1446
+ );
1265
1447
  });
1266
1448
  }
1267
1449
 
1450
+ /**
1451
+ * Flattens all custom type tabs and fields into a map of field ids to fields.
1452
+ * Also filters out invalid fields.
1453
+ */
1454
+ function getCustomTypeStaticFieldsMap(
1455
+ customType: CustomType,
1456
+ ): Record<string, NestableWidget | Group> {
1457
+ return Object.fromEntries(getCustomTypeStaticFields(customType));
1458
+ }
1459
+
1460
+ function getGroupFieldFromMap(
1461
+ flattenFields: Record<string, NestableWidget | Group>,
1462
+ groupId: string,
1463
+ fieldId: string,
1464
+ ) {
1465
+ const group = flattenFields[groupId];
1466
+ if (group === undefined || group.type !== "Group") return undefined;
1467
+ return group.config?.fields?.[fieldId];
1468
+ }
1469
+
1470
+ function isValidField(
1471
+ fieldId: string,
1472
+ field: DynamicWidget,
1473
+ ): field is NestableWidget | Group {
1474
+ return (
1475
+ field.type !== "Slices" &&
1476
+ field.type !== "Choice" &&
1477
+ // We don't display uid fields because they're a special field returned by
1478
+ // the API and they're not included in the document data object.
1479
+ // We also filter by key "uid", because (as of the time of writing this)
1480
+ // creating any field with that API id will result in it being used for
1481
+ // metadata, regardless of its type.
1482
+ field.type !== "UID" &&
1483
+ fieldId !== "uid"
1484
+ );
1485
+ }
1486
+
1268
1487
  function getGroupFields(group: Group) {
1269
1488
  if (!group.config?.fields) return [];
1270
1489
  return Object.entries(group.config.fields).map(([fieldId, field]) => {