@contentful/experiences-visual-editor-react 1.39.0-alpha-20250528T1342-e28bc3d.0 → 1.39.0-alpha-20250603T1404-5a5eb4e.0

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.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import styleInject from 'style-inject';
2
2
  import React, { useState, useEffect, useCallback, forwardRef, useMemo, useLayoutEffect, useRef } from 'react';
3
3
  import { create } from 'zustand';
4
4
  import { produce } from 'immer';
5
- import { isEqual, omit, isArray, get as get$1 } from 'lodash-es';
5
+ import { isEqual, omit, isArray, get as get$1, debounce } from 'lodash-es';
6
6
  import { z } from 'zod';
7
7
  import md5 from 'md5';
8
8
  import { BLOCKS } from '@contentful/rich-text-types';
@@ -228,6 +228,7 @@ const OUTGOING_EVENTS = {
228
228
  OutsideCanvasClick: 'outsideCanvasClick',
229
229
  SDKFeatures: 'sdkFeatures',
230
230
  RequestEntities: 'REQUEST_ENTITIES',
231
+ CanvasGeometryUpdated: 'canvasGeometryUpdated',
231
232
  };
232
233
  const INCOMING_EVENTS$1 = {
233
234
  RequestEditorMode: 'requestEditorMode',
@@ -867,17 +868,16 @@ const PrimitiveValueSchema$1 = z.union([
867
868
  z.record(z.any(), z.any()),
868
869
  z.undefined(),
869
870
  ]);
871
+ const UsedComponentsSchema$1 = z.array(z.object({
872
+ sys: z.object({
873
+ type: z.literal('Link'),
874
+ id: z.string(),
875
+ linkType: z.literal('Entry'),
876
+ }),
877
+ }));
870
878
  const uuidKeySchema$1 = z
871
879
  .string()
872
880
  .regex(/^[a-zA-Z0-9-_]{1,21}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,21}$/' });
873
- /**
874
- * Property keys for imported components have a limit of 32 characters (to be implemented) while
875
- * property keys for patterns have a limit of 54 characters (<32-char-variabl-name>_<21-char-nanoid-id>).
876
- * Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
877
- */
878
- const propertyKeySchema$1 = z
879
- .string()
880
- .regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
881
881
  const DataSourceSchema$1 = z.record(uuidKeySchema$1, z.object({
882
882
  sys: z.object({
883
883
  type: z.literal('Link'),
@@ -885,7 +885,62 @@ const DataSourceSchema$1 = z.record(uuidKeySchema$1, z.object({
885
885
  linkType: z.enum(['Entry', 'Asset']),
886
886
  }),
887
887
  }));
888
+ const UnboundValuesSchema$1 = z.record(uuidKeySchema$1, z.object({
889
+ value: PrimitiveValueSchema$1,
890
+ }));
891
+ /**
892
+ * Property keys for imported components have a limit of 32 characters (to be implemented) while
893
+ * property keys for patterns have a limit of 54 characters (<32-char-variable-name>_<21-char-nanoid-id>).
894
+ * Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
895
+ */
896
+ const propertyKeySchema$1 = z
897
+ .string()
898
+ .regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
899
+ const ComponentTreeNodeIdSchema$1 = z
900
+ .string()
901
+ .regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
902
+ const breakpointsRefinement$1 = (value, ctx) => {
903
+ if (!value.length || value[0].query !== '*') {
904
+ ctx.addIssue({
905
+ code: z.ZodIssueCode.custom,
906
+ message: `The first breakpoint should include the following attributes: { "query": "*" }`,
907
+ });
908
+ }
909
+ const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
910
+ // check if the current breakpoint id is found in the rest of the array
911
+ const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
912
+ return breakpointIndex !== currentBreakpointIndex;
913
+ });
914
+ if (hasDuplicateIds) {
915
+ ctx.addIssue({
916
+ code: z.ZodIssueCode.custom,
917
+ message: `Breakpoint IDs must be unique`,
918
+ });
919
+ }
920
+ // Extract the queries boundary by removing the special characters around it
921
+ const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
922
+ // sort updates queries array in place so we need to create a copy
923
+ const originalQueries = [...queries];
924
+ queries.sort((q1, q2) => {
925
+ if (q1 === '*') {
926
+ return -1;
927
+ }
928
+ if (q2 === '*') {
929
+ return 1;
930
+ }
931
+ return q1 > q2 ? -1 : 1;
932
+ });
933
+ if (originalQueries.join('') !== queries.join('')) {
934
+ ctx.addIssue({
935
+ code: z.ZodIssueCode.custom,
936
+ message: `Breakpoints should be ordered from largest to smallest pixel value`,
937
+ });
938
+ }
939
+ };
888
940
  const ValuesByBreakpointSchema$1 = z.record(z.lazy(() => PrimitiveValueSchema$1));
941
+ const BindingSourceTypeEnumSchema$1 = z
942
+ .array(z.enum(['entry', 'asset', 'manual', 'experience']))
943
+ .nonempty();
889
944
  const DesignValueSchema$1 = z
890
945
  .object({
891
946
  type: z.literal('DesignValue'),
@@ -918,8 +973,6 @@ const ComponentValueSchema$1 = z
918
973
  key: z.string(),
919
974
  })
920
975
  .strict();
921
- // TODO: finalize schema structure before release
922
- // https://contentful.atlassian.net/browse/LUMOS-523
923
976
  const NoValueSchema$1 = z.object({ type: z.literal('NoValue') }).strict();
924
977
  const ComponentPropertyValueSchema$1 = z.discriminatedUnion('type', [
925
978
  DesignValueSchema$1,
@@ -931,41 +984,12 @@ const ComponentPropertyValueSchema$1 = z.discriminatedUnion('type', [
931
984
  ]);
932
985
  // TODO: finalize schema structure before release
933
986
  // https://contentful.atlassian.net/browse/LUMOS-523
934
- const VariableMappingSchema$1 = z.object({
935
- patternPropertyDefinitionId: propertyKeySchema$1,
936
- type: z.literal('ContentTypeMapping'),
937
- pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
938
- });
939
- const VariableMappingsSchema$1 = z.record(propertyKeySchema$1, VariableMappingSchema$1);
940
- // TODO: finalize schema structure before release
941
- // https://contentful.atlassian.net/browse/LUMOS-523
942
- const PatternPropertyDefinitionSchema$1 = z.object({
943
- defaultValue: z
944
- .record(z.string(), z.object({
945
- sys: z.object({
946
- type: z.literal('Link'),
947
- id: z.string(),
948
- linkType: z.enum(['Entry']),
949
- }),
950
- }))
951
- .optional(),
952
- contentTypes: z.record(z.string(), z.object({
953
- sys: z.object({
954
- type: z.literal('Link'),
955
- id: z.string(),
956
- linkType: z.enum(['ContentType']),
957
- }),
958
- })),
959
- });
960
- const PatternPropertyDefinitionsSchema$1 = z.record(propertyKeySchema$1, PatternPropertyDefinitionSchema$1);
961
- // TODO: finalize schema structure before release
962
- // https://contentful.atlassian.net/browse/LUMOS-523
963
987
  const PatternPropertySchema$1 = z.object({
964
988
  type: z.literal('BoundValue'),
965
989
  path: z.string(),
966
990
  contentType: z.string(),
967
991
  });
968
- const PatternPropertysSchema$1 = z.record(propertyKeySchema$1, PatternPropertySchema$1);
992
+ const PatternPropertiesSchema$1 = z.record(propertyKeySchema$1, PatternPropertySchema$1);
969
993
  const BreakpointSchema$1 = z
970
994
  .object({
971
995
  id: propertyKeySchema$1,
@@ -975,12 +999,6 @@ const BreakpointSchema$1 = z
975
999
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
976
1000
  })
977
1001
  .strict();
978
- const UnboundValuesSchema$1 = z.record(uuidKeySchema$1, z.object({
979
- value: PrimitiveValueSchema$1,
980
- }));
981
- const ComponentTreeNodeIdSchema$1 = z
982
- .string()
983
- .regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
984
1002
  // Use helper schema to define a recursive schema with its type correctly below
985
1003
  const BaseComponentTreeNodeSchema$1 = z.object({
986
1004
  id: ComponentTreeNodeIdSchema$1.optional(),
@@ -988,14 +1006,8 @@ const BaseComponentTreeNodeSchema$1 = z.object({
988
1006
  displayName: z.string().optional(),
989
1007
  slotId: z.string().optional(),
990
1008
  variables: z.record(propertyKeySchema$1, ComponentPropertyValueSchema$1),
991
- patternProperties: PatternPropertysSchema$1.optional(),
1009
+ patternProperties: PatternPropertiesSchema$1.optional(),
992
1010
  });
993
- const ComponentTreeNodeSchema$1 = BaseComponentTreeNodeSchema$1.extend({
994
- children: z.lazy(() => ComponentTreeNodeSchema$1.array()),
995
- });
996
- const BindingSourceTypeEnumSchema$1 = z
997
- .array(z.enum(['entry', 'asset', 'manual', 'experience']))
998
- .nonempty();
999
1011
  const ComponentVariableSchema$1 = z.object({
1000
1012
  displayName: z.string().optional(),
1001
1013
  type: DefinitionPropertyTypeSchema$1,
@@ -1016,8 +1028,25 @@ const ComponentVariableSchema$1 = z.object({
1016
1028
  })
1017
1029
  .optional(),
1018
1030
  });
1019
- const ComponentVariablesSchema$1 = z.record(z.string().regex(/^[a-zA-Z0-9-_]{1,54}$/), // Here the key is <variableName>_<nanoidId> so we need to allow for a longer length
1020
- ComponentVariableSchema$1);
1031
+ const ComponentTreeNodeSchema$1 = BaseComponentTreeNodeSchema$1.extend({
1032
+ children: z.lazy(() => ComponentTreeNodeSchema$1.array()),
1033
+ });
1034
+ const ComponentTreeSchema$1 = z
1035
+ .object({
1036
+ breakpoints: z.array(BreakpointSchema$1).superRefine(breakpointsRefinement$1),
1037
+ children: z.array(ComponentTreeNodeSchema$1),
1038
+ schemaVersion: SchemaVersions$1,
1039
+ })
1040
+ .strict();
1041
+ const localeWrapper$1 = (fieldSchema) => z.record(z.string(), fieldSchema);
1042
+
1043
+ z.object({
1044
+ componentTree: localeWrapper$1(ComponentTreeSchema$1),
1045
+ dataSource: localeWrapper$1(DataSourceSchema$1),
1046
+ unboundValues: localeWrapper$1(UnboundValuesSchema$1),
1047
+ usedComponents: localeWrapper$1(UsedComponentsSchema$1).optional(),
1048
+ });
1049
+
1021
1050
  const THUMBNAIL_IDS$1 = [
1022
1051
  'columns',
1023
1052
  'columnsPlusRight',
@@ -1043,6 +1072,37 @@ const THUMBNAIL_IDS$1 = [
1043
1072
  'textColumns',
1044
1073
  'duplex',
1045
1074
  ];
1075
+ // TODO: finalize schema structure before release
1076
+ // https://contentful.atlassian.net/browse/LUMOS-523
1077
+ const VariableMappingSchema$1 = z.object({
1078
+ patternPropertyDefinitionId: propertyKeySchema$1,
1079
+ type: z.literal('ContentTypeMapping'),
1080
+ pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
1081
+ });
1082
+ // TODO: finalize schema structure before release
1083
+ // https://contentful.atlassian.net/browse/LUMOS-523
1084
+ const PatternPropertyDefinitionSchema$1 = z.object({
1085
+ defaultValue: z
1086
+ .record(z.string(), z.object({
1087
+ sys: z.object({
1088
+ type: z.literal('Link'),
1089
+ id: z.string(),
1090
+ linkType: z.enum(['Entry']),
1091
+ }),
1092
+ }))
1093
+ .optional(),
1094
+ contentTypes: z.record(z.string(), z.object({
1095
+ sys: z.object({
1096
+ type: z.literal('Link'),
1097
+ id: z.string(),
1098
+ linkType: z.enum(['ContentType']),
1099
+ }),
1100
+ })),
1101
+ });
1102
+ const PatternPropertyDefinitionsSchema$1 = z.record(propertyKeySchema$1, PatternPropertyDefinitionSchema$1);
1103
+ const VariableMappingsSchema$1 = z.record(propertyKeySchema$1, VariableMappingSchema$1);
1104
+ const ComponentVariablesSchema$1 = z.record(z.string().regex(/^[a-zA-Z0-9-_]{1,54}$/), // Here the key is <variableName>_<nanoidId> so we need to allow for a longer length
1105
+ ComponentVariableSchema$1);
1046
1106
  const ComponentSettingsSchema$1 = z.object({
1047
1107
  variableDefinitions: ComponentVariablesSchema$1,
1048
1108
  thumbnailId: z.enum(THUMBNAIL_IDS$1).optional(),
@@ -1050,65 +1110,12 @@ const ComponentSettingsSchema$1 = z.object({
1050
1110
  variableMappings: VariableMappingsSchema$1.optional(),
1051
1111
  patternPropertyDefinitions: PatternPropertyDefinitionsSchema$1.optional(),
1052
1112
  });
1053
- const UsedComponentsSchema$1 = z.array(z.object({
1054
- sys: z.object({
1055
- type: z.literal('Link'),
1056
- id: z.string(),
1057
- linkType: z.literal('Entry'),
1058
- }),
1059
- }));
1060
- const breakpointsRefinement$1 = (value, ctx) => {
1061
- if (!value.length || value[0].query !== '*') {
1062
- ctx.addIssue({
1063
- code: z.ZodIssueCode.custom,
1064
- message: `The first breakpoint should include the following attributes: { "query": "*" }`,
1065
- });
1066
- }
1067
- const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
1068
- // check if the current breakpoint id is found in the rest of the array
1069
- const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
1070
- return breakpointIndex !== currentBreakpointIndex;
1071
- });
1072
- if (hasDuplicateIds) {
1073
- ctx.addIssue({
1074
- code: z.ZodIssueCode.custom,
1075
- message: `Breakpoint IDs must be unique`,
1076
- });
1077
- }
1078
- // Extract the queries boundary by removing the special characters around it
1079
- const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
1080
- // sort updates queries array in place so we need to create a copy
1081
- const originalQueries = [...queries];
1082
- queries.sort((q1, q2) => {
1083
- if (q1 === '*') {
1084
- return -1;
1085
- }
1086
- if (q2 === '*') {
1087
- return 1;
1088
- }
1089
- return q1 > q2 ? -1 : 1;
1090
- });
1091
- if (originalQueries.join('') !== queries.join('')) {
1092
- ctx.addIssue({
1093
- code: z.ZodIssueCode.custom,
1094
- message: `Breakpoints should be ordered from largest to smallest pixel value`,
1095
- });
1096
- }
1097
- };
1098
- const ComponentTreeSchema$1 = z
1099
- .object({
1100
- breakpoints: z.array(BreakpointSchema$1).superRefine(breakpointsRefinement$1),
1101
- children: z.array(ComponentTreeNodeSchema$1),
1102
- schemaVersion: SchemaVersions$1,
1103
- })
1104
- .strict();
1105
- const localeWrapper$1 = (fieldSchema) => z.record(z.string(), fieldSchema);
1106
1113
  z.object({
1107
1114
  componentTree: localeWrapper$1(ComponentTreeSchema$1),
1108
1115
  dataSource: localeWrapper$1(DataSourceSchema$1),
1109
1116
  unboundValues: localeWrapper$1(UnboundValuesSchema$1),
1110
1117
  usedComponents: localeWrapper$1(UsedComponentsSchema$1).optional(),
1111
- componentSettings: localeWrapper$1(ComponentSettingsSchema$1).optional(),
1118
+ componentSettings: localeWrapper$1(ComponentSettingsSchema$1),
1112
1119
  });
1113
1120
 
1114
1121
  z.object({
@@ -1392,6 +1399,51 @@ let DebugLogger$1 = class DebugLogger {
1392
1399
  DebugLogger$1.instance = null;
1393
1400
  DebugLogger$1.getInstance();
1394
1401
 
1402
+ const findOutermostCoordinates = (first, second) => {
1403
+ return {
1404
+ top: Math.min(first.top, second.top),
1405
+ right: Math.max(first.right, second.right),
1406
+ bottom: Math.max(first.bottom, second.bottom),
1407
+ left: Math.min(first.left, second.left),
1408
+ };
1409
+ };
1410
+ const getElementCoordinates = (element) => {
1411
+ const rect = element.getBoundingClientRect();
1412
+ /**
1413
+ * If element does not have children, or element has it's own width or height,
1414
+ * return the element's coordinates.
1415
+ */
1416
+ if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
1417
+ return rect;
1418
+ }
1419
+ const rects = [];
1420
+ /**
1421
+ * If element has children, or element does not have it's own width and height,
1422
+ * we find the cordinates of the children, and assume the outermost coordinates of the children
1423
+ * as the coordinate of the element.
1424
+ *
1425
+ * E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
1426
+ * The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
1427
+ */
1428
+ for (const child of element.children) {
1429
+ const childRect = getElementCoordinates(child);
1430
+ if (childRect.width !== 0 || childRect.height !== 0) {
1431
+ const { top, right, bottom, left } = childRect;
1432
+ rects.push({ top, right, bottom, left });
1433
+ }
1434
+ }
1435
+ if (rects.length === 0) {
1436
+ return rect;
1437
+ }
1438
+ const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
1439
+ return DOMRect.fromRect({
1440
+ x: left,
1441
+ y: top,
1442
+ height: bottom - top,
1443
+ width: right - left,
1444
+ });
1445
+ };
1446
+
1395
1447
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1396
1448
  const isLinkToAsset = (variable) => {
1397
1449
  if (!variable)
@@ -3026,17 +3078,16 @@ const PrimitiveValueSchema = z.union([
3026
3078
  z.record(z.any(), z.any()),
3027
3079
  z.undefined(),
3028
3080
  ]);
3081
+ const UsedComponentsSchema = z.array(z.object({
3082
+ sys: z.object({
3083
+ type: z.literal('Link'),
3084
+ id: z.string(),
3085
+ linkType: z.literal('Entry'),
3086
+ }),
3087
+ }));
3029
3088
  const uuidKeySchema = z
3030
3089
  .string()
3031
3090
  .regex(/^[a-zA-Z0-9-_]{1,21}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,21}$/' });
3032
- /**
3033
- * Property keys for imported components have a limit of 32 characters (to be implemented) while
3034
- * property keys for patterns have a limit of 54 characters (<32-char-variabl-name>_<21-char-nanoid-id>).
3035
- * Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
3036
- */
3037
- const propertyKeySchema = z
3038
- .string()
3039
- .regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
3040
3091
  const DataSourceSchema = z.record(uuidKeySchema, z.object({
3041
3092
  sys: z.object({
3042
3093
  type: z.literal('Link'),
@@ -3044,7 +3095,62 @@ const DataSourceSchema = z.record(uuidKeySchema, z.object({
3044
3095
  linkType: z.enum(['Entry', 'Asset']),
3045
3096
  }),
3046
3097
  }));
3098
+ const UnboundValuesSchema = z.record(uuidKeySchema, z.object({
3099
+ value: PrimitiveValueSchema,
3100
+ }));
3101
+ /**
3102
+ * Property keys for imported components have a limit of 32 characters (to be implemented) while
3103
+ * property keys for patterns have a limit of 54 characters (<32-char-variable-name>_<21-char-nanoid-id>).
3104
+ * Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
3105
+ */
3106
+ const propertyKeySchema = z
3107
+ .string()
3108
+ .regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
3109
+ const ComponentTreeNodeIdSchema = z
3110
+ .string()
3111
+ .regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
3112
+ const breakpointsRefinement = (value, ctx) => {
3113
+ if (!value.length || value[0].query !== '*') {
3114
+ ctx.addIssue({
3115
+ code: z.ZodIssueCode.custom,
3116
+ message: `The first breakpoint should include the following attributes: { "query": "*" }`,
3117
+ });
3118
+ }
3119
+ const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
3120
+ // check if the current breakpoint id is found in the rest of the array
3121
+ const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
3122
+ return breakpointIndex !== currentBreakpointIndex;
3123
+ });
3124
+ if (hasDuplicateIds) {
3125
+ ctx.addIssue({
3126
+ code: z.ZodIssueCode.custom,
3127
+ message: `Breakpoint IDs must be unique`,
3128
+ });
3129
+ }
3130
+ // Extract the queries boundary by removing the special characters around it
3131
+ const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
3132
+ // sort updates queries array in place so we need to create a copy
3133
+ const originalQueries = [...queries];
3134
+ queries.sort((q1, q2) => {
3135
+ if (q1 === '*') {
3136
+ return -1;
3137
+ }
3138
+ if (q2 === '*') {
3139
+ return 1;
3140
+ }
3141
+ return q1 > q2 ? -1 : 1;
3142
+ });
3143
+ if (originalQueries.join('') !== queries.join('')) {
3144
+ ctx.addIssue({
3145
+ code: z.ZodIssueCode.custom,
3146
+ message: `Breakpoints should be ordered from largest to smallest pixel value`,
3147
+ });
3148
+ }
3149
+ };
3047
3150
  const ValuesByBreakpointSchema = z.record(z.lazy(() => PrimitiveValueSchema));
3151
+ const BindingSourceTypeEnumSchema = z
3152
+ .array(z.enum(['entry', 'asset', 'manual', 'experience']))
3153
+ .nonempty();
3048
3154
  const DesignValueSchema = z
3049
3155
  .object({
3050
3156
  type: z.literal('DesignValue'),
@@ -3077,8 +3183,6 @@ const ComponentValueSchema = z
3077
3183
  key: z.string(),
3078
3184
  })
3079
3185
  .strict();
3080
- // TODO: finalize schema structure before release
3081
- // https://contentful.atlassian.net/browse/LUMOS-523
3082
3186
  const NoValueSchema = z.object({ type: z.literal('NoValue') }).strict();
3083
3187
  const ComponentPropertyValueSchema = z.discriminatedUnion('type', [
3084
3188
  DesignValueSchema,
@@ -3090,41 +3194,12 @@ const ComponentPropertyValueSchema = z.discriminatedUnion('type', [
3090
3194
  ]);
3091
3195
  // TODO: finalize schema structure before release
3092
3196
  // https://contentful.atlassian.net/browse/LUMOS-523
3093
- const VariableMappingSchema = z.object({
3094
- patternPropertyDefinitionId: propertyKeySchema,
3095
- type: z.literal('ContentTypeMapping'),
3096
- pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
3097
- });
3098
- const VariableMappingsSchema = z.record(propertyKeySchema, VariableMappingSchema);
3099
- // TODO: finalize schema structure before release
3100
- // https://contentful.atlassian.net/browse/LUMOS-523
3101
- const PatternPropertyDefinitionSchema = z.object({
3102
- defaultValue: z
3103
- .record(z.string(), z.object({
3104
- sys: z.object({
3105
- type: z.literal('Link'),
3106
- id: z.string(),
3107
- linkType: z.enum(['Entry']),
3108
- }),
3109
- }))
3110
- .optional(),
3111
- contentTypes: z.record(z.string(), z.object({
3112
- sys: z.object({
3113
- type: z.literal('Link'),
3114
- id: z.string(),
3115
- linkType: z.enum(['ContentType']),
3116
- }),
3117
- })),
3118
- });
3119
- const PatternPropertyDefinitionsSchema = z.record(propertyKeySchema, PatternPropertyDefinitionSchema);
3120
- // TODO: finalize schema structure before release
3121
- // https://contentful.atlassian.net/browse/LUMOS-523
3122
3197
  const PatternPropertySchema = z.object({
3123
3198
  type: z.literal('BoundValue'),
3124
3199
  path: z.string(),
3125
3200
  contentType: z.string(),
3126
3201
  });
3127
- const PatternPropertysSchema = z.record(propertyKeySchema, PatternPropertySchema);
3202
+ const PatternPropertiesSchema = z.record(propertyKeySchema, PatternPropertySchema);
3128
3203
  const BreakpointSchema = z
3129
3204
  .object({
3130
3205
  id: propertyKeySchema,
@@ -3134,12 +3209,6 @@ const BreakpointSchema = z
3134
3209
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
3135
3210
  })
3136
3211
  .strict();
3137
- const UnboundValuesSchema = z.record(uuidKeySchema, z.object({
3138
- value: PrimitiveValueSchema,
3139
- }));
3140
- const ComponentTreeNodeIdSchema = z
3141
- .string()
3142
- .regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
3143
3212
  // Use helper schema to define a recursive schema with its type correctly below
3144
3213
  const BaseComponentTreeNodeSchema = z.object({
3145
3214
  id: ComponentTreeNodeIdSchema.optional(),
@@ -3147,14 +3216,8 @@ const BaseComponentTreeNodeSchema = z.object({
3147
3216
  displayName: z.string().optional(),
3148
3217
  slotId: z.string().optional(),
3149
3218
  variables: z.record(propertyKeySchema, ComponentPropertyValueSchema),
3150
- patternProperties: PatternPropertysSchema.optional(),
3219
+ patternProperties: PatternPropertiesSchema.optional(),
3151
3220
  });
3152
- const ComponentTreeNodeSchema = BaseComponentTreeNodeSchema.extend({
3153
- children: z.lazy(() => ComponentTreeNodeSchema.array()),
3154
- });
3155
- const BindingSourceTypeEnumSchema = z
3156
- .array(z.enum(['entry', 'asset', 'manual', 'experience']))
3157
- .nonempty();
3158
3221
  const ComponentVariableSchema = z.object({
3159
3222
  displayName: z.string().optional(),
3160
3223
  type: DefinitionPropertyTypeSchema,
@@ -3175,8 +3238,25 @@ const ComponentVariableSchema = z.object({
3175
3238
  })
3176
3239
  .optional(),
3177
3240
  });
3178
- const ComponentVariablesSchema = z.record(z.string().regex(/^[a-zA-Z0-9-_]{1,54}$/), // Here the key is <variableName>_<nanoidId> so we need to allow for a longer length
3179
- ComponentVariableSchema);
3241
+ const ComponentTreeNodeSchema = BaseComponentTreeNodeSchema.extend({
3242
+ children: z.lazy(() => ComponentTreeNodeSchema.array()),
3243
+ });
3244
+ const ComponentTreeSchema = z
3245
+ .object({
3246
+ breakpoints: z.array(BreakpointSchema).superRefine(breakpointsRefinement),
3247
+ children: z.array(ComponentTreeNodeSchema),
3248
+ schemaVersion: SchemaVersions,
3249
+ })
3250
+ .strict();
3251
+ const localeWrapper = (fieldSchema) => z.record(z.string(), fieldSchema);
3252
+
3253
+ z.object({
3254
+ componentTree: localeWrapper(ComponentTreeSchema),
3255
+ dataSource: localeWrapper(DataSourceSchema),
3256
+ unboundValues: localeWrapper(UnboundValuesSchema),
3257
+ usedComponents: localeWrapper(UsedComponentsSchema).optional(),
3258
+ });
3259
+
3180
3260
  const THUMBNAIL_IDS = [
3181
3261
  'columns',
3182
3262
  'columnsPlusRight',
@@ -3202,6 +3282,37 @@ const THUMBNAIL_IDS = [
3202
3282
  'textColumns',
3203
3283
  'duplex',
3204
3284
  ];
3285
+ // TODO: finalize schema structure before release
3286
+ // https://contentful.atlassian.net/browse/LUMOS-523
3287
+ const VariableMappingSchema = z.object({
3288
+ patternPropertyDefinitionId: propertyKeySchema,
3289
+ type: z.literal('ContentTypeMapping'),
3290
+ pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
3291
+ });
3292
+ // TODO: finalize schema structure before release
3293
+ // https://contentful.atlassian.net/browse/LUMOS-523
3294
+ const PatternPropertyDefinitionSchema = z.object({
3295
+ defaultValue: z
3296
+ .record(z.string(), z.object({
3297
+ sys: z.object({
3298
+ type: z.literal('Link'),
3299
+ id: z.string(),
3300
+ linkType: z.enum(['Entry']),
3301
+ }),
3302
+ }))
3303
+ .optional(),
3304
+ contentTypes: z.record(z.string(), z.object({
3305
+ sys: z.object({
3306
+ type: z.literal('Link'),
3307
+ id: z.string(),
3308
+ linkType: z.enum(['ContentType']),
3309
+ }),
3310
+ })),
3311
+ });
3312
+ const PatternPropertyDefinitionsSchema = z.record(propertyKeySchema, PatternPropertyDefinitionSchema);
3313
+ const VariableMappingsSchema = z.record(propertyKeySchema, VariableMappingSchema);
3314
+ const ComponentVariablesSchema = z.record(z.string().regex(/^[a-zA-Z0-9-_]{1,54}$/), // Here the key is <variableName>_<nanoidId> so we need to allow for a longer length
3315
+ ComponentVariableSchema);
3205
3316
  const ComponentSettingsSchema = z.object({
3206
3317
  variableDefinitions: ComponentVariablesSchema,
3207
3318
  thumbnailId: z.enum(THUMBNAIL_IDS).optional(),
@@ -3209,65 +3320,12 @@ const ComponentSettingsSchema = z.object({
3209
3320
  variableMappings: VariableMappingsSchema.optional(),
3210
3321
  patternPropertyDefinitions: PatternPropertyDefinitionsSchema.optional(),
3211
3322
  });
3212
- const UsedComponentsSchema = z.array(z.object({
3213
- sys: z.object({
3214
- type: z.literal('Link'),
3215
- id: z.string(),
3216
- linkType: z.literal('Entry'),
3217
- }),
3218
- }));
3219
- const breakpointsRefinement = (value, ctx) => {
3220
- if (!value.length || value[0].query !== '*') {
3221
- ctx.addIssue({
3222
- code: z.ZodIssueCode.custom,
3223
- message: `The first breakpoint should include the following attributes: { "query": "*" }`,
3224
- });
3225
- }
3226
- const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
3227
- // check if the current breakpoint id is found in the rest of the array
3228
- const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
3229
- return breakpointIndex !== currentBreakpointIndex;
3230
- });
3231
- if (hasDuplicateIds) {
3232
- ctx.addIssue({
3233
- code: z.ZodIssueCode.custom,
3234
- message: `Breakpoint IDs must be unique`,
3235
- });
3236
- }
3237
- // Extract the queries boundary by removing the special characters around it
3238
- const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
3239
- // sort updates queries array in place so we need to create a copy
3240
- const originalQueries = [...queries];
3241
- queries.sort((q1, q2) => {
3242
- if (q1 === '*') {
3243
- return -1;
3244
- }
3245
- if (q2 === '*') {
3246
- return 1;
3247
- }
3248
- return q1 > q2 ? -1 : 1;
3249
- });
3250
- if (originalQueries.join('') !== queries.join('')) {
3251
- ctx.addIssue({
3252
- code: z.ZodIssueCode.custom,
3253
- message: `Breakpoints should be ordered from largest to smallest pixel value`,
3254
- });
3255
- }
3256
- };
3257
- const ComponentTreeSchema = z
3258
- .object({
3259
- breakpoints: z.array(BreakpointSchema).superRefine(breakpointsRefinement),
3260
- children: z.array(ComponentTreeNodeSchema),
3261
- schemaVersion: SchemaVersions,
3262
- })
3263
- .strict();
3264
- const localeWrapper = (fieldSchema) => z.record(z.string(), fieldSchema);
3265
3323
  z.object({
3266
3324
  componentTree: localeWrapper(ComponentTreeSchema),
3267
3325
  dataSource: localeWrapper(DataSourceSchema),
3268
3326
  unboundValues: localeWrapper(UnboundValuesSchema),
3269
3327
  usedComponents: localeWrapper(UsedComponentsSchema).optional(),
3270
- componentSettings: localeWrapper(ComponentSettingsSchema).optional(),
3328
+ componentSettings: localeWrapper(ComponentSettingsSchema),
3271
3329
  });
3272
3330
 
3273
3331
  z.object({
@@ -4061,18 +4119,115 @@ const EmptyCanvasMessage = () => {
4061
4119
  React.createElement("span", { className: styles['empty-canvas-label'] }, "Add components to begin")));
4062
4120
  };
4063
4121
 
4122
+ /**
4123
+ * This function gets the element co-ordinates of a specified component in the DOM and its parent
4124
+ * and sends the DOM Rect to the client app.
4125
+ */
4126
+ const sendCanvasGeometryUpdatedMessage = async (tree, sourceEvent) => {
4127
+ const nodeToCoordinatesMap = {};
4128
+ await waitForAllImagesToBeLoaded();
4129
+ collectNodeCoordinates(tree.root, nodeToCoordinatesMap);
4130
+ sendMessage(OUTGOING_EVENTS.CanvasGeometryUpdated, {
4131
+ size: {
4132
+ width: document.documentElement.scrollWidth,
4133
+ height: document.documentElement.scrollHeight,
4134
+ },
4135
+ nodes: nodeToCoordinatesMap,
4136
+ sourceEvent,
4137
+ });
4138
+ };
4139
+ const collectNodeCoordinates = (node, nodeToCoordinatesMap) => {
4140
+ const selectedElement = document.querySelector(`[data-cf-node-id="${node.data.id}"]`);
4141
+ if (selectedElement) {
4142
+ const rect = getElementCoordinates(selectedElement);
4143
+ nodeToCoordinatesMap[node.data.id] = {
4144
+ coordinates: {
4145
+ x: rect.x + window.scrollX,
4146
+ y: rect.y + window.scrollY,
4147
+ width: rect.width,
4148
+ height: rect.height,
4149
+ },
4150
+ };
4151
+ }
4152
+ node.children.forEach((child) => collectNodeCoordinates(child, nodeToCoordinatesMap));
4153
+ };
4154
+ const waitForAllImagesToBeLoaded = () => {
4155
+ // If the document contains an image, wait for this image to be loaded before collecting & sending all geometry data.
4156
+ const allImageNodes = document.querySelectorAll('img');
4157
+ return Promise.all(Array.from(allImageNodes).map((imageNode) => {
4158
+ if (imageNode.complete) {
4159
+ return Promise.resolve();
4160
+ }
4161
+ return new Promise((resolve, reject) => {
4162
+ const handleImageLoad = (event) => {
4163
+ imageNode.removeEventListener('load', handleImageLoad);
4164
+ imageNode.removeEventListener('error', handleImageLoad);
4165
+ if (event.type === 'error') {
4166
+ console.warn('Image failed to load:', imageNode);
4167
+ reject();
4168
+ }
4169
+ else {
4170
+ resolve();
4171
+ }
4172
+ };
4173
+ imageNode.addEventListener('load', handleImageLoad);
4174
+ imageNode.addEventListener('error', handleImageLoad);
4175
+ });
4176
+ }));
4177
+ };
4178
+
4179
+ const useCanvasGeometryUpdates = ({ tree, rootContainerRef, }) => {
4180
+ const debouncedUpdateGeometry = useMemo(() => debounce((tree, sourceEvent) => {
4181
+ // When the DOM changed, we still need to wait for the next frame to ensure that
4182
+ // rendering is complete (e.g. this is required when deleting a node).
4183
+ window.requestAnimationFrame(() => {
4184
+ sendCanvasGeometryUpdatedMessage(tree, sourceEvent);
4185
+ });
4186
+ }, 100, {
4187
+ leading: true,
4188
+ // To be sure, we recalculate it at the end of the frame again. Though, we couldn't
4189
+ // yet show the need for this. So we might be able to drop this later to boost performance.
4190
+ trailing: true,
4191
+ }), []);
4192
+ // Store tree in a ref to avoid the need to deactivate & reactivate the mutation observer
4193
+ // when the tree changes. This is important to avoid missing out on some mutation events.
4194
+ const treeRef = useRef(tree);
4195
+ useEffect(() => {
4196
+ treeRef.current = tree;
4197
+ }, [tree]);
4198
+ // Handling window resize events
4199
+ useEffect(() => {
4200
+ const resizeEventListener = () => debouncedUpdateGeometry(treeRef.current, 'resize');
4201
+ window.addEventListener('resize', resizeEventListener);
4202
+ return () => window.removeEventListener('resize', resizeEventListener);
4203
+ }, [debouncedUpdateGeometry]);
4204
+ // Handling DOM mutations
4205
+ useEffect(() => {
4206
+ if (!rootContainerRef.current)
4207
+ return;
4208
+ const observer = new MutationObserver(() => debouncedUpdateGeometry(treeRef.current, 'mutation'));
4209
+ observer.observe(rootContainerRef.current, {
4210
+ childList: true,
4211
+ subtree: true,
4212
+ attributes: true,
4213
+ });
4214
+ return () => observer.disconnect();
4215
+ }, [debouncedUpdateGeometry, rootContainerRef]);
4216
+ };
4217
+
4064
4218
  const RootRenderer = () => {
4219
+ const rootContainerRef = useRef(null);
4220
+ const tree = useTreeStore((state) => state.tree);
4221
+ useCanvasGeometryUpdates({ tree, rootContainerRef });
4065
4222
  useEditorSubscriber();
4066
4223
  const breakpoints = useTreeStore((state) => state.breakpoints);
4067
- const containerRef = useRef(null);
4068
4224
  const { resolveDesignValue } = useBreakpoints(breakpoints);
4069
- const tree = useTreeStore((state) => state.tree);
4070
4225
  // If the root blockId is defined but not the default string, it is the entry ID
4071
4226
  // of the experience/ pattern to properly detect circular dependencies.
4072
4227
  const rootBlockId = tree.root.data.blockId ?? ROOT_ID;
4073
4228
  const wrappingPatternIds = rootBlockId !== ROOT_ID ? new Set([rootBlockId]) : new Set();
4074
4229
  return (React.createElement(React.Fragment, null,
4075
- React.createElement("div", { "data-ctfl-root": true, className: styles$2.rootContainer, ref: containerRef }, !tree.root.children.length ? (React.createElement(EmptyCanvasMessage, null)) : (tree.root.children.map((topLevelChildNode) => (React.createElement(EditorBlock, { key: topLevelChildNode.data.id, node: topLevelChildNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds })))))));
4230
+ React.createElement("div", { "data-ctfl-root": true, className: styles$2.rootContainer, ref: rootContainerRef }, !tree.root.children.length ? (React.createElement(EmptyCanvasMessage, null)) : (tree.root.children.map((topLevelChildNode) => (React.createElement(EditorBlock, { key: topLevelChildNode.data.id, node: topLevelChildNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds })))))));
4076
4231
  };
4077
4232
 
4078
4233
  const useInitializeEditor = () => {