@contentful/experiences-visual-editor-react 3.7.0-beta.0 → 3.7.0-dev-20250917T1203-0e23897.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
@@ -1,16 +1,15 @@
1
1
  import styleInject from 'style-inject';
2
2
  import React, { useState, useEffect, useCallback, forwardRef, useMemo, useLayoutEffect, useRef } from 'react';
3
3
  import { z } from 'zod';
4
- import cloneDeep from 'lodash.clonedeep';
4
+ import { cloneDeep, omit, isArray, isEqual, get as get$2, debounce } from 'lodash-es';
5
5
  import md5 from 'md5';
6
6
  import { BLOCKS } from '@contentful/rich-text-types';
7
7
  import { create, useStore } from 'zustand';
8
8
  import { produce } from 'immer';
9
- import { isEqual, get as get$2, debounce } from 'lodash-es';
10
9
  import '@contentful/rich-text-react-renderer';
11
10
 
12
- var css_248z$b = "html,\nbody {\n margin: 0;\n padding: 0;\n}\n\n/*\n * All of these variables are tokens from Forma-36 and should not be adjusted as these\n * are global variables that may affect multiple places.\n * As our customers may use other design libraries, we try to avoid overlapping global\n * variables by always using the prefix `--exp-builder-` inside this SDK.\n */\n\n:root {\n /* Color tokens from Forma 36: https://f36.contentful.com/tokens/color-system */\n --exp-builder-blue100: #e8f5ff;\n --exp-builder-blue200: #ceecff;\n --exp-builder-blue300: #98cbff;\n --exp-builder-blue400: #40a0ff;\n --exp-builder-blue500: #036fe3;\n --exp-builder-blue600: #0059c8;\n --exp-builder-blue700: #0041ab;\n --exp-builder-blue800: #003298;\n --exp-builder-blue900: #002a8e;\n --exp-builder-gray100: #f7f9fa;\n --exp-builder-gray200: #e7ebee;\n --exp-builder-gray300: #cfd9e0;\n --exp-builder-gray400: #aec1cc;\n --exp-builder-gray500: #67728a;\n --exp-builder-gray600: #5a657c;\n --exp-builder-gray700: #414d63;\n --exp-builder-gray800: #1b273a;\n --exp-builder-gray900: #111b2b;\n --exp-builder-purple600: #6c3ecf;\n --exp-builder-red200: #ffe0e0;\n --exp-builder-red800: #7f0010;\n --exp-builder-color-white: #ffffff;\n --exp-builder-glow-primary: 0px 0px 0px 3px #e8f5ff;\n\n /* RGB colors for applying opacity */\n --exp-builder-blue100-rgb: 232, 245, 255;\n --exp-builder-blue300-rgb: 152, 203, 255;\n\n /* Spacing tokens from Forma 36: https://f36.contentful.com/tokens/spacing */\n --exp-builder-spacing-s: 0.75rem;\n --exp-builder-spacing-2xs: 0.25rem;\n\n /* Typography tokens from Forma 36: https://f36.contentful.com/tokens/typography */\n --exp-builder-font-size-l: 1rem;\n --exp-builder-font-size-m: 0.875rem;\n --exp-builder-font-stack-primary: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;\n --exp-builder-line-height-condensed: 1.25;\n}\n";
13
- styleInject(css_248z$b);
11
+ var css_248z$a = "html,\nbody {\n margin: 0;\n padding: 0;\n}\n\n/*\n * All of these variables are tokens from Forma-36 and should not be adjusted as these\n * are global variables that may affect multiple places.\n * As our customers may use other design libraries, we try to avoid overlapping global\n * variables by always using the prefix `--exp-builder-` inside this SDK.\n */\n\n:root {\n /* Color tokens from Forma 36: https://f36.contentful.com/tokens/color-system */\n --exp-builder-blue100: #e8f5ff;\n --exp-builder-blue200: #ceecff;\n --exp-builder-blue300: #98cbff;\n --exp-builder-blue400: #40a0ff;\n --exp-builder-blue500: #036fe3;\n --exp-builder-blue600: #0059c8;\n --exp-builder-blue700: #0041ab;\n --exp-builder-blue800: #003298;\n --exp-builder-blue900: #002a8e;\n --exp-builder-gray100: #f7f9fa;\n --exp-builder-gray200: #e7ebee;\n --exp-builder-gray300: #cfd9e0;\n --exp-builder-gray400: #aec1cc;\n --exp-builder-gray500: #67728a;\n --exp-builder-gray600: #5a657c;\n --exp-builder-gray700: #414d63;\n --exp-builder-gray800: #1b273a;\n --exp-builder-gray900: #111b2b;\n --exp-builder-purple600: #6c3ecf;\n --exp-builder-red200: #ffe0e0;\n --exp-builder-red800: #7f0010;\n --exp-builder-color-white: #ffffff;\n --exp-builder-glow-primary: 0px 0px 0px 3px #e8f5ff;\n\n /* RGB colors for applying opacity */\n --exp-builder-blue100-rgb: 232, 245, 255;\n --exp-builder-blue300-rgb: 152, 203, 255;\n\n /* Spacing tokens from Forma 36: https://f36.contentful.com/tokens/spacing */\n --exp-builder-spacing-s: 0.75rem;\n --exp-builder-spacing-2xs: 0.25rem;\n\n /* Typography tokens from Forma 36: https://f36.contentful.com/tokens/typography */\n --exp-builder-font-size-l: 1rem;\n --exp-builder-font-size-m: 0.875rem;\n --exp-builder-font-stack-primary: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;\n --exp-builder-line-height-condensed: 1.25;\n}\n";
12
+ styleInject(css_248z$a);
14
13
 
15
14
  /** @deprecated will be removed when dropping backward compatibility for old DND */
16
15
  const INCOMING_EVENTS$1 = {
@@ -34,9 +33,7 @@ const INCOMING_EVENTS$1 = {
34
33
  /** @deprecated will be removed when dropping backward compatibility for old DND */
35
34
  HoverComponent: 'hoverComponent',
36
35
  UpdatedEntity: 'updatedEntity',
37
- /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
38
36
  AssembliesAdded: 'assembliesAdded',
39
- /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
40
37
  AssembliesRegistered: 'assembliesRegistered',
41
38
  /** @deprecated will be removed when dropping backward compatibility for old DND */
42
39
  MouseMove: 'mouseMove',
@@ -99,7 +96,6 @@ const CONTENTFUL_COMPONENTS$1 = {
99
96
  name: 'Carousel',
100
97
  },
101
98
  };
102
- const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
103
99
  const CF_STYLE_ATTRIBUTES = [
104
100
  'cfVisibility',
105
101
  'cfHorizontalAlignment',
@@ -758,7 +754,7 @@ const BreakpointSchema$1 = z
758
754
  id: propertyKeySchema$1,
759
755
  // Can be replace with z.templateLiteral when upgrading to zod v4
760
756
  query: z.string().refine((s) => BREAKPOINT_QUERY_REGEX$1.test(s)),
761
- previewSize: z.string().optional(),
757
+ previewSize: z.string(),
762
758
  displayName: z.string(),
763
759
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
764
760
  })
@@ -812,25 +808,6 @@ z.object({
812
808
  usedComponents: localeWrapper$1(UsedComponentsSchema$1).optional(),
813
809
  });
814
810
 
815
- function treeVisit$1$1(initialNode, onNode) {
816
- const _treeVisit = (currentNode) => {
817
- const children = [...currentNode.children];
818
- onNode(currentNode);
819
- for (const child of children) {
820
- _treeVisit(child);
821
- }
822
- };
823
- if (Array.isArray(initialNode)) {
824
- for (const node of initialNode) {
825
- _treeVisit(node);
826
- }
827
- }
828
- else {
829
- _treeVisit(initialNode);
830
- }
831
- }
832
-
833
- const MAX_ALLOWED_PATHS$1 = 200;
834
811
  const THUMBNAIL_IDS$1 = [
835
812
  'columns',
836
813
  'columnsPlusRight',
@@ -861,17 +838,7 @@ const THUMBNAIL_IDS$1 = [
861
838
  const VariableMappingSchema$1 = z.object({
862
839
  parameterId: propertyKeySchema$1,
863
840
  type: z.literal('ContentTypeMapping'),
864
- pathsByContentType: z
865
- .record(z.string(), z.object({ path: z.string() }))
866
- .superRefine((paths, ctx) => {
867
- const variableId = ctx.path[ctx.path.length - 2];
868
- if (Object.keys(paths).length > MAX_ALLOWED_PATHS$1) {
869
- ctx.addIssue({
870
- code: z.ZodIssueCode.custom,
871
- message: `Too many paths defined for variable mapping with id "${variableId}", maximum allowed is ${MAX_ALLOWED_PATHS$1}`,
872
- });
873
- }
874
- }),
841
+ pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
875
842
  });
876
843
  const PassToNodeSchema$1 = z
877
844
  .object({
@@ -895,10 +862,7 @@ const ParameterDefinitionSchema$1 = z.object({
895
862
  })
896
863
  .optional(),
897
864
  contentTypes: z.array(z.string()).min(1),
898
- passToNodes: z
899
- .array(PassToNodeSchema$1)
900
- .max(1, 'At most one "passToNodes" element is allowed per parameter definition.')
901
- .optional(), // we might change this to be empty array for native parameter definitions, that's why we don't use .length(1)
865
+ passToNodes: z.array(PassToNodeSchema$1).optional(),
902
866
  });
903
867
  const ParameterDefinitionsSchema$1 = z.record(propertyKeySchema$1, ParameterDefinitionSchema$1);
904
868
  const VariableMappingsSchema$1 = z.record(propertyKeySchema$1, VariableMappingSchema$1);
@@ -919,108 +883,14 @@ const ComponentSettingsSchema$1 = z
919
883
  category: z.string().max(50, 'Category must contain at most 50 characters').optional(),
920
884
  prebindingDefinitions: z.array(PrebindingDefinitionSchema$1).length(1).optional(),
921
885
  })
922
- .strict()
923
- .superRefine((componentSettings, ctx) => {
924
- const { variableDefinitions, prebindingDefinitions } = componentSettings;
925
- if (!prebindingDefinitions || prebindingDefinitions.length === 0) {
926
- return;
927
- }
928
- const { parameterDefinitions, variableMappings, allowedVariableOverrides } = prebindingDefinitions[0];
929
- validateAtMostOneNativeParameterDefinition$1(parameterDefinitions, ctx);
930
- validateNoOverlapBetweenMappingAndOverrides$1(variableMappings, allowedVariableOverrides, ctx);
931
- validateMappingsAgainstVariableDefinitions$1(variableMappings, allowedVariableOverrides, variableDefinitions, ctx);
932
- validateMappingsAgainstParameterDefinitions$1(variableMappings, parameterDefinitions, ctx);
933
- });
934
- z
935
- .object({
886
+ .strict();
887
+ z.object({
936
888
  componentTree: localeWrapper$1(ComponentTreeSchema$1),
937
889
  dataSource: localeWrapper$1(DataSourceSchema$1),
938
890
  unboundValues: localeWrapper$1(UnboundValuesSchema$1),
939
891
  usedComponents: localeWrapper$1(UsedComponentsSchema$1).optional(),
940
892
  componentSettings: localeWrapper$1(ComponentSettingsSchema$1),
941
- })
942
- .superRefine((patternFields, ctx) => {
943
- const { componentTree, componentSettings } = patternFields;
944
- // values at this point are wrapped under locale code
945
- const nonLocalisedComponentTree = Object.values(componentTree)[0];
946
- const nonLocalisedComponentSettings = Object.values(componentSettings)[0];
947
- if (!nonLocalisedComponentSettings || !nonLocalisedComponentTree) {
948
- return;
949
- }
950
- validatePassToNodes$1(nonLocalisedComponentTree.children || [], nonLocalisedComponentSettings || {}, ctx);
951
893
  });
952
- const validateAtMostOneNativeParameterDefinition$1 = (parameterDefinitions, ctx) => {
953
- const nativeParamDefinitions = Object.values(parameterDefinitions).filter((paramDefinition) => !(paramDefinition.passToNodes && paramDefinition.passToNodes.length > 0));
954
- if (nativeParamDefinitions.length > 1) {
955
- ctx.addIssue({
956
- code: z.ZodIssueCode.custom,
957
- message: `Only one native parameter definition (parameter definition without passToNodes) is allowed per prebinding definition.`,
958
- });
959
- }
960
- };
961
- const validateNoOverlapBetweenMappingAndOverrides$1 = (variableMappings, allowedVariableOverrides, ctx) => {
962
- const variableMappingKeys = Object.keys(variableMappings || {});
963
- const overridesSet = new Set(allowedVariableOverrides || []);
964
- const overlap = variableMappingKeys.filter((key) => overridesSet.has(key));
965
- if (overlap.length > 0) {
966
- ctx.addIssue({
967
- code: z.ZodIssueCode.custom,
968
- message: `Found both variable mapping and allowed override for the following keys: ${overlap.map((key) => `"${key}"`).join(', ')}.`,
969
- });
970
- }
971
- };
972
- const validateMappingsAgainstVariableDefinitions$1 = (variableMappings, allowedVariableOverrides, variableDefinitions, ctx) => {
973
- const nonDesignVariableDefinitionKeys = Object.entries(variableDefinitions)
974
- .filter(([_, def]) => def.group !== 'style')
975
- .map(([key]) => key);
976
- const variableMappingKeys = Object.keys(variableMappings || {});
977
- const allKeys = [...variableMappingKeys, ...(allowedVariableOverrides || [])];
978
- const invalidMappings = allKeys.filter((key) => !nonDesignVariableDefinitionKeys.includes(key));
979
- if (invalidMappings.length > 0) {
980
- ctx.addIssue({
981
- code: z.ZodIssueCode.custom,
982
- message: `The following variable mappings or overrides are missing from the variable definitions: ${invalidMappings.map((key) => `"${key}"`).join(', ')}.`,
983
- });
984
- }
985
- };
986
- const validateMappingsAgainstParameterDefinitions$1 = (variableMappings, parameterDefinitions, ctx) => {
987
- const parameterDefinitionKeys = Object.keys(parameterDefinitions || {});
988
- for (const [mappingKey, mappingValue] of Object.entries(variableMappings || {})) {
989
- if (!parameterDefinitionKeys.includes(mappingValue.parameterId)) {
990
- ctx.addIssue({
991
- code: z.ZodIssueCode.custom,
992
- message: `The variable mapping with id "${mappingKey}" references a non-existing parameterId "${mappingValue.parameterId}".`,
993
- });
994
- }
995
- }
996
- };
997
- const validatePassToNodes$1 = (rootChildren, componentSettings, ctx) => {
998
- if (!componentSettings.prebindingDefinitions ||
999
- componentSettings.prebindingDefinitions.length === 0) {
1000
- return;
1001
- }
1002
- const { parameterDefinitions } = componentSettings.prebindingDefinitions[0];
1003
- let nodeIds = new Set();
1004
- for (const paramDef of Object.values(parameterDefinitions || {})) {
1005
- paramDef.passToNodes?.forEach((n) => nodeIds.add(n.nodeId));
1006
- }
1007
- treeVisit$1$1(rootChildren, (node) => {
1008
- if (!node.id)
1009
- return;
1010
- if (nodeIds.has(node.id)) {
1011
- nodeIds.delete(node.id);
1012
- }
1013
- });
1014
- if (nodeIds.size > 0) {
1015
- const stringifiedNodeIds = Array.from(nodeIds)
1016
- .map((id) => `"${id}"`)
1017
- .join(', ');
1018
- ctx.addIssue({
1019
- code: z.ZodIssueCode.custom,
1020
- message: `The following node IDs referenced in passToNodes are not present in the component tree: ${stringifiedNodeIds}.`,
1021
- });
1022
- }
1023
- };
1024
894
 
1025
895
  z
1026
896
  .object({
@@ -1687,9 +1557,7 @@ const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.inclu
1687
1557
  // cfWrapColumns & cfWrapColumnsCount are no real style attributes as they are handled on the editor side
1688
1558
  const propsToRemove = ['cfSsrClassName', 'cfWrapColumns', 'cfWrapColumnsCount'];
1689
1559
  const sanitizeNodeProps = (nodeProps) => {
1690
- const keysToRemove = [...stylesToRemove, ...propsToRemove];
1691
- const sanitizedProps = Object.fromEntries(Object.entries(nodeProps).filter(([key]) => !keysToRemove.includes(key)));
1692
- return sanitizedProps;
1560
+ return omit(nodeProps, stylesToRemove, propsToRemove);
1693
1561
  };
1694
1562
 
1695
1563
  /** Turn the visibility value into a style object that can be used for inline styles in React */
@@ -2168,7 +2036,7 @@ function getArrayValue(entryOrAsset, path, entityStore) {
2168
2036
  }
2169
2037
  const fieldName = path.split('/').slice(2, -1);
2170
2038
  const arrayValue = get$1(entryOrAsset, fieldName);
2171
- if (!Array.isArray(arrayValue)) {
2039
+ if (!isArray(arrayValue)) {
2172
2040
  debug$1.warn(`[experiences-core::getArrayValue] A field '${fieldName}' of an entity was bound to an Array variable. Expected value of that field to be an array, but got: ${JSON.stringify(arrayValue)}`, { entity: entryOrAsset });
2173
2041
  return;
2174
2042
  }
@@ -2285,19 +2153,6 @@ function getTargetValueInPixels(targetWidthObject) {
2285
2153
  return targetWidthObject.value;
2286
2154
  }
2287
2155
  }
2288
- /**
2289
- * Creates a component definition for an assembly. As all assemblies use the same definition in the SDK,
2290
- * all should be registered via this function.
2291
- */
2292
- const createAssemblyDefinition = (definitionId) => {
2293
- return {
2294
- id: definitionId,
2295
- name: 'Component',
2296
- variables: {},
2297
- children: true,
2298
- category: ASSEMBLY_DEFAULT_CATEGORY,
2299
- };
2300
- };
2301
2156
 
2302
2157
  class ParseError extends Error {
2303
2158
  constructor(message) {
@@ -3206,9 +3061,7 @@ const INCOMING_EVENTS = {
3206
3061
  /** @deprecated will be removed when dropping backward compatibility for old DND */
3207
3062
  HoverComponent: 'hoverComponent',
3208
3063
  UpdatedEntity: 'updatedEntity',
3209
- /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
3210
3064
  AssembliesAdded: 'assembliesAdded',
3211
- /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
3212
3065
  AssembliesRegistered: 'assembliesRegistered',
3213
3066
  /** @deprecated will be removed when dropping backward compatibility for old DND */
3214
3067
  MouseMove: 'mouseMove',
@@ -3233,6 +3086,7 @@ var StudioCanvasMode$2;
3233
3086
  StudioCanvasMode["NONE"] = "none";
3234
3087
  })(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
3235
3088
  const ASSEMBLY_NODE_TYPE = 'assembly';
3089
+ const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
3236
3090
  const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
3237
3091
  const EMPTY_CONTAINER_SIZE = '80px';
3238
3092
  const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
@@ -3391,7 +3245,34 @@ const useBreakpoints = (breakpoints) => {
3391
3245
  return { resolveDesignValue };
3392
3246
  };
3393
3247
 
3248
+ // Note: During development, the hot reloading might empty this and it
3249
+ // stays empty leading to not rendering assemblies. Ideally, this is
3250
+ // integrated into the state machine to keep track of its state.
3251
+ const assembliesRegistry = new Map([]);
3252
+ const setAssemblies = (assemblies) => {
3253
+ for (const assembly of assemblies) {
3254
+ assembliesRegistry.set(assembly.sys.id, assembly);
3255
+ }
3256
+ };
3394
3257
  const componentRegistry = new Map();
3258
+ const addComponentRegistration = (componentRegistration) => {
3259
+ componentRegistry.set(componentRegistration.definition.id, componentRegistration);
3260
+ };
3261
+ const createAssemblyRegistration = ({ definitionId, definitionName, component, }) => {
3262
+ const componentRegistration = componentRegistry.get(definitionId);
3263
+ if (componentRegistration) {
3264
+ return componentRegistration;
3265
+ }
3266
+ const definition = {
3267
+ id: definitionId,
3268
+ name: definitionName || 'Component',
3269
+ variables: {},
3270
+ children: true,
3271
+ category: ASSEMBLY_DEFAULT_CATEGORY,
3272
+ };
3273
+ addComponentRegistration({ component, definition });
3274
+ return componentRegistry.get(definitionId);
3275
+ };
3395
3276
 
3396
3277
  const useEditorStore = create((set, get) => ({
3397
3278
  dataSource: {},
@@ -3431,278 +3312,6 @@ const useEditorStore = create((set, get) => ({
3431
3312
  },
3432
3313
  }));
3433
3314
 
3434
- function useEditorSubscriber(inMemoryEntitiesStore) {
3435
- const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
3436
- const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
3437
- const setEntitiesFetched = inMemoryEntitiesStore((state) => state.setEntitiesFetched);
3438
- const resetEntityStore = inMemoryEntitiesStore((state) => state.resetEntityStore);
3439
- const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
3440
- updateTree: state.updateTree,
3441
- updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
3442
- }));
3443
- const unboundValues = useEditorStore((state) => state.unboundValues);
3444
- const dataSource = useEditorStore((state) => state.dataSource);
3445
- const setLocale = useEditorStore((state) => state.setLocale);
3446
- const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
3447
- const setDataSource = useEditorStore((state) => state.setDataSource);
3448
- const reloadApp = () => {
3449
- sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
3450
- // Wait a moment to ensure that the message was sent
3451
- setTimeout(() => {
3452
- // Received a hot reload message from webpack dev server -> reload the canvas
3453
- window.location.reload();
3454
- }, 50);
3455
- };
3456
- useEffect(() => {
3457
- sendMessage(OUTGOING_EVENTS.RequestComponentTreeUpdate, undefined);
3458
- }, []);
3459
- /**
3460
- * Fills up entityStore with entities from newDataSource and from the tree.
3461
- * Also manages "entity status" variables (areEntitiesFetched, isFetchingEntities)
3462
- */
3463
- const fetchMissingEntities = useCallback(async (entityStore, newDataSource, tree) => {
3464
- // if we realize that there's nothing missing and nothing to fill-fetch before we do any async call,
3465
- // then we can simply return and not lock the EntityStore at all.
3466
- const startFetching = () => {
3467
- setEntitiesFetched(false);
3468
- };
3469
- const endFetching = () => {
3470
- setEntitiesFetched(true);
3471
- };
3472
- // Prepare L1 entities and deepReferences
3473
- const entityLinksL1 = Object.values(newDataSource);
3474
- /**
3475
- * Checks only for _missing_ L1 entities
3476
- * WARNING: Does NOT check for entity staleness/versions. If an entity is stale, it will NOT be considered missing.
3477
- * If ExperienceBuilder wants to update stale entities, it should post `▼UPDATED_ENTITY` message to SDK.
3478
- */
3479
- const isMissingL1Entities = (entityLinks) => {
3480
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinks);
3481
- return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
3482
- };
3483
- /**
3484
- * PRECONDITION: all L1 entities are fetched
3485
- */
3486
- const isMissingL2Entities = (deepReferences) => {
3487
- const referentLinks = deepReferences
3488
- .map((deepReference) => deepReference.extractReferent(entityStore))
3489
- .filter(isLink$1);
3490
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
3491
- return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
3492
- };
3493
- /**
3494
- * POST_CONDITION: entityStore is has all L1 entities (aka headEntities)
3495
- */
3496
- const fillupL1 = async ({ entityLinksL1, }) => {
3497
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinksL1);
3498
- await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
3499
- };
3500
- /**
3501
- * PRECONDITION: all L1 entites are fetched
3502
- */
3503
- const fillupL2 = async ({ deepReferences }) => {
3504
- const referentLinks = deepReferences
3505
- .map((deepReference) => deepReference.extractReferent(entityStore))
3506
- .filter(isLink$1);
3507
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
3508
- await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
3509
- };
3510
- try {
3511
- if (isMissingL1Entities(entityLinksL1)) {
3512
- startFetching();
3513
- await fillupL1({ entityLinksL1 });
3514
- }
3515
- const deepReferences = gatherDeepReferencesFromTree(tree.root, newDataSource, entityStore.getEntityFromLink.bind(entityStore));
3516
- if (isMissingL2Entities(deepReferences)) {
3517
- startFetching();
3518
- await fillupL2({ deepReferences });
3519
- }
3520
- }
3521
- catch (error) {
3522
- debug$1.error('[experiences-visual-editor-react::useEditorSubscriber] Failed fetching entities', { error });
3523
- throw error; // TODO: The original catch didn't let's rethrow; for the moment throw to see if we have any errors
3524
- }
3525
- finally {
3526
- endFetching();
3527
- }
3528
- }, [setEntitiesFetched]);
3529
- useEffect(() => {
3530
- const onMessage = async (event) => {
3531
- let reason;
3532
- if ((reason = doesMismatchMessageSchema(event))) {
3533
- if (event.origin.startsWith('http://localhost') &&
3534
- `${event.data}`.includes('webpackHotUpdate')) {
3535
- reloadApp();
3536
- }
3537
- else {
3538
- debug$1.warn(`[experiences-visual-editor-react::onMessage] Ignoring alien incoming message from origin [${event.origin}], due to: [${reason}]`, event);
3539
- }
3540
- return;
3541
- }
3542
- const eventData = tryParseMessage(event);
3543
- debug$1.debug(`[experiences-visual-editor-react::onMessage] Received message [${eventData.eventType}]`, eventData);
3544
- if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
3545
- // Expected message: This message is handled in the EntityStore to store fetched entities
3546
- return;
3547
- }
3548
- switch (eventData.eventType) {
3549
- case INCOMING_EVENTS.ExperienceUpdated: {
3550
- const { tree, locale, changedNode, changedValueType } = eventData.payload;
3551
- let newEntityStore = entityStore;
3552
- if (entityStore.locale !== locale) {
3553
- newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
3554
- setLocale(locale);
3555
- resetEntityStore(newEntityStore);
3556
- }
3557
- // Below are mutually exclusive cases
3558
- if (changedNode) {
3559
- /**
3560
- * On single node updates, we want to skip the process of getting the data (datasource and unbound values)
3561
- * from tree. Since we know the updated node, we can skip that recursion everytime the tree updates and
3562
- * just update the relevant data we need from the relevant node.
3563
- *
3564
- * We still update the tree here so we don't have a stale "tree"
3565
- */
3566
- if (changedValueType === 'BoundValue') {
3567
- const newDataSource = { ...dataSource, ...changedNode.data.dataSource };
3568
- setDataSource(newDataSource);
3569
- await fetchMissingEntities(newEntityStore, newDataSource, tree);
3570
- }
3571
- else if (changedValueType === 'UnboundValue') {
3572
- setUnboundValues({
3573
- ...unboundValues,
3574
- ...changedNode.data.unboundValues,
3575
- });
3576
- }
3577
- }
3578
- else {
3579
- const { dataSource, unboundValues } = getDataFromTree(tree);
3580
- setDataSource(dataSource);
3581
- setUnboundValues(unboundValues);
3582
- await fetchMissingEntities(newEntityStore, dataSource, tree);
3583
- }
3584
- // Update the tree when all necessary data is fetched and ready for rendering.
3585
- updateTree(tree);
3586
- break;
3587
- }
3588
- case INCOMING_EVENTS.AssembliesRegistered: {
3589
- // Not necessary anymore since `patternResolution` which was introduced in 2024.
3590
- break;
3591
- }
3592
- case INCOMING_EVENTS.AssembliesAdded: {
3593
- // Not necessary anymore since `patternResolution` which was introduced in 2024.
3594
- break;
3595
- }
3596
- case INCOMING_EVENTS.UpdatedEntity: {
3597
- const { entity: updatedEntity, shouldRerender } = eventData.payload;
3598
- if (updatedEntity) {
3599
- const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
3600
- const didEntityChange = storedEntity?.sys.version !== updatedEntity.sys.version;
3601
- entityStore.updateEntity(updatedEntity);
3602
- // We traverse the whole tree, so this is a opt-in feature to only use it when required.
3603
- if (shouldRerender && didEntityChange) {
3604
- updateNodesByUpdatedEntity(updatedEntity.sys.id);
3605
- }
3606
- }
3607
- break;
3608
- }
3609
- case INCOMING_EVENTS.RequestEditorMode: {
3610
- break;
3611
- }
3612
- default: {
3613
- const knownEvents = Object.values(INCOMING_EVENTS);
3614
- const isDeprecatedMessage = knownEvents.includes(eventData.eventType);
3615
- if (!isDeprecatedMessage) {
3616
- debug$1.error(`[experiences-visual-editor-react::onMessage] Logic error, unsupported eventType: [${eventData.eventType}]`);
3617
- }
3618
- }
3619
- }
3620
- };
3621
- window.addEventListener('message', onMessage);
3622
- return () => {
3623
- window.removeEventListener('message', onMessage);
3624
- };
3625
- }, [
3626
- entityStore,
3627
- setDataSource,
3628
- setLocale,
3629
- dataSource,
3630
- areEntitiesFetched,
3631
- fetchMissingEntities,
3632
- setUnboundValues,
3633
- unboundValues,
3634
- updateTree,
3635
- updateNodesByUpdatedEntity,
3636
- resetEntityStore,
3637
- ]);
3638
- }
3639
-
3640
- const CircularDependencyErrorPlaceholder = ({ wrappingPatternIds, ...props }) => {
3641
- return (React.createElement("div", { ...props, "data-cf-node-error": "circular-pattern-dependency", style: {
3642
- border: '1px solid red',
3643
- background: 'rgba(255, 0, 0, 0.1)',
3644
- padding: '1rem 1rem 0 1rem',
3645
- width: '100%',
3646
- height: '100%',
3647
- } },
3648
- "Circular usage of patterns detected:",
3649
- React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
3650
- const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
3651
- const entry = inMemoryEntities.maybeResolveLink(entryLink);
3652
- const entryTitle = entry?.fields?.title;
3653
- const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
3654
- return React.createElement("li", { key: patternId }, text);
3655
- }))));
3656
- };
3657
-
3658
- class ImportedComponentError extends Error {
3659
- constructor(message) {
3660
- super(message);
3661
- this.name = 'ImportedComponentError';
3662
- }
3663
- }
3664
- class ExperienceSDKError extends Error {
3665
- constructor(message) {
3666
- super(message);
3667
- this.name = 'ExperienceSDKError';
3668
- }
3669
- }
3670
- class ImportedComponentErrorBoundary extends React.Component {
3671
- componentDidCatch(error, _errorInfo) {
3672
- if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
3673
- // This error was already handled by a nested error boundary and should be passed upwards
3674
- // We have to do this as we wrap every component on every layer with this error boundary and
3675
- // thus an error deep in the tree bubbles through many layers of error boundaries.
3676
- throw error;
3677
- }
3678
- // Differentiate between custom and SDK-provided components for error tracking
3679
- const ErrorClass = isContentfulComponent(this.props.componentId)
3680
- ? ExperienceSDKError
3681
- : ImportedComponentError;
3682
- const err = new ErrorClass(error.message);
3683
- err.stack = error.stack;
3684
- throw err;
3685
- }
3686
- render() {
3687
- return this.props.children;
3688
- }
3689
- }
3690
-
3691
- const MissingComponentPlaceholder = ({ blockId }) => {
3692
- return (React.createElement("div", { style: {
3693
- border: '1px solid red',
3694
- width: '100%',
3695
- height: '100%',
3696
- } },
3697
- "Missing component '",
3698
- blockId,
3699
- "'"));
3700
- };
3701
-
3702
- var css_248z$a = ".EditorBlock-module_emptySlot__za-Bi {\n min-height: 80px;\n min-width: 80px;\n}\n";
3703
- var styles$1 = {"emptySlot":"EditorBlock-module_emptySlot__za-Bi"};
3704
- styleInject(css_248z$a);
3705
-
3706
3315
  var css_248z$8 = ":root{--cf-color-white:#fff;--cf-color-black:#000;--cf-color-gray100:#f7f9fa;--cf-color-gray400:#aec1cc;--cf-color-gray400-rgb:174,193,204;--cf-spacing-0:0rem;--cf-spacing-1:0.125rem;--cf-spacing-2:0.25rem;--cf-spacing-3:0.375rem;--cf-spacing-4:0.5rem;--cf-spacing-5:0.625rem;--cf-spacing-6:0.75rem;--cf-spacing-7:0.875rem;--cf-spacing-8:1rem;--cf-spacing-9:1.25rem;--cf-spacing-10:1.5rem;--cf-spacing-11:1.75rem;--cf-spacing-12:2rem;--cf-spacing-13:2.25rem;--cf-text-xs:0.75rem;--cf-text-sm:0.875rem;--cf-text-base:1rem;--cf-text-lg:1.125rem;--cf-text-xl:1.25rem;--cf-text-2xl:1.5rem;--cf-text-3xl:2rem;--cf-text-4xl:2.75rem;--cf-font-light:300;--cf-font-normal:400;--cf-font-medium:500;--cf-font-semibold:600;--cf-font-bold:700;--cf-font-extra-bold:800;--cf-font-black:900;--cf-border-radius-none:0px;--cf-border-radius-sm:0.125rem;--cf-border-radius:0.25rem;--cf-border-radius-md:0.375rem;--cf-border-radius-lg:0.5rem;--cf-border-radius-xl:0.75rem;--cf-border-radius-2xl:1rem;--cf-border-radius-3xl:1.5rem;--cf-border-radius-full:9999px;--cf-max-width-full:100%;--cf-button-bg:var(--cf-color-black);--cf-button-color:var(--cf-color-white);--cf-text-color:var(--cf-color-black)}*{box-sizing:border-box}";
3707
3316
  styleInject(css_248z$8);
3708
3317
 
@@ -3996,7 +3605,7 @@ const BreakpointSchema = z
3996
3605
  id: propertyKeySchema,
3997
3606
  // Can be replace with z.templateLiteral when upgrading to zod v4
3998
3607
  query: z.string().refine((s) => BREAKPOINT_QUERY_REGEX.test(s)),
3999
- previewSize: z.string().optional(),
3608
+ previewSize: z.string(),
4000
3609
  displayName: z.string(),
4001
3610
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
4002
3611
  })
@@ -4050,25 +3659,6 @@ z.object({
4050
3659
  usedComponents: localeWrapper(UsedComponentsSchema).optional(),
4051
3660
  });
4052
3661
 
4053
- function treeVisit$1(initialNode, onNode) {
4054
- const _treeVisit = (currentNode) => {
4055
- const children = [...currentNode.children];
4056
- onNode(currentNode);
4057
- for (const child of children) {
4058
- _treeVisit(child);
4059
- }
4060
- };
4061
- if (Array.isArray(initialNode)) {
4062
- for (const node of initialNode) {
4063
- _treeVisit(node);
4064
- }
4065
- }
4066
- else {
4067
- _treeVisit(initialNode);
4068
- }
4069
- }
4070
-
4071
- const MAX_ALLOWED_PATHS = 200;
4072
3662
  const THUMBNAIL_IDS = [
4073
3663
  'columns',
4074
3664
  'columnsPlusRight',
@@ -4099,17 +3689,7 @@ const THUMBNAIL_IDS = [
4099
3689
  const VariableMappingSchema = z.object({
4100
3690
  parameterId: propertyKeySchema,
4101
3691
  type: z.literal('ContentTypeMapping'),
4102
- pathsByContentType: z
4103
- .record(z.string(), z.object({ path: z.string() }))
4104
- .superRefine((paths, ctx) => {
4105
- const variableId = ctx.path[ctx.path.length - 2];
4106
- if (Object.keys(paths).length > MAX_ALLOWED_PATHS) {
4107
- ctx.addIssue({
4108
- code: z.ZodIssueCode.custom,
4109
- message: `Too many paths defined for variable mapping with id "${variableId}", maximum allowed is ${MAX_ALLOWED_PATHS}`,
4110
- });
4111
- }
4112
- }),
3692
+ pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
4113
3693
  });
4114
3694
  const PassToNodeSchema = z
4115
3695
  .object({
@@ -4133,10 +3713,7 @@ const ParameterDefinitionSchema = z.object({
4133
3713
  })
4134
3714
  .optional(),
4135
3715
  contentTypes: z.array(z.string()).min(1),
4136
- passToNodes: z
4137
- .array(PassToNodeSchema)
4138
- .max(1, 'At most one "passToNodes" element is allowed per parameter definition.')
4139
- .optional(), // we might change this to be empty array for native parameter definitions, that's why we don't use .length(1)
3716
+ passToNodes: z.array(PassToNodeSchema).optional(),
4140
3717
  });
4141
3718
  const ParameterDefinitionsSchema = z.record(propertyKeySchema, ParameterDefinitionSchema);
4142
3719
  const VariableMappingsSchema = z.record(propertyKeySchema, VariableMappingSchema);
@@ -4157,108 +3734,14 @@ const ComponentSettingsSchema = z
4157
3734
  category: z.string().max(50, 'Category must contain at most 50 characters').optional(),
4158
3735
  prebindingDefinitions: z.array(PrebindingDefinitionSchema).length(1).optional(),
4159
3736
  })
4160
- .strict()
4161
- .superRefine((componentSettings, ctx) => {
4162
- const { variableDefinitions, prebindingDefinitions } = componentSettings;
4163
- if (!prebindingDefinitions || prebindingDefinitions.length === 0) {
4164
- return;
4165
- }
4166
- const { parameterDefinitions, variableMappings, allowedVariableOverrides } = prebindingDefinitions[0];
4167
- validateAtMostOneNativeParameterDefinition(parameterDefinitions, ctx);
4168
- validateNoOverlapBetweenMappingAndOverrides(variableMappings, allowedVariableOverrides, ctx);
4169
- validateMappingsAgainstVariableDefinitions(variableMappings, allowedVariableOverrides, variableDefinitions, ctx);
4170
- validateMappingsAgainstParameterDefinitions(variableMappings, parameterDefinitions, ctx);
4171
- });
4172
- z
4173
- .object({
3737
+ .strict();
3738
+ z.object({
4174
3739
  componentTree: localeWrapper(ComponentTreeSchema),
4175
3740
  dataSource: localeWrapper(DataSourceSchema),
4176
3741
  unboundValues: localeWrapper(UnboundValuesSchema),
4177
3742
  usedComponents: localeWrapper(UsedComponentsSchema).optional(),
4178
3743
  componentSettings: localeWrapper(ComponentSettingsSchema),
4179
- })
4180
- .superRefine((patternFields, ctx) => {
4181
- const { componentTree, componentSettings } = patternFields;
4182
- // values at this point are wrapped under locale code
4183
- const nonLocalisedComponentTree = Object.values(componentTree)[0];
4184
- const nonLocalisedComponentSettings = Object.values(componentSettings)[0];
4185
- if (!nonLocalisedComponentSettings || !nonLocalisedComponentTree) {
4186
- return;
4187
- }
4188
- validatePassToNodes(nonLocalisedComponentTree.children || [], nonLocalisedComponentSettings || {}, ctx);
4189
3744
  });
4190
- const validateAtMostOneNativeParameterDefinition = (parameterDefinitions, ctx) => {
4191
- const nativeParamDefinitions = Object.values(parameterDefinitions).filter((paramDefinition) => !(paramDefinition.passToNodes && paramDefinition.passToNodes.length > 0));
4192
- if (nativeParamDefinitions.length > 1) {
4193
- ctx.addIssue({
4194
- code: z.ZodIssueCode.custom,
4195
- message: `Only one native parameter definition (parameter definition without passToNodes) is allowed per prebinding definition.`,
4196
- });
4197
- }
4198
- };
4199
- const validateNoOverlapBetweenMappingAndOverrides = (variableMappings, allowedVariableOverrides, ctx) => {
4200
- const variableMappingKeys = Object.keys(variableMappings || {});
4201
- const overridesSet = new Set(allowedVariableOverrides || []);
4202
- const overlap = variableMappingKeys.filter((key) => overridesSet.has(key));
4203
- if (overlap.length > 0) {
4204
- ctx.addIssue({
4205
- code: z.ZodIssueCode.custom,
4206
- message: `Found both variable mapping and allowed override for the following keys: ${overlap.map((key) => `"${key}"`).join(', ')}.`,
4207
- });
4208
- }
4209
- };
4210
- const validateMappingsAgainstVariableDefinitions = (variableMappings, allowedVariableOverrides, variableDefinitions, ctx) => {
4211
- const nonDesignVariableDefinitionKeys = Object.entries(variableDefinitions)
4212
- .filter(([_, def]) => def.group !== 'style')
4213
- .map(([key]) => key);
4214
- const variableMappingKeys = Object.keys(variableMappings || {});
4215
- const allKeys = [...variableMappingKeys, ...(allowedVariableOverrides || [])];
4216
- const invalidMappings = allKeys.filter((key) => !nonDesignVariableDefinitionKeys.includes(key));
4217
- if (invalidMappings.length > 0) {
4218
- ctx.addIssue({
4219
- code: z.ZodIssueCode.custom,
4220
- message: `The following variable mappings or overrides are missing from the variable definitions: ${invalidMappings.map((key) => `"${key}"`).join(', ')}.`,
4221
- });
4222
- }
4223
- };
4224
- const validateMappingsAgainstParameterDefinitions = (variableMappings, parameterDefinitions, ctx) => {
4225
- const parameterDefinitionKeys = Object.keys(parameterDefinitions || {});
4226
- for (const [mappingKey, mappingValue] of Object.entries(variableMappings || {})) {
4227
- if (!parameterDefinitionKeys.includes(mappingValue.parameterId)) {
4228
- ctx.addIssue({
4229
- code: z.ZodIssueCode.custom,
4230
- message: `The variable mapping with id "${mappingKey}" references a non-existing parameterId "${mappingValue.parameterId}".`,
4231
- });
4232
- }
4233
- }
4234
- };
4235
- const validatePassToNodes = (rootChildren, componentSettings, ctx) => {
4236
- if (!componentSettings.prebindingDefinitions ||
4237
- componentSettings.prebindingDefinitions.length === 0) {
4238
- return;
4239
- }
4240
- const { parameterDefinitions } = componentSettings.prebindingDefinitions[0];
4241
- let nodeIds = new Set();
4242
- for (const paramDef of Object.values(parameterDefinitions || {})) {
4243
- paramDef.passToNodes?.forEach((n) => nodeIds.add(n.nodeId));
4244
- }
4245
- treeVisit$1(rootChildren, (node) => {
4246
- if (!node.id)
4247
- return;
4248
- if (nodeIds.has(node.id)) {
4249
- nodeIds.delete(node.id);
4250
- }
4251
- });
4252
- if (nodeIds.size > 0) {
4253
- const stringifiedNodeIds = Array.from(nodeIds)
4254
- .map((id) => `"${id}"`)
4255
- .join(', ');
4256
- ctx.addIssue({
4257
- code: z.ZodIssueCode.custom,
4258
- message: `The following node IDs referenced in passToNodes are not present in the component tree: ${stringifiedNodeIds}.`,
4259
- });
4260
- }
4261
- };
4262
3745
 
4263
3746
  z
4264
3747
  .object({
@@ -4910,8 +4393,8 @@ var VisualEditorMode;
4910
4393
  VisualEditorMode["InjectScript"] = "injectScript";
4911
4394
  })(VisualEditorMode || (VisualEditorMode = {}));
4912
4395
 
4913
- var css_248z$2 = ".contentful-container{display:flex;pointer-events:all;position:relative}.contentful-container::-webkit-scrollbar{display:none}.cf-container-wrapper{position:relative;width:100%}.contentful-container:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.contentful-section-label:after{content:\"Section\"}.contentful-container-label:after{content:\"Container\"}.contentful-container-link,.contentful-container-link:active,.contentful-container-link:focus-visible,.contentful-container-link:hover,.contentful-container-link:read-write,.contentful-container-link:visited{color:inherit;outline:unset;text-decoration:unset}";
4914
- styleInject(css_248z$2);
4396
+ var css_248z$2$1 = ".contentful-container{display:flex;pointer-events:all;position:relative}.contentful-container::-webkit-scrollbar{display:none}.cf-container-wrapper{position:relative;width:100%}.contentful-container:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.contentful-section-label:after{content:\"Section\"}.contentful-container-label:after{content:\"Container\"}.contentful-container-link,.contentful-container-link:active,.contentful-container-link:focus-visible,.contentful-container-link:hover,.contentful-container-link:read-write,.contentful-container-link:visited{color:inherit;outline:unset;text-decoration:unset}";
4397
+ styleInject(css_248z$2$1);
4915
4398
 
4916
4399
  const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave, onMouseDown, onClick, flex, flexBasis, flexShrink, flexDirection, gap, justifyContent, justifyItems, justifySelf, alignItems, alignSelf, alignContent, order, flexWrap, flexGrow, className, cssStyles, ...props }, ref) => {
4917
4400
  return (React.createElement("div", { id: id, ref: ref, style: {
@@ -4948,16 +4431,315 @@ const Assembly = (props) => {
4948
4431
  return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
4949
4432
  };
4950
4433
 
4434
+ function useEditorSubscriber(inMemoryEntitiesStore) {
4435
+ const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
4436
+ const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
4437
+ const setEntitiesFetched = inMemoryEntitiesStore((state) => state.setEntitiesFetched);
4438
+ const resetEntityStore = inMemoryEntitiesStore((state) => state.resetEntityStore);
4439
+ const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
4440
+ updateTree: state.updateTree,
4441
+ updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
4442
+ }));
4443
+ const unboundValues = useEditorStore((state) => state.unboundValues);
4444
+ const dataSource = useEditorStore((state) => state.dataSource);
4445
+ const setLocale = useEditorStore((state) => state.setLocale);
4446
+ const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
4447
+ const setDataSource = useEditorStore((state) => state.setDataSource);
4448
+ const reloadApp = () => {
4449
+ sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
4450
+ // Wait a moment to ensure that the message was sent
4451
+ setTimeout(() => {
4452
+ // Received a hot reload message from webpack dev server -> reload the canvas
4453
+ window.location.reload();
4454
+ }, 50);
4455
+ };
4456
+ useEffect(() => {
4457
+ sendMessage(OUTGOING_EVENTS.RequestComponentTreeUpdate, undefined);
4458
+ }, []);
4459
+ /**
4460
+ * Fills up entityStore with entities from newDataSource and from the tree.
4461
+ * Also manages "entity status" variables (areEntitiesFetched, isFetchingEntities)
4462
+ */
4463
+ const fetchMissingEntities = useCallback(async (entityStore, newDataSource, tree) => {
4464
+ // if we realize that there's nothing missing and nothing to fill-fetch before we do any async call,
4465
+ // then we can simply return and not lock the EntityStore at all.
4466
+ const startFetching = () => {
4467
+ setEntitiesFetched(false);
4468
+ };
4469
+ const endFetching = () => {
4470
+ setEntitiesFetched(true);
4471
+ };
4472
+ // Prepare L1 entities and deepReferences
4473
+ const entityLinksL1 = [
4474
+ ...Object.values(newDataSource),
4475
+ ...assembliesRegistry.values(), // we count assemblies here as "L1 entities", for convenience. Even though they're not headEntities.
4476
+ ];
4477
+ /**
4478
+ * Checks only for _missing_ L1 entities
4479
+ * WARNING: Does NOT check for entity staleness/versions. If an entity is stale, it will NOT be considered missing.
4480
+ * If ExperienceBuilder wants to update stale entities, it should post `▼UPDATED_ENTITY` message to SDK.
4481
+ */
4482
+ const isMissingL1Entities = (entityLinks) => {
4483
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinks);
4484
+ return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
4485
+ };
4486
+ /**
4487
+ * PRECONDITION: all L1 entities are fetched
4488
+ */
4489
+ const isMissingL2Entities = (deepReferences) => {
4490
+ const referentLinks = deepReferences
4491
+ .map((deepReference) => deepReference.extractReferent(entityStore))
4492
+ .filter(isLink$1);
4493
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4494
+ return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
4495
+ };
4496
+ /**
4497
+ * POST_CONDITION: entityStore is has all L1 entities (aka headEntities)
4498
+ */
4499
+ const fillupL1 = async ({ entityLinksL1, }) => {
4500
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinksL1);
4501
+ await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
4502
+ };
4503
+ /**
4504
+ * PRECONDITION: all L1 entites are fetched
4505
+ */
4506
+ const fillupL2 = async ({ deepReferences }) => {
4507
+ const referentLinks = deepReferences
4508
+ .map((deepReference) => deepReference.extractReferent(entityStore))
4509
+ .filter(isLink$1);
4510
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4511
+ await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
4512
+ };
4513
+ try {
4514
+ if (isMissingL1Entities(entityLinksL1)) {
4515
+ startFetching();
4516
+ await fillupL1({ entityLinksL1 });
4517
+ }
4518
+ const deepReferences = gatherDeepReferencesFromTree(tree.root, newDataSource, entityStore.getEntityFromLink.bind(entityStore));
4519
+ if (isMissingL2Entities(deepReferences)) {
4520
+ startFetching();
4521
+ await fillupL2({ deepReferences });
4522
+ }
4523
+ }
4524
+ catch (error) {
4525
+ debug$1.error('[experiences-visual-editor-react::useEditorSubscriber] Failed fetching entities', { error });
4526
+ throw error; // TODO: The original catch didn't let's rethrow; for the moment throw to see if we have any errors
4527
+ }
4528
+ finally {
4529
+ endFetching();
4530
+ }
4531
+ }, [setEntitiesFetched /* setFetchingEntities, assembliesRegistry */]);
4532
+ useEffect(() => {
4533
+ const onMessage = async (event) => {
4534
+ let reason;
4535
+ if ((reason = doesMismatchMessageSchema(event))) {
4536
+ if (event.origin.startsWith('http://localhost') &&
4537
+ `${event.data}`.includes('webpackHotUpdate')) {
4538
+ reloadApp();
4539
+ }
4540
+ else {
4541
+ debug$1.warn(`[experiences-visual-editor-react::onMessage] Ignoring alien incoming message from origin [${event.origin}], due to: [${reason}]`, event);
4542
+ }
4543
+ return;
4544
+ }
4545
+ const eventData = tryParseMessage(event);
4546
+ debug$1.debug(`[experiences-visual-editor-react::onMessage] Received message [${eventData.eventType}]`, eventData);
4547
+ if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
4548
+ // Expected message: This message is handled in the EntityStore to store fetched entities
4549
+ return;
4550
+ }
4551
+ switch (eventData.eventType) {
4552
+ case INCOMING_EVENTS.ExperienceUpdated: {
4553
+ const { tree, locale, changedNode, changedValueType, assemblies } = eventData.payload;
4554
+ // Make sure to first store the assemblies before setting the tree and thus triggering a rerender
4555
+ if (assemblies) {
4556
+ setAssemblies(assemblies);
4557
+ // If the assemblyEntry is not yet fetched, this will be done below by
4558
+ // the imperative calls to fetchMissingEntities.
4559
+ }
4560
+ let newEntityStore = entityStore;
4561
+ if (entityStore.locale !== locale) {
4562
+ newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
4563
+ setLocale(locale);
4564
+ resetEntityStore(newEntityStore);
4565
+ }
4566
+ // Below are mutually exclusive cases
4567
+ if (changedNode) {
4568
+ /**
4569
+ * On single node updates, we want to skip the process of getting the data (datasource and unbound values)
4570
+ * from tree. Since we know the updated node, we can skip that recursion everytime the tree updates and
4571
+ * just update the relevant data we need from the relevant node.
4572
+ *
4573
+ * We still update the tree here so we don't have a stale "tree"
4574
+ */
4575
+ if (changedValueType === 'BoundValue') {
4576
+ const newDataSource = { ...dataSource, ...changedNode.data.dataSource };
4577
+ setDataSource(newDataSource);
4578
+ await fetchMissingEntities(newEntityStore, newDataSource, tree);
4579
+ }
4580
+ else if (changedValueType === 'UnboundValue') {
4581
+ setUnboundValues({
4582
+ ...unboundValues,
4583
+ ...changedNode.data.unboundValues,
4584
+ });
4585
+ }
4586
+ }
4587
+ else {
4588
+ const { dataSource, unboundValues } = getDataFromTree(tree);
4589
+ setDataSource(dataSource);
4590
+ setUnboundValues(unboundValues);
4591
+ await fetchMissingEntities(newEntityStore, dataSource, tree);
4592
+ }
4593
+ // Update the tree when all necessary data is fetched and ready for rendering.
4594
+ updateTree(tree);
4595
+ break;
4596
+ }
4597
+ case INCOMING_EVENTS.AssembliesRegistered: {
4598
+ const { assemblies } = eventData.payload;
4599
+ assemblies.forEach((definition) => {
4600
+ addComponentRegistration({
4601
+ component: Assembly,
4602
+ definition,
4603
+ });
4604
+ });
4605
+ break;
4606
+ }
4607
+ case INCOMING_EVENTS.AssembliesAdded: {
4608
+ const { assembly, assemblyDefinition, } = eventData.payload;
4609
+ entityStore.updateEntity(assembly);
4610
+ // Using a Map here to avoid setting state and rerending all existing assemblies when a new assembly is added
4611
+ // TODO: Figure out if we can extend this love to data source and unbound values. Maybe that'll solve the blink
4612
+ // of all bound and unbound values when new values are added
4613
+ assembliesRegistry.set(assembly.sys.id, {
4614
+ sys: { id: assembly.sys.id, linkType: 'Entry', type: 'Link' },
4615
+ });
4616
+ if (assemblyDefinition) {
4617
+ addComponentRegistration({
4618
+ component: Assembly,
4619
+ definition: assemblyDefinition,
4620
+ });
4621
+ }
4622
+ break;
4623
+ }
4624
+ case INCOMING_EVENTS.UpdatedEntity: {
4625
+ const { entity: updatedEntity, shouldRerender } = eventData.payload;
4626
+ if (updatedEntity) {
4627
+ const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
4628
+ const didEntityChange = storedEntity?.sys.version !== updatedEntity.sys.version;
4629
+ entityStore.updateEntity(updatedEntity);
4630
+ // We traverse the whole tree, so this is a opt-in feature to only use it when required.
4631
+ if (shouldRerender && didEntityChange) {
4632
+ updateNodesByUpdatedEntity(updatedEntity.sys.id);
4633
+ }
4634
+ }
4635
+ break;
4636
+ }
4637
+ case INCOMING_EVENTS.RequestEditorMode: {
4638
+ break;
4639
+ }
4640
+ default: {
4641
+ const knownEvents = Object.values(INCOMING_EVENTS);
4642
+ const isDeprecatedMessage = knownEvents.includes(eventData.eventType);
4643
+ if (!isDeprecatedMessage) {
4644
+ debug$1.error(`[experiences-visual-editor-react::onMessage] Logic error, unsupported eventType: [${eventData.eventType}]`);
4645
+ }
4646
+ }
4647
+ }
4648
+ };
4649
+ window.addEventListener('message', onMessage);
4650
+ return () => {
4651
+ window.removeEventListener('message', onMessage);
4652
+ };
4653
+ }, [
4654
+ entityStore,
4655
+ setDataSource,
4656
+ setLocale,
4657
+ dataSource,
4658
+ areEntitiesFetched,
4659
+ fetchMissingEntities,
4660
+ setUnboundValues,
4661
+ unboundValues,
4662
+ updateTree,
4663
+ updateNodesByUpdatedEntity,
4664
+ resetEntityStore,
4665
+ ]);
4666
+ }
4667
+
4668
+ const CircularDependencyErrorPlaceholder = ({ wrappingPatternIds, ...props }) => {
4669
+ return (React.createElement("div", { ...props, "data-cf-node-error": "circular-pattern-dependency", style: {
4670
+ border: '1px solid red',
4671
+ background: 'rgba(255, 0, 0, 0.1)',
4672
+ padding: '1rem 1rem 0 1rem',
4673
+ width: '100%',
4674
+ height: '100%',
4675
+ } },
4676
+ "Circular usage of patterns detected:",
4677
+ React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
4678
+ const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
4679
+ const entry = inMemoryEntities.maybeResolveLink(entryLink);
4680
+ const entryTitle = entry?.fields?.title;
4681
+ const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
4682
+ return React.createElement("li", { key: patternId }, text);
4683
+ }))));
4684
+ };
4685
+
4686
+ class ImportedComponentError extends Error {
4687
+ constructor(message) {
4688
+ super(message);
4689
+ this.name = 'ImportedComponentError';
4690
+ }
4691
+ }
4692
+ class ExperienceSDKError extends Error {
4693
+ constructor(message) {
4694
+ super(message);
4695
+ this.name = 'ExperienceSDKError';
4696
+ }
4697
+ }
4698
+ class ImportedComponentErrorBoundary extends React.Component {
4699
+ componentDidCatch(error, _errorInfo) {
4700
+ if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
4701
+ // This error was already handled by a nested error boundary and should be passed upwards
4702
+ // We have to do this as we wrap every component on every layer with this error boundary and
4703
+ // thus an error deep in the tree bubbles through many layers of error boundaries.
4704
+ throw error;
4705
+ }
4706
+ // Differentiate between custom and SDK-provided components for error tracking
4707
+ const ErrorClass = isContentfulComponent(this.props.componentId)
4708
+ ? ExperienceSDKError
4709
+ : ImportedComponentError;
4710
+ const err = new ErrorClass(error.message);
4711
+ err.stack = error.stack;
4712
+ throw err;
4713
+ }
4714
+ render() {
4715
+ return this.props.children;
4716
+ }
4717
+ }
4718
+
4719
+ const MissingComponentPlaceholder = ({ blockId }) => {
4720
+ return (React.createElement("div", { style: {
4721
+ border: '1px solid red',
4722
+ width: '100%',
4723
+ height: '100%',
4724
+ } },
4725
+ "Missing component '",
4726
+ blockId,
4727
+ "'"));
4728
+ };
4729
+
4730
+ var css_248z$2 = ".EditorBlock-module_emptySlot__za-Bi {\n min-height: 80px;\n min-width: 80px;\n}\n";
4731
+ var styles$1 = {"emptySlot":"EditorBlock-module_emptySlot__za-Bi"};
4732
+ styleInject(css_248z$2);
4733
+
4951
4734
  const useComponentRegistration = (node) => {
4952
4735
  return useMemo(() => {
4953
- if (node.type === ASSEMBLY_NODE_TYPE) {
4954
- // The definition and component are the same for all assemblies
4955
- return {
4736
+ let registration = componentRegistry.get(node.data.blockId);
4737
+ if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
4738
+ registration = createAssemblyRegistration({
4739
+ definitionId: node.data.blockId,
4956
4740
  component: Assembly,
4957
- definition: createAssemblyDefinition(node.data.blockId),
4958
- };
4741
+ });
4959
4742
  }
4960
- const registration = componentRegistry.get(node.data.blockId);
4961
4743
  if (!registration) {
4962
4744
  debug$1.warn(`[experiences-visual-editor-react::useComponentRegistration] Component registration not found for component with id: "${node.data.blockId}". The registered component might have been removed from the code. To proceed, remove the component manually from the layers tab.`);
4963
4745
  return undefined;
@@ -5049,9 +4831,7 @@ const checkIsNodeVisible = (node, resolveDesignValue) => {
5049
4831
  return node.children.some((childNode) => checkIsNodeVisible(childNode, resolveDesignValue));
5050
4832
  }
5051
4833
  // Check if the current node is visible (`cfVisibility` is enforced on all nodes)
5052
- // Check explicitly for false, as `undefined` is treated as `true`. It could become undefined when the breakpoint IDs changed.
5053
- return (resolveDesignValue(node.data.props['cfVisibility'].valuesByBreakpoint) !==
5054
- false);
4834
+ return !!resolveDesignValue(node.data.props['cfVisibility'].valuesByBreakpoint);
5055
4835
  };
5056
4836
 
5057
4837
  const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, definition, options, }) => {