slice-machine-ui 2.16.2-alpha.jp-cr-ui-fix-invalid-fields-checked.10 → 2.16.2-alpha.lg-cr-types-update.2

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.
Files changed (28) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/630-a9927675acae2970.js +1 -0
  3. package/out/_next/static/chunks/{882-151468121d542ed6.js → 882-53d88aa0b7e3ffeb.js} +1 -1
  4. package/out/_next/static/chunks/pages/{_app-856e0f3f647b7ee9.js → _app-a47439153d488526.js} +1 -1
  5. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/{[variation]-bd5e45632c419567.js → [variation]-94c9cad9da2c3089.js} +1 -1
  6. package/out/_next/static/{MkhVqkJrIrDEIBAwuuZS4 → uKHyiqCfn0UKI3AoChZs-}/_buildManifest.js +1 -1
  7. package/out/changelog.html +1 -1
  8. package/out/changes.html +1 -1
  9. package/out/custom-types/[customTypeId].html +1 -1
  10. package/out/custom-types.html +1 -1
  11. package/out/index.html +1 -1
  12. package/out/labs.html +1 -1
  13. package/out/page-types/[pageTypeId].html +1 -1
  14. package/out/settings.html +1 -1
  15. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  16. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  17. package/out/slices.html +1 -1
  18. package/package.json +3 -3
  19. package/src/features/builder/fields/{contentRelationship/ContentRelationshipFieldPicker.tsx → ContentRelationshipFieldPicker.tsx} +230 -499
  20. package/src/legacy/lib/builders/common/Zone/Card/components/Hints/index.tsx +1 -12
  21. package/src/legacy/lib/models/common/widgets/ContentRelationship/Form.tsx +1 -1
  22. package/src/utils/tracking/trackFieldAdded.ts +5 -2
  23. package/src/utils/tracking/trackFieldUpdated.ts +5 -2
  24. package/out/_next/static/chunks/630-e53690702c17b243.js +0 -1
  25. package/src/features/builder/fields/contentRelationship/Hint.tsx +0 -28
  26. package/src/features/builder/fields/contentRelationship/__tests__/ContentRelationshipFieldPicker.test.ts +0 -1323
  27. package/src/utils/tracking/getLinkTrackingProperties.ts +0 -28
  28. /package/out/_next/static/{MkhVqkJrIrDEIBAwuuZS4 → uKHyiqCfn0UKI3AoChZs-}/_ssgManifest.js +0 -0
@@ -22,7 +22,6 @@ import {
22
22
  } from "@prismicio/editor-ui";
23
23
  import {
24
24
  CustomType,
25
- DynamicWidget,
26
25
  Group,
27
26
  Link,
28
27
  LinkConfig,
@@ -261,11 +260,11 @@ export function ContentRelationshipFieldPicker(
261
260
  function ContentRelationshipFieldPickerContent(
262
261
  props: ContentRelationshipFieldPickerProps,
263
262
  ) {
264
- const { value: linkCustomtypes, onChange } = props;
265
- const { allCustomTypes, pickedCustomTypes } = useCustomTypes(linkCustomtypes);
263
+ const { value, onChange } = props;
264
+ const { allCustomTypes, pickedCustomTypes } = useCustomTypes(value);
266
265
 
267
- const fieldCheckMap = linkCustomtypes
268
- ? convertLinkCustomtypesToFieldCheckMap({ linkCustomtypes, allCustomTypes })
266
+ const fieldCheckMap = value
267
+ ? convertLinkCustomtypesToFieldCheckMap(value)
269
268
  : {};
270
269
 
271
270
  function onCustomTypesChange(id: string, newCustomType: PickerCustomType) {
@@ -275,24 +274,22 @@ function ContentRelationshipFieldPickerContent(
275
274
  // represent new types added without any picked fields.
276
275
  onChange(
277
276
  mergeAndConvertCheckMapToLinkCustomtypes({
278
- fieldCheckMap,
279
- newCustomType,
280
- linkCustomtypes,
277
+ existingLinkCustomtypes: value,
278
+ previousPickerCustomtypes: fieldCheckMap,
281
279
  customTypeId: id,
280
+ newCustomType,
282
281
  }),
283
282
  );
284
283
  }
285
284
 
286
285
  function addCustomType(id: string) {
287
- const newFields = linkCustomtypes ? [...linkCustomtypes, id] : [id];
286
+ const newFields = value ? [...value, id] : [id];
288
287
  onChange(newFields);
289
288
  }
290
289
 
291
290
  function removeCustomType(id: string) {
292
- if (linkCustomtypes) {
293
- onChange(
294
- linkCustomtypes.filter((existingCt) => getId(existingCt) !== id),
295
- );
291
+ if (value) {
292
+ onChange(value.filter((existingCt) => getId(existingCt) !== id));
296
293
  }
297
294
  }
298
295
 
@@ -407,14 +404,14 @@ function ContentRelationshipFieldPickerContent(
407
404
  <Text variant="normal" color="grey11">
408
405
  Have ideas for improving this field?{" "}
409
406
  <a
410
- href="https://community.prismic.io/t/content-relationship-share-your-requests-and-feedback/19843"
407
+ // TODO: Add real URL: https://linear.app/prismic/issue/DT-2693
408
+ href="https://community.prismic.io/t/TODO"
411
409
  target="_blank"
412
410
  rel="noopener noreferrer"
413
411
  style={{ color: "inherit", textDecoration: "underline" }}
414
412
  >
415
- Please provide your feedback here
413
+ Please provide your feedback here.
416
414
  </a>
417
- .
418
415
  </Text>
419
416
  </Box>
420
417
  </Box>
@@ -467,20 +464,20 @@ function AddTypeButton(props: AddTypeButtonProps) {
467
464
  </Button>
468
465
  );
469
466
 
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
- }
467
+ const disabledButton = (
468
+ <Box>
469
+ <Tooltip
470
+ content="No type available"
471
+ side="bottom"
472
+ align="start"
473
+ disableHoverableContent
474
+ >
475
+ {triggerButton}
476
+ </Tooltip>
477
+ </Box>
478
+ );
479
+
480
+ if (allCustomTypes.length === 0) return disabledButton;
484
481
 
485
482
  return (
486
483
  <Box>
@@ -533,10 +530,10 @@ function TreeViewCustomType(props: TreeViewCustomTypeProps) {
533
530
  } = props;
534
531
 
535
532
  const renderedFields = getCustomTypeStaticFields(customType).map(
536
- ([fieldId, field]) => {
533
+ ({ fieldId, field }) => {
537
534
  // Group field
538
535
 
539
- if (field.type === "Group") {
536
+ if (isGroupField(field)) {
540
537
  const onGroupFieldChange = (
541
538
  newGroupFields: PickerFirstLevelGroupFieldValue,
542
539
  ) => {
@@ -564,9 +561,9 @@ function TreeViewCustomType(props: TreeViewCustomTypeProps) {
564
561
  );
565
562
  }
566
563
 
567
- // Content relationship field with custom types
564
+ // Content relationship field
568
565
 
569
- if (isContentRelationshipFieldWithSingleCustomtype(field)) {
566
+ if (isContentRelationshipField(field)) {
570
567
  const onContentRelationshipFieldChange = (
571
568
  newCrFields: PickerContentRelationshipFieldValue,
572
569
  ) => {
@@ -618,23 +615,23 @@ function TreeViewCustomType(props: TreeViewCustomTypeProps) {
618
615
  );
619
616
 
620
617
  const exposedFieldsCount = countPickedFields(customTypeFieldsCheckMap);
621
-
622
618
  return (
623
619
  <TreeViewSection
624
620
  key={customType.id}
625
621
  title={customType.id}
626
622
  subtitle={
627
- exposedFieldsCount.pickedFields > 0
628
- ? getPickedFieldsLabel(
629
- exposedFieldsCount.pickedFields,
630
- "returned in the API",
631
- )
623
+ exposedFieldsCount > 0
624
+ ? getExposedFieldsLabel(exposedFieldsCount)
632
625
  : "(No fields returned in the API)"
633
626
  }
634
627
  badge={getTypeFormatLabel(customType.format)}
635
628
  defaultOpen
636
629
  >
637
- {renderedFields.length > 0 ? renderedFields : <NoFieldsAvailable />}
630
+ {renderedFields.length > 0 ? (
631
+ renderedFields
632
+ ) : (
633
+ <Text color="grey11">No available fields to select</Text>
634
+ )}
638
635
  </TreeViewSection>
639
636
  );
640
637
  }
@@ -670,9 +667,7 @@ function TreeViewContentRelationshipField(
670
667
  return (
671
668
  <TreeViewSection
672
669
  title={fieldId}
673
- subtitle={getPickedFieldsLabel(
674
- countPickedFields(crFieldsCheckMap).pickedFields,
675
- )}
670
+ subtitle={getExposedFieldsLabel(countPickedFields(crFieldsCheckMap))}
676
671
  >
677
672
  {resolvedCustomTypes.map((customType) => {
678
673
  if (typeof customType === "string") return null;
@@ -689,10 +684,10 @@ function TreeViewContentRelationshipField(
689
684
  const nestedCtFieldsCheckMap = crFieldsCheckMap[customType.id] ?? {};
690
685
 
691
686
  const renderedFields = getCustomTypeStaticFields(customType).map(
692
- ([fieldId, field]) => {
687
+ ({ fieldId, field }) => {
693
688
  // Group field
694
689
 
695
- if (field.type === "Group") {
690
+ if (isGroupField(field)) {
696
691
  const onGroupFieldsChange = (
697
692
  newGroupFields: PickerLeafGroupFieldValue,
698
693
  ) => {
@@ -739,16 +734,18 @@ function TreeViewContentRelationshipField(
739
734
  },
740
735
  );
741
736
 
737
+ if (renderedFields.length === 0) return null;
738
+
742
739
  return (
743
740
  <TreeViewSection
744
741
  key={customType.id}
745
742
  title={customType.id}
746
- subtitle={getPickedFieldsLabel(
747
- countPickedFields(nestedCtFieldsCheckMap).pickedFields,
743
+ subtitle={getExposedFieldsLabel(
744
+ countPickedFields(nestedCtFieldsCheckMap),
748
745
  )}
749
746
  badge={getTypeFormatLabel(customType.format)}
750
747
  >
751
- {renderedFields.length > 0 ? renderedFields : <NoFieldsAvailable />}
748
+ {renderedFields}
752
749
  </TreeViewSection>
753
750
  );
754
751
  })}
@@ -756,10 +753,6 @@ function TreeViewContentRelationshipField(
756
753
  );
757
754
  }
758
755
 
759
- function NoFieldsAvailable() {
760
- return <Text color="grey11">No available fields to select</Text>;
761
- }
762
-
763
756
  interface TreeViewLeafGroupFieldProps {
764
757
  group: Group;
765
758
  groupId: string;
@@ -795,16 +788,16 @@ function TreeViewLeafGroupField(props: TreeViewLeafGroupFieldProps) {
795
788
  );
796
789
  });
797
790
 
791
+ if (renderedFields.length === 0) return null;
792
+
798
793
  return (
799
794
  <TreeViewSection
800
795
  key={groupId}
801
796
  title={groupId}
802
- subtitle={getPickedFieldsLabel(
803
- countPickedFields(groupFieldsCheckMap).pickedFields,
804
- )}
797
+ subtitle={getExposedFieldsLabel(countPickedFields(groupFieldsCheckMap))}
805
798
  badge="Group"
806
799
  >
807
- {renderedFields.length > 0 ? renderedFields : <NoFieldsAvailable />}
800
+ {renderedFields}
808
801
  </TreeViewSection>
809
802
  );
810
803
  }
@@ -828,76 +821,74 @@ function TreeViewFirstLevelGroupField(
828
821
  allCustomTypes,
829
822
  } = props;
830
823
 
831
- const renderedFields = getGroupFields(group).map(({ fieldId, field }) => {
832
- // Content relationship field with custom types
833
-
834
- if (isContentRelationshipFieldWithSingleCustomtype(field)) {
835
- const onContentRelationshipFieldChange = (
836
- newCrFields: PickerContentRelationshipFieldValue,
837
- ) => {
838
- onGroupFieldChange({
839
- ...groupFieldsCheckMap,
840
- [fieldId]: {
841
- type: "contentRelationship",
842
- value: newCrFields,
843
- },
844
- });
845
- };
846
-
847
- const crFieldCheckMap = groupFieldsCheckMap[fieldId] ?? {};
848
-
849
- return (
850
- <TreeViewContentRelationshipField
851
- key={fieldId}
852
- field={field}
853
- fieldId={fieldId}
854
- fieldCheckMap={
855
- crFieldCheckMap.type === "contentRelationship"
856
- ? crFieldCheckMap.value
857
- : {}
858
- }
859
- onChange={onContentRelationshipFieldChange}
860
- allCustomTypes={allCustomTypes}
861
- />
862
- );
863
- }
864
-
865
- // Regular field
866
-
867
- const onCheckedChange = (newChecked: boolean) => {
868
- onGroupFieldChange({
869
- ...groupFieldsCheckMap,
870
- [fieldId]: { type: "checkbox", value: newChecked },
871
- });
872
- };
873
-
874
- return (
875
- <TreeViewCheckbox
876
- key={fieldId}
877
- title={fieldId}
878
- checked={groupFieldsCheckMap[fieldId]?.value === true}
879
- onCheckedChange={onCheckedChange}
880
- />
881
- );
882
- });
824
+ if (!group.config?.fields) return null;
883
825
 
884
826
  return (
885
827
  <TreeViewSection
886
828
  key={groupId}
887
829
  title={groupId}
888
- subtitle={getPickedFieldsLabel(
889
- countPickedFields(groupFieldsCheckMap).pickedFields,
890
- )}
830
+ subtitle={getExposedFieldsLabel(countPickedFields(groupFieldsCheckMap))}
891
831
  badge="Group"
892
832
  >
893
- {renderedFields.length > 0 ? renderedFields : <NoFieldsAvailable />}
833
+ {getGroupFields(group).map(({ fieldId, field }) => {
834
+ if (isContentRelationshipField(field)) {
835
+ const onContentRelationshipFieldChange = (
836
+ newCrFields: PickerContentRelationshipFieldValue,
837
+ ) => {
838
+ onGroupFieldChange({
839
+ ...groupFieldsCheckMap,
840
+ [fieldId]: {
841
+ type: "contentRelationship",
842
+ value: newCrFields,
843
+ },
844
+ });
845
+ };
846
+
847
+ const crFieldCheckMap = groupFieldsCheckMap[fieldId] ?? {};
848
+
849
+ return (
850
+ <TreeViewContentRelationshipField
851
+ key={fieldId}
852
+ field={field}
853
+ fieldId={fieldId}
854
+ fieldCheckMap={
855
+ crFieldCheckMap.type === "contentRelationship"
856
+ ? crFieldCheckMap.value
857
+ : {}
858
+ }
859
+ onChange={onContentRelationshipFieldChange}
860
+ allCustomTypes={allCustomTypes}
861
+ />
862
+ );
863
+ }
864
+
865
+ const onCheckedChange = (newChecked: boolean) => {
866
+ onGroupFieldChange({
867
+ ...groupFieldsCheckMap,
868
+ [fieldId]: { type: "checkbox", value: newChecked },
869
+ });
870
+ };
871
+
872
+ return (
873
+ <TreeViewCheckbox
874
+ key={fieldId}
875
+ title={fieldId}
876
+ checked={groupFieldsCheckMap[fieldId]?.value === true}
877
+ onCheckedChange={onCheckedChange}
878
+ />
879
+ );
880
+ })}
894
881
  </TreeViewSection>
895
882
  );
896
883
  }
897
884
 
898
- function getPickedFieldsLabel(count: number, suffix = "selected") {
885
+ function getExposedFieldsLabel(count: number) {
899
886
  if (count === 0) return undefined;
900
- return `(${count} ${pluralize(count, "field", "fields")} ${suffix})`;
887
+ return `(${count} ${pluralize(
888
+ count,
889
+ "field",
890
+ "fields",
891
+ )} returned in the API)`;
901
892
  }
902
893
 
903
894
  function getTypeFormatLabel(format: CustomType["format"]) {
@@ -905,7 +896,7 @@ function getTypeFormatLabel(format: CustomType["format"]) {
905
896
  }
906
897
 
907
898
  /** Retrieves all existing page & custom types. */
908
- function useCustomTypes(linkCustomtypes: LinkCustomtypes | undefined): {
899
+ function useCustomTypes(value: LinkCustomtypes | undefined): {
909
900
  /** Every existing custom type, used to discover nested custom types down the tree and the add type dropdown. */
910
901
  allCustomTypes: CustomType[];
911
902
  /** The custom types that are already picked. */
@@ -917,14 +908,14 @@ function useCustomTypes(linkCustomtypes: LinkCustomtypes | undefined): {
917
908
  void revalidateGetCustomTypes();
918
909
  }, []);
919
910
 
920
- if (!linkCustomtypes) {
911
+ if (!value) {
921
912
  return {
922
913
  allCustomTypes,
923
914
  pickedCustomTypes: [],
924
915
  };
925
916
  }
926
917
 
927
- const pickedCustomTypes = linkCustomtypes.flatMap(
918
+ const pickedCustomTypes = value.flatMap(
928
919
  (pickedCt) => allCustomTypes.find((ct) => ct.id === getId(pickedCt)) ?? [],
929
920
  );
930
921
 
@@ -935,293 +926,107 @@ function useCustomTypes(linkCustomtypes: LinkCustomtypes | undefined): {
935
926
  }
936
927
 
937
928
  function resolveContentRelationshipCustomTypes(
938
- linkCustomtypes: LinkCustomtypes,
939
- allCustomTypes: CustomType[],
929
+ customTypes: LinkCustomtypes,
930
+ localCustomTypes: CustomType[],
940
931
  ): CustomType[] {
941
- return linkCustomtypes.flatMap((linkCustomtype) => {
942
- return allCustomTypes.find((ct) => ct.id === getId(linkCustomtype)) ?? [];
932
+ const fields = customTypes.flatMap<CustomType>((customType) => {
933
+ if (typeof customType === "string") return [];
934
+ return localCustomTypes.find((ct) => ct.id === customType.id) ?? [];
943
935
  });
936
+
937
+ return fields;
944
938
  }
945
939
 
946
940
  /**
947
941
  * Converts a Link config `customtypes` ({@link LinkCustomtypes}) structure into
948
942
  * picker fields check map ({@link PickerCustomTypes}).
949
943
  */
950
- export function convertLinkCustomtypesToFieldCheckMap(args: {
951
- linkCustomtypes: LinkCustomtypes;
952
- allCustomTypes?: CustomType[];
953
- }): PickerCustomTypes {
954
- const { linkCustomtypes, allCustomTypes } = args;
955
-
956
- // If allCustomTypes is undefined, avoid checking if the fields exist.
957
- const shouldValidate = allCustomTypes !== undefined;
958
-
959
- const checkMap = linkCustomtypes.reduce<PickerCustomTypes>(
960
- (customTypes, customType) => {
961
- if (typeof customType === "string") return customTypes;
962
-
963
- let ctFlatFieldMap: Record<string, NestableWidget | Group> = {};
964
-
965
- if (shouldValidate) {
966
- const existingCt = allCustomTypes.find((c) => c.id === customType.id);
967
- // Exit early if the custom type doesn't exist
968
- if (!existingCt) return customTypes;
969
-
970
- ctFlatFieldMap = getCustomTypeStaticFieldsMap(existingCt);
971
- }
972
-
973
- const customTypeFields = customType.fields.reduce<PickerCustomType>(
974
- (fields, field) => {
975
- // Check if the field exists (only if validating)
976
- const existingField = ctFlatFieldMap[getId(field)];
977
- if (shouldValidate && existingField === undefined) return fields;
978
-
944
+ function convertLinkCustomtypesToFieldCheckMap(
945
+ customTypes: LinkCustomtypes,
946
+ ): PickerCustomTypes {
947
+ return customTypes.reduce<PickerCustomTypes>((customTypes, customType) => {
948
+ if (typeof customType === "string") return customTypes;
949
+
950
+ customTypes[customType.id] = customType.fields.reduce<PickerCustomType>(
951
+ (customTypeFields, field) => {
952
+ if (typeof field === "string") {
979
953
  // Regular field
980
- if (typeof field === "string") {
981
- // Check if the field matched the existing one in the custom type (only if validating)
982
- if (
983
- shouldValidate &&
984
- existingField !== undefined &&
985
- existingField.type === "Group"
986
- ) {
987
- return fields;
988
- }
989
-
990
- fields[field] = { type: "checkbox", value: true };
991
- return fields;
992
- }
993
-
954
+ customTypeFields[field] = { type: "checkbox", value: true };
955
+ } else if ("fields" in field && field.fields !== undefined) {
994
956
  // Group field
995
- if ("fields" in field && field.fields !== undefined) {
996
- // Check if the field matched the existing one in the custom type (only if validating)
997
- if (
998
- shouldValidate &&
999
- existingField !== undefined &&
1000
- existingField.type !== "Group"
1001
- ) {
1002
- return fields;
1003
- }
1004
-
1005
- const groupFieldCheckMap = createGroupFieldCheckMap({
1006
- group: field,
1007
- allCustomTypes,
1008
- ctFlatFieldMap,
1009
- });
1010
-
1011
- if (groupFieldCheckMap) {
1012
- fields[field.id] = groupFieldCheckMap;
1013
- }
1014
-
1015
- return fields;
1016
- }
1017
-
957
+ customTypeFields[field.id] = createGroupFieldCheckMap(field);
958
+ } else if ("customtypes" in field && field.customtypes !== undefined) {
1018
959
  // Content relationship field
1019
- if ("customtypes" in field && field.customtypes !== undefined) {
1020
- // Check if the field matched the existing one in the custom type (only if validating)
1021
- if (
1022
- shouldValidate &&
1023
- existingField !== undefined &&
1024
- !isContentRelationshipField(existingField)
1025
- ) {
1026
- return fields;
1027
- }
1028
-
1029
- const crFieldCheckMap = createContentRelationshipFieldCheckMap({
1030
- field,
1031
- allCustomTypes,
1032
- });
1033
-
1034
- if (crFieldCheckMap) {
1035
- fields[field.id] = crFieldCheckMap;
1036
- }
1037
-
1038
- return fields;
1039
- }
1040
-
1041
- return fields;
1042
- },
1043
- {},
1044
- );
1045
-
1046
- if (Object.keys(customTypeFields).length > 0) {
1047
- customTypes[customType.id] = customTypeFields;
1048
- }
1049
-
1050
- return customTypes;
1051
- },
1052
- {},
1053
- );
1054
-
1055
- return checkMap;
1056
- }
1057
-
1058
- function createGroupFieldCheckMap(args: {
1059
- group: LinkCustomtypesGroupFieldValue;
1060
- allCustomTypes?: CustomType[];
1061
- ctFlatFieldMap: Record<string, NestableWidget | Group>;
1062
- }): PickerFirstLevelGroupField | undefined {
1063
- const { group, ctFlatFieldMap, allCustomTypes } = args;
1064
-
1065
- // If allCustomTypes is undefined, avoid checking if the fields exist.
1066
- const shouldValidate = allCustomTypes !== undefined;
1067
-
1068
- const fieldEntries = group.fields.reduce<PickerFirstLevelGroupFieldValue>(
1069
- (fields, field) => {
1070
- // Check if the field exists (only if validating)
1071
- const existingField = getGroupFieldFromMap(
1072
- ctFlatFieldMap,
1073
- group.id,
1074
- getId(field),
1075
- );
1076
- if (shouldValidate && !existingField) return fields;
1077
-
1078
- // Regular field
1079
- if (typeof field === "string") {
1080
- // Check if the field matched the existing one in the custom type (only if validating)
1081
- if (
1082
- shouldValidate &&
1083
- existingField !== undefined &&
1084
- existingField.type === "Group"
1085
- ) {
1086
- return fields;
960
+ customTypeFields[field.id] =
961
+ createContentRelationshipFieldCheckMap(field);
1087
962
  }
1088
963
 
1089
- fields[field] = { type: "checkbox", value: true };
1090
- return fields;
1091
- }
1092
-
1093
- // Content relationship field
1094
- if ("customtypes" in field && field.customtypes !== undefined) {
1095
- // Check if the field matched the existing one in the custom type (only if validating)
1096
- if (
1097
- shouldValidate &&
1098
- existingField !== undefined &&
1099
- !isContentRelationshipField(existingField)
1100
- ) {
1101
- return fields;
1102
- }
1103
-
1104
- const crFieldCheckMap = createContentRelationshipFieldCheckMap({
1105
- field,
1106
- allCustomTypes,
1107
- });
964
+ return customTypeFields;
965
+ },
966
+ {},
967
+ );
968
+ return customTypes;
969
+ }, {});
970
+ }
1108
971
 
1109
- if (crFieldCheckMap) {
1110
- fields[field.id] = crFieldCheckMap;
972
+ function createGroupFieldCheckMap(
973
+ group: LinkCustomtypesGroupFieldValue,
974
+ ): PickerFirstLevelGroupField {
975
+ return {
976
+ type: "group",
977
+ value: group.fields.reduce<PickerFirstLevelGroupFieldValue>(
978
+ (fields, field) => {
979
+ if (typeof field === "string") {
980
+ // Regular field
981
+ fields[field] = { type: "checkbox", value: true };
982
+ } else if ("customtypes" in field && field.customtypes !== undefined) {
983
+ // Content relationship field
984
+ fields[field.id] = createContentRelationshipFieldCheckMap(field);
1111
985
  }
1112
986
 
1113
987
  return fields;
1114
- }
1115
-
1116
- return fields;
1117
- },
1118
- {},
1119
- );
1120
-
1121
- if (Object.keys(fieldEntries).length === 0) return undefined;
1122
-
1123
- return {
1124
- type: "group",
1125
- value: fieldEntries,
988
+ },
989
+ {},
990
+ ),
1126
991
  };
1127
992
  }
1128
993
 
1129
- function createContentRelationshipFieldCheckMap(args: {
1130
- field: LinkCustomtypesContentRelationshipFieldValue;
1131
- allCustomTypes?: CustomType[];
1132
- }): PickerContentRelationshipField | undefined {
1133
- const { field, allCustomTypes } = args;
1134
-
1135
- // If allCustomTypes is undefined, avoid checking if the fields exists.
1136
- const shouldValidate = allCustomTypes !== undefined;
1137
-
1138
- const fieldEntries =
1139
- field.customtypes.reduce<PickerContentRelationshipFieldValue>(
1140
- (customTypes, customType) => {
1141
- if (typeof customType === "string") return customTypes;
1142
-
1143
- let ctFlatFieldMap: Record<string, NestableWidget | Group> = {};
1144
-
1145
- if (shouldValidate) {
1146
- const existingCt = allCustomTypes.find((c) => c.id === customType.id);
1147
- // Exit early if the custom type doesn't exist
1148
- if (!existingCt) return customTypes;
1149
-
1150
- ctFlatFieldMap = getCustomTypeStaticFieldsMap(existingCt);
1151
- }
1152
-
1153
- const ctFields = customType.fields.reduce<PickerNestedCustomTypeValue>(
1154
- (nestedFields, nestedField) => {
1155
- // Regular field
1156
- if (typeof nestedField === "string") {
1157
- const existingField = ctFlatFieldMap[nestedField];
1158
-
1159
- // Check if the field matched the existing one in the custom type (only if validating)
1160
- if (
1161
- shouldValidate &&
1162
- (existingField === undefined || existingField.type === "Group")
1163
- ) {
1164
- return nestedFields;
1165
- }
1166
-
1167
- nestedFields[nestedField] = { type: "checkbox", value: true };
1168
- return nestedFields;
1169
- }
994
+ function createContentRelationshipFieldCheckMap(
995
+ field: LinkCustomtypesContentRelationshipFieldValue,
996
+ ): PickerContentRelationshipField {
997
+ const crField: PickerContentRelationshipField = {
998
+ type: "contentRelationship",
999
+ value: {},
1000
+ };
1001
+ const crFieldCustomTypes = crField.value;
1170
1002
 
1171
- if ("fields" in nestedField && nestedField.fields !== undefined) {
1172
- // Group field
1173
- const groupFields =
1174
- nestedField.fields.reduce<PickerLeafGroupFieldValue>(
1175
- (groupFields, groupField) => {
1176
- const existingField = getGroupFieldFromMap(
1177
- ctFlatFieldMap,
1178
- nestedField.id,
1179
- groupField,
1180
- );
1181
-
1182
- // Check if the field matched the existing one in the custom type (only if validating)
1183
- if (
1184
- shouldValidate &&
1185
- (existingField === undefined ||
1186
- existingField.type === "Group")
1187
- ) {
1188
- return groupFields;
1189
- }
1003
+ for (const customType of field.customtypes) {
1004
+ if (typeof customType === "string") continue;
1190
1005
 
1191
- groupFields[groupField] = { type: "checkbox", value: true };
1192
- return groupFields;
1193
- },
1194
- {},
1195
- );
1196
-
1197
- if (Object.keys(groupFields).length > 0) {
1198
- nestedFields[nestedField.id] = {
1199
- type: "group",
1200
- value: groupFields,
1201
- };
1202
- }
1203
- }
1006
+ crFieldCustomTypes[customType.id] ??= {};
1007
+ const customTypeFields = crFieldCustomTypes[customType.id];
1204
1008
 
1205
- return nestedFields;
1206
- },
1207
- {},
1009
+ for (const nestedField of customType.fields) {
1010
+ if (typeof nestedField === "string") {
1011
+ // Regular field
1012
+ customTypeFields[nestedField] = { type: "checkbox", value: true };
1013
+ } else {
1014
+ // Group field
1015
+ const groupFieldsEntries = nestedField.fields.map(
1016
+ (field) => [field, { type: "checkbox", value: true }] as const,
1208
1017
  );
1209
1018
 
1210
- if (Object.keys(ctFields).length > 0) {
1211
- customTypes[customType.id] = ctFields;
1019
+ if (groupFieldsEntries.length > 0) {
1020
+ customTypeFields[nestedField.id] = {
1021
+ type: "group",
1022
+ value: Object.fromEntries(groupFieldsEntries),
1023
+ };
1212
1024
  }
1025
+ }
1026
+ }
1027
+ }
1213
1028
 
1214
- return customTypes;
1215
- },
1216
- {},
1217
- );
1218
-
1219
- if (Object.keys(fieldEntries).length === 0) return undefined;
1220
-
1221
- return {
1222
- type: "contentRelationship",
1223
- value: fieldEntries,
1224
- };
1029
+ return crField;
1225
1030
  }
1226
1031
 
1227
1032
  /**
@@ -1230,22 +1035,27 @@ function createContentRelationshipFieldCheckMap(args: {
1230
1035
  * made correctly and that the order is preserved.
1231
1036
  */
1232
1037
  function mergeAndConvertCheckMapToLinkCustomtypes(args: {
1233
- linkCustomtypes: LinkCustomtypes | undefined;
1234
- fieldCheckMap: PickerCustomTypes;
1038
+ existingLinkCustomtypes: LinkCustomtypes | undefined;
1039
+ previousPickerCustomtypes: PickerCustomTypes;
1235
1040
  newCustomType: PickerCustomType;
1236
1041
  customTypeId: string;
1237
1042
  }): LinkCustomtypes {
1238
- const { linkCustomtypes, fieldCheckMap, newCustomType, customTypeId } = args;
1043
+ const {
1044
+ existingLinkCustomtypes,
1045
+ previousPickerCustomtypes,
1046
+ newCustomType,
1047
+ customTypeId,
1048
+ } = args;
1239
1049
 
1240
1050
  const result: NonReadonly<LinkCustomtypes> = [];
1241
1051
  const pickerLinkCustomtypes = convertFieldCheckMapToLinkCustomtypes({
1242
- ...fieldCheckMap,
1052
+ ...previousPickerCustomtypes,
1243
1053
  [customTypeId]: newCustomType,
1244
1054
  });
1245
1055
 
1246
- if (!linkCustomtypes) return pickerLinkCustomtypes;
1056
+ if (!existingLinkCustomtypes) return pickerLinkCustomtypes;
1247
1057
 
1248
- for (const existingLinkCt of linkCustomtypes) {
1058
+ for (const existingLinkCt of existingLinkCustomtypes) {
1249
1059
  const existingPickerLinkCt = pickerLinkCustomtypes.find((ct) => {
1250
1060
  return getId(ct) === getId(existingLinkCt);
1251
1061
  });
@@ -1353,139 +1163,61 @@ function createContentRelationshipLinkCustomtypes(
1353
1163
  );
1354
1164
  }
1355
1165
 
1356
- type CountPickedFieldsResult = {
1357
- pickedFields: number;
1358
- nestedPickedFields: number;
1359
- };
1360
-
1361
1166
  /**
1362
1167
  * Generic recursive function that goes down the fields check map and counts all
1363
1168
  * the properties that are set to true, which correspond to selected fields.
1364
1169
  *
1365
- * Distinguishes between all picked fields and nested picked fields within a
1366
- * content relationship field.
1367
- *
1368
1170
  * It's not type safe, but checks the type of the values at runtime so that
1369
1171
  * it only recurses into valid objects, and only counts checkbox fields.
1370
1172
  */
1371
- export function countPickedFields(
1173
+ function countPickedFields(
1372
1174
  fields: Record<string, unknown> | undefined,
1373
- isNested = false,
1374
- ): CountPickedFieldsResult {
1375
- if (!fields) return { pickedFields: 0, nestedPickedFields: 0 };
1376
-
1377
- return Object.values(fields).reduce<CountPickedFieldsResult>(
1378
- (result, value) => {
1379
- if (!isValidObject(value)) return result;
1380
-
1381
- if ("type" in value && value.type === "checkbox") {
1382
- const isChecked = Boolean(value.value);
1383
- if (!isChecked) return result;
1384
-
1385
- return {
1386
- pickedFields: result.pickedFields + 1,
1387
- nestedPickedFields: result.nestedPickedFields + (isNested ? 1 : 0),
1388
- };
1389
- }
1390
-
1391
- if ("type" in value && value.type === "contentRelationship") {
1392
- const { pickedFields, nestedPickedFields } = countPickedFields(
1393
- value,
1394
- true,
1395
- );
1396
-
1397
- return {
1398
- pickedFields: result.pickedFields + pickedFields,
1399
- nestedPickedFields: result.nestedPickedFields + nestedPickedFields,
1400
- };
1401
- }
1402
-
1403
- const { pickedFields, nestedPickedFields } = countPickedFields(
1404
- value,
1405
- isNested,
1406
- );
1407
-
1408
- return {
1409
- pickedFields: result.pickedFields + pickedFields,
1410
- nestedPickedFields: result.nestedPickedFields + nestedPickedFields,
1411
- };
1412
- },
1413
- {
1414
- pickedFields: 0,
1415
- nestedPickedFields: 0,
1416
- },
1417
- );
1175
+ ): number {
1176
+ if (!fields) return 0;
1177
+ return Object.values(fields).reduce<number>((count, value) => {
1178
+ if (!isValidObject(value)) return count;
1179
+ if (isCheckboxValue(value)) return count + (value.value ? 1 : 0);
1180
+ return count + countPickedFields(value);
1181
+ }, 0);
1182
+ }
1183
+ function isCheckboxValue(value: unknown): value is PickerCheckboxField {
1184
+ if (!isValidObject(value)) return false;
1185
+ return "type" in value && value.type === "checkbox";
1418
1186
  }
1419
1187
 
1420
- function isContentRelationshipField(field: DynamicWidget): field is Link {
1421
- return field.type === "Link" && field.config?.select === "document";
1188
+ function isGroupField(field: NestableWidget | Group): field is Group {
1189
+ return field.type === "Group";
1422
1190
  }
1423
1191
 
1424
- /**
1425
- * Check if the field is a Content Relationship Link with a **single** custom
1426
- * type. CRs with multiple custom types are not currently supported (legacy).
1427
- */
1428
- function isContentRelationshipFieldWithSingleCustomtype(
1192
+ function isContentRelationshipField(
1429
1193
  field: NestableWidget | Group,
1430
1194
  ): field is Link {
1431
- return !!(
1432
- isContentRelationshipField(field) &&
1433
- field.config?.customtypes &&
1434
- field.config.customtypes.length === 1
1195
+ return (
1196
+ field.type === "Link" &&
1197
+ field.config?.select === "document" &&
1198
+ field.config?.customtypes !== undefined
1435
1199
  );
1436
1200
  }
1437
1201
 
1438
- /**
1439
- * Flattens all custom type tabs and fields into an array of [fieldId, field] tuples.
1440
- * Also filters out invalid fields.
1441
- */
1442
- function getCustomTypeStaticFields(
1443
- customType: CustomType,
1444
- ): [fieldId: string, field: NestableWidget | Group][] {
1202
+ function getCustomTypeStaticFields(customType: CustomType) {
1445
1203
  return Object.values(customType.json).flatMap((tabFields) => {
1446
- return Object.entries(tabFields).flatMap<[string, NestableWidget | Group]>(
1447
- ([fieldId, field]) => {
1448
- return isValidField(fieldId, field) ? [[fieldId, field]] : [];
1449
- },
1450
- );
1451
- });
1452
- }
1453
-
1454
- /**
1455
- * Flattens all custom type tabs and fields into a map of field ids to fields.
1456
- * Also filters out invalid fields.
1457
- */
1458
- function getCustomTypeStaticFieldsMap(
1459
- customType: CustomType,
1460
- ): Record<string, NestableWidget | Group> {
1461
- return Object.fromEntries(getCustomTypeStaticFields(customType));
1462
- }
1463
-
1464
- function getGroupFieldFromMap(
1465
- flattenFields: Record<string, NestableWidget | Group>,
1466
- groupId: string,
1467
- fieldId: string,
1468
- ) {
1469
- const group = flattenFields[groupId];
1470
- if (group === undefined || group.type !== "Group") return undefined;
1471
- return group.config?.fields?.[fieldId];
1472
- }
1204
+ return Object.entries(tabFields).flatMap(([fieldId, field]) => {
1205
+ if (
1206
+ field.type !== "Slices" &&
1207
+ field.type !== "Choice" &&
1208
+ // Filter out uid fields because it's a special field returned by the
1209
+ // API and is not part of the data object in the document.
1210
+ // We also filter by key "uid", because (as of the time of writing
1211
+ // this), creating any field with that API id will result in it being
1212
+ // used for metadata.
1213
+ (field.type !== "UID" || fieldId !== "uid")
1214
+ ) {
1215
+ return { fieldId, field: field as NestableWidget | Group };
1216
+ }
1473
1217
 
1474
- function isValidField(
1475
- fieldId: string,
1476
- field: DynamicWidget,
1477
- ): field is NestableWidget | Group {
1478
- return (
1479
- field.type !== "Slices" &&
1480
- field.type !== "Choice" &&
1481
- // We don't display uid fields because they're a special field returned by
1482
- // the API and they're not included in the document data object.
1483
- // We also filter by key "uid", because (as of the time of writing this)
1484
- // creating any field with that API id will result in it being used for
1485
- // metadata, regardless of its type.
1486
- field.type !== "UID" &&
1487
- fieldId !== "uid"
1488
- );
1218
+ return [];
1219
+ });
1220
+ });
1489
1221
  }
1490
1222
 
1491
1223
  function getGroupFields(group: Group) {
@@ -1494,7 +1226,6 @@ function getGroupFields(group: Group) {
1494
1226
  return { fieldId, field: field as NestableWidget };
1495
1227
  });
1496
1228
  }
1497
-
1498
1229
  /** If it's a string, return it, otherwise return the `id` property. */
1499
1230
  function getId<T extends string | { id: string }>(customType: T): string {
1500
1231
  if (typeof customType === "string") return customType;