@contentful/experiences-visual-editor-react 3.7.0-prerelease-20250915T1724-8825648.0 → 3.7.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,15 +1,16 @@
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, omit, isArray, isEqual, get as get$2, debounce } from 'lodash-es';
4
+ import cloneDeep from 'lodash.clonedeep';
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';
9
10
  import '@contentful/rich-text-react-renderer';
10
11
 
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);
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);
13
14
 
14
15
  /** @deprecated will be removed when dropping backward compatibility for old DND */
15
16
  const INCOMING_EVENTS$1 = {
@@ -33,7 +34,9 @@ const INCOMING_EVENTS$1 = {
33
34
  /** @deprecated will be removed when dropping backward compatibility for old DND */
34
35
  HoverComponent: 'hoverComponent',
35
36
  UpdatedEntity: 'updatedEntity',
37
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
36
38
  AssembliesAdded: 'assembliesAdded',
39
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
37
40
  AssembliesRegistered: 'assembliesRegistered',
38
41
  /** @deprecated will be removed when dropping backward compatibility for old DND */
39
42
  MouseMove: 'mouseMove',
@@ -96,6 +99,7 @@ const CONTENTFUL_COMPONENTS$1 = {
96
99
  name: 'Carousel',
97
100
  },
98
101
  };
102
+ const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
99
103
  const CF_STYLE_ATTRIBUTES = [
100
104
  'cfVisibility',
101
105
  'cfHorizontalAlignment',
@@ -754,7 +758,7 @@ const BreakpointSchema$1 = z
754
758
  id: propertyKeySchema$1,
755
759
  // Can be replace with z.templateLiteral when upgrading to zod v4
756
760
  query: z.string().refine((s) => BREAKPOINT_QUERY_REGEX$1.test(s)),
757
- previewSize: z.string(),
761
+ previewSize: z.string().optional(),
758
762
  displayName: z.string(),
759
763
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
760
764
  })
@@ -956,7 +960,8 @@ const validateAtMostOneNativeParameterDefinition$1 = (parameterDefinitions, ctx)
956
960
  };
957
961
  const validateNoOverlapBetweenMappingAndOverrides$1 = (variableMappings, allowedVariableOverrides, ctx) => {
958
962
  const variableMappingKeys = Object.keys(variableMappings || {});
959
- const overlap = variableMappingKeys.filter((key) => allowedVariableOverrides?.includes(key));
963
+ const overridesSet = new Set(allowedVariableOverrides || []);
964
+ const overlap = variableMappingKeys.filter((key) => overridesSet.has(key));
960
965
  if (overlap.length > 0) {
961
966
  ctx.addIssue({
962
967
  code: z.ZodIssueCode.custom,
@@ -1682,7 +1687,9 @@ const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.inclu
1682
1687
  // cfWrapColumns & cfWrapColumnsCount are no real style attributes as they are handled on the editor side
1683
1688
  const propsToRemove = ['cfSsrClassName', 'cfWrapColumns', 'cfWrapColumnsCount'];
1684
1689
  const sanitizeNodeProps = (nodeProps) => {
1685
- return omit(nodeProps, stylesToRemove, propsToRemove);
1690
+ const keysToRemove = [...stylesToRemove, ...propsToRemove];
1691
+ const sanitizedProps = Object.fromEntries(Object.entries(nodeProps).filter(([key]) => !keysToRemove.includes(key)));
1692
+ return sanitizedProps;
1686
1693
  };
1687
1694
 
1688
1695
  /** Turn the visibility value into a style object that can be used for inline styles in React */
@@ -2161,7 +2168,7 @@ function getArrayValue(entryOrAsset, path, entityStore) {
2161
2168
  }
2162
2169
  const fieldName = path.split('/').slice(2, -1);
2163
2170
  const arrayValue = get$1(entryOrAsset, fieldName);
2164
- if (!isArray(arrayValue)) {
2171
+ if (!Array.isArray(arrayValue)) {
2165
2172
  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 });
2166
2173
  return;
2167
2174
  }
@@ -2278,6 +2285,19 @@ function getTargetValueInPixels(targetWidthObject) {
2278
2285
  return targetWidthObject.value;
2279
2286
  }
2280
2287
  }
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
+ };
2281
2301
 
2282
2302
  class ParseError extends Error {
2283
2303
  constructor(message) {
@@ -2913,6 +2933,9 @@ class DeepReference {
2913
2933
  return new DeepReference(opt);
2914
2934
  }
2915
2935
  }
2936
+ /**
2937
+ * used in editor mode. for delivery mode see `gatherDeepReferencesFromExperienceEntry`
2938
+ */
2916
2939
  function gatherDeepReferencesFromTree(startingNode, dataSource, getEntityFromLink) {
2917
2940
  const deepReferences = [];
2918
2941
  treeVisit(startingNode, (node) => {
@@ -3183,7 +3206,9 @@ const INCOMING_EVENTS = {
3183
3206
  /** @deprecated will be removed when dropping backward compatibility for old DND */
3184
3207
  HoverComponent: 'hoverComponent',
3185
3208
  UpdatedEntity: 'updatedEntity',
3209
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
3186
3210
  AssembliesAdded: 'assembliesAdded',
3211
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
3187
3212
  AssembliesRegistered: 'assembliesRegistered',
3188
3213
  /** @deprecated will be removed when dropping backward compatibility for old DND */
3189
3214
  MouseMove: 'mouseMove',
@@ -3208,7 +3233,6 @@ var StudioCanvasMode$2;
3208
3233
  StudioCanvasMode["NONE"] = "none";
3209
3234
  })(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
3210
3235
  const ASSEMBLY_NODE_TYPE = 'assembly';
3211
- const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
3212
3236
  const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
3213
3237
  const EMPTY_CONTAINER_SIZE = '80px';
3214
3238
  const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
@@ -3367,34 +3391,7 @@ const useBreakpoints = (breakpoints) => {
3367
3391
  return { resolveDesignValue };
3368
3392
  };
3369
3393
 
3370
- // Note: During development, the hot reloading might empty this and it
3371
- // stays empty leading to not rendering assemblies. Ideally, this is
3372
- // integrated into the state machine to keep track of its state.
3373
- const assembliesRegistry = new Map([]);
3374
- const setAssemblies = (assemblies) => {
3375
- for (const assembly of assemblies) {
3376
- assembliesRegistry.set(assembly.sys.id, assembly);
3377
- }
3378
- };
3379
3394
  const componentRegistry = new Map();
3380
- const addComponentRegistration = (componentRegistration) => {
3381
- componentRegistry.set(componentRegistration.definition.id, componentRegistration);
3382
- };
3383
- const createAssemblyRegistration = ({ definitionId, definitionName, component, }) => {
3384
- const componentRegistration = componentRegistry.get(definitionId);
3385
- if (componentRegistration) {
3386
- return componentRegistration;
3387
- }
3388
- const definition = {
3389
- id: definitionId,
3390
- name: definitionName || 'Component',
3391
- variables: {},
3392
- children: true,
3393
- category: ASSEMBLY_DEFAULT_CATEGORY,
3394
- };
3395
- addComponentRegistration({ component, definition });
3396
- return componentRegistry.get(definitionId);
3397
- };
3398
3395
 
3399
3396
  const useEditorStore = create((set, get) => ({
3400
3397
  dataSource: {},
@@ -3434,38 +3431,310 @@ const useEditorStore = create((set, get) => ({
3434
3431
  },
3435
3432
  }));
3436
3433
 
3437
- 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}";
3438
- styleInject(css_248z$8);
3439
-
3440
- /** @deprecated will be removed when dropping backward compatibility for old DND */
3441
- /**
3442
- * These modes are ONLY intended to be internally used within the context of
3443
- * editing an experience inside of Contentful Studio. i.e. these modes
3444
- * intentionally do not include preview/delivery modes.
3445
- */
3446
- var StudioCanvasMode$1;
3447
- (function (StudioCanvasMode) {
3448
- StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
3449
- StudioCanvasMode["EDITOR"] = "editorMode";
3450
- StudioCanvasMode["NONE"] = "none";
3451
- })(StudioCanvasMode$1 || (StudioCanvasMode$1 = {}));
3452
- var PostMessageMethods$1;
3453
- (function (PostMessageMethods) {
3454
- PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
3455
- PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
3456
- })(PostMessageMethods$1 || (PostMessageMethods$1 = {}));
3457
-
3458
- var css_248z$7 = ".cf-button:empty:before{content:\"\";display:inline-block}";
3459
- styleInject(css_248z$7);
3460
-
3461
- var css_248z$6 = ".cf-heading{white-space:pre-line}";
3462
- styleInject(css_248z$6);
3463
-
3464
- var css_248z$5 = ".cf-richtext{white-space:pre-line}.cf-richtext>:first-child{margin-top:0}.cf-richtext>:last-child{margin-bottom:0}";
3465
- styleInject(css_248z$5);
3466
-
3467
- var css_248z$4 = ".cf-text{white-space:pre-line}.cf-text-link .cf-text{margin:0}";
3468
- styleInject(css_248z$4);
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
+ 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
+ styleInject(css_248z$8);
3708
+
3709
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
3710
+ /**
3711
+ * These modes are ONLY intended to be internally used within the context of
3712
+ * editing an experience inside of Contentful Studio. i.e. these modes
3713
+ * intentionally do not include preview/delivery modes.
3714
+ */
3715
+ var StudioCanvasMode$1;
3716
+ (function (StudioCanvasMode) {
3717
+ StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
3718
+ StudioCanvasMode["EDITOR"] = "editorMode";
3719
+ StudioCanvasMode["NONE"] = "none";
3720
+ })(StudioCanvasMode$1 || (StudioCanvasMode$1 = {}));
3721
+ var PostMessageMethods$1;
3722
+ (function (PostMessageMethods) {
3723
+ PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
3724
+ PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
3725
+ })(PostMessageMethods$1 || (PostMessageMethods$1 = {}));
3726
+
3727
+ var css_248z$7 = ".cf-button:empty:before{content:\"\";display:inline-block}";
3728
+ styleInject(css_248z$7);
3729
+
3730
+ var css_248z$6 = ".cf-heading{white-space:pre-line}";
3731
+ styleInject(css_248z$6);
3732
+
3733
+ var css_248z$5 = ".cf-richtext{white-space:pre-line}.cf-richtext>:first-child{margin-top:0}.cf-richtext>:last-child{margin-bottom:0}";
3734
+ styleInject(css_248z$5);
3735
+
3736
+ var css_248z$4 = ".cf-text{white-space:pre-line}.cf-text-link .cf-text{margin:0}";
3737
+ styleInject(css_248z$4);
3469
3738
 
3470
3739
  var css_248z$3 = "div.cf-placeholder-wrapper{outline:2px solid rgba(var(--cf-color-gray400-rgb),.5);outline-offset:-2px;overflow:hidden;position:relative}img.cf-placeholder-image{background-color:var(--cf-color-gray100);height:100%;width:100%}svg.cf-placeholder-icon{height:var(--cf-text-3xl);left:50%;max-height:100%;max-width:100%;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--cf-text-3xl)}svg.cf-placeholder-icon path{fill:var(--cf-color-gray400)}";
3471
3740
  styleInject(css_248z$3);
@@ -3727,7 +3996,7 @@ const BreakpointSchema = z
3727
3996
  id: propertyKeySchema,
3728
3997
  // Can be replace with z.templateLiteral when upgrading to zod v4
3729
3998
  query: z.string().refine((s) => BREAKPOINT_QUERY_REGEX.test(s)),
3730
- previewSize: z.string(),
3999
+ previewSize: z.string().optional(),
3731
4000
  displayName: z.string(),
3732
4001
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
3733
4002
  })
@@ -3929,7 +4198,8 @@ const validateAtMostOneNativeParameterDefinition = (parameterDefinitions, ctx) =
3929
4198
  };
3930
4199
  const validateNoOverlapBetweenMappingAndOverrides = (variableMappings, allowedVariableOverrides, ctx) => {
3931
4200
  const variableMappingKeys = Object.keys(variableMappings || {});
3932
- const overlap = variableMappingKeys.filter((key) => allowedVariableOverrides?.includes(key));
4201
+ const overridesSet = new Set(allowedVariableOverrides || []);
4202
+ const overlap = variableMappingKeys.filter((key) => overridesSet.has(key));
3933
4203
  if (overlap.length > 0) {
3934
4204
  ctx.addIssue({
3935
4205
  code: z.ZodIssueCode.custom,
@@ -4640,8 +4910,8 @@ var VisualEditorMode;
4640
4910
  VisualEditorMode["InjectScript"] = "injectScript";
4641
4911
  })(VisualEditorMode || (VisualEditorMode = {}));
4642
4912
 
4643
- 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}";
4644
- styleInject(css_248z$2$1);
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);
4645
4915
 
4646
4916
  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) => {
4647
4917
  return (React.createElement("div", { id: id, ref: ref, style: {
@@ -4678,315 +4948,16 @@ const Assembly = (props) => {
4678
4948
  return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
4679
4949
  };
4680
4950
 
4681
- function useEditorSubscriber(inMemoryEntitiesStore) {
4682
- const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
4683
- const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
4684
- const setEntitiesFetched = inMemoryEntitiesStore((state) => state.setEntitiesFetched);
4685
- const resetEntityStore = inMemoryEntitiesStore((state) => state.resetEntityStore);
4686
- const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
4687
- updateTree: state.updateTree,
4688
- updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
4689
- }));
4690
- const unboundValues = useEditorStore((state) => state.unboundValues);
4691
- const dataSource = useEditorStore((state) => state.dataSource);
4692
- const setLocale = useEditorStore((state) => state.setLocale);
4693
- const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
4694
- const setDataSource = useEditorStore((state) => state.setDataSource);
4695
- const reloadApp = () => {
4696
- sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
4697
- // Wait a moment to ensure that the message was sent
4698
- setTimeout(() => {
4699
- // Received a hot reload message from webpack dev server -> reload the canvas
4700
- window.location.reload();
4701
- }, 50);
4702
- };
4703
- useEffect(() => {
4704
- sendMessage(OUTGOING_EVENTS.RequestComponentTreeUpdate, undefined);
4705
- }, []);
4706
- /**
4707
- * Fills up entityStore with entities from newDataSource and from the tree.
4708
- * Also manages "entity status" variables (areEntitiesFetched, isFetchingEntities)
4709
- */
4710
- const fetchMissingEntities = useCallback(async (entityStore, newDataSource, tree) => {
4711
- // if we realize that there's nothing missing and nothing to fill-fetch before we do any async call,
4712
- // then we can simply return and not lock the EntityStore at all.
4713
- const startFetching = () => {
4714
- setEntitiesFetched(false);
4715
- };
4716
- const endFetching = () => {
4717
- setEntitiesFetched(true);
4718
- };
4719
- // Prepare L1 entities and deepReferences
4720
- const entityLinksL1 = [
4721
- ...Object.values(newDataSource),
4722
- ...assembliesRegistry.values(), // we count assemblies here as "L1 entities", for convenience. Even though they're not headEntities.
4723
- ];
4724
- /**
4725
- * Checks only for _missing_ L1 entities
4726
- * WARNING: Does NOT check for entity staleness/versions. If an entity is stale, it will NOT be considered missing.
4727
- * If ExperienceBuilder wants to update stale entities, it should post `▼UPDATED_ENTITY` message to SDK.
4728
- */
4729
- const isMissingL1Entities = (entityLinks) => {
4730
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinks);
4731
- return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
4732
- };
4733
- /**
4734
- * PRECONDITION: all L1 entities are fetched
4735
- */
4736
- const isMissingL2Entities = (deepReferences) => {
4737
- const referentLinks = deepReferences
4738
- .map((deepReference) => deepReference.extractReferent(entityStore))
4739
- .filter(isLink$1);
4740
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4741
- return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
4742
- };
4743
- /**
4744
- * POST_CONDITION: entityStore is has all L1 entities (aka headEntities)
4745
- */
4746
- const fillupL1 = async ({ entityLinksL1, }) => {
4747
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinksL1);
4748
- await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
4749
- };
4750
- /**
4751
- * PRECONDITION: all L1 entites are fetched
4752
- */
4753
- const fillupL2 = async ({ deepReferences }) => {
4754
- const referentLinks = deepReferences
4755
- .map((deepReference) => deepReference.extractReferent(entityStore))
4756
- .filter(isLink$1);
4757
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4758
- await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
4759
- };
4760
- try {
4761
- if (isMissingL1Entities(entityLinksL1)) {
4762
- startFetching();
4763
- await fillupL1({ entityLinksL1 });
4764
- }
4765
- const deepReferences = gatherDeepReferencesFromTree(tree.root, newDataSource, entityStore.getEntityFromLink.bind(entityStore));
4766
- if (isMissingL2Entities(deepReferences)) {
4767
- startFetching();
4768
- await fillupL2({ deepReferences });
4769
- }
4770
- }
4771
- catch (error) {
4772
- debug$1.error('[experiences-visual-editor-react::useEditorSubscriber] Failed fetching entities', { error });
4773
- throw error; // TODO: The original catch didn't let's rethrow; for the moment throw to see if we have any errors
4774
- }
4775
- finally {
4776
- endFetching();
4777
- }
4778
- }, [setEntitiesFetched /* setFetchingEntities, assembliesRegistry */]);
4779
- useEffect(() => {
4780
- const onMessage = async (event) => {
4781
- let reason;
4782
- if ((reason = doesMismatchMessageSchema(event))) {
4783
- if (event.origin.startsWith('http://localhost') &&
4784
- `${event.data}`.includes('webpackHotUpdate')) {
4785
- reloadApp();
4786
- }
4787
- else {
4788
- debug$1.warn(`[experiences-visual-editor-react::onMessage] Ignoring alien incoming message from origin [${event.origin}], due to: [${reason}]`, event);
4789
- }
4790
- return;
4791
- }
4792
- const eventData = tryParseMessage(event);
4793
- debug$1.debug(`[experiences-visual-editor-react::onMessage] Received message [${eventData.eventType}]`, eventData);
4794
- if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
4795
- // Expected message: This message is handled in the EntityStore to store fetched entities
4796
- return;
4797
- }
4798
- switch (eventData.eventType) {
4799
- case INCOMING_EVENTS.ExperienceUpdated: {
4800
- const { tree, locale, changedNode, changedValueType, assemblies } = eventData.payload;
4801
- // Make sure to first store the assemblies before setting the tree and thus triggering a rerender
4802
- if (assemblies) {
4803
- setAssemblies(assemblies);
4804
- // If the assemblyEntry is not yet fetched, this will be done below by
4805
- // the imperative calls to fetchMissingEntities.
4806
- }
4807
- let newEntityStore = entityStore;
4808
- if (entityStore.locale !== locale) {
4809
- newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
4810
- setLocale(locale);
4811
- resetEntityStore(newEntityStore);
4812
- }
4813
- // Below are mutually exclusive cases
4814
- if (changedNode) {
4815
- /**
4816
- * On single node updates, we want to skip the process of getting the data (datasource and unbound values)
4817
- * from tree. Since we know the updated node, we can skip that recursion everytime the tree updates and
4818
- * just update the relevant data we need from the relevant node.
4819
- *
4820
- * We still update the tree here so we don't have a stale "tree"
4821
- */
4822
- if (changedValueType === 'BoundValue') {
4823
- const newDataSource = { ...dataSource, ...changedNode.data.dataSource };
4824
- setDataSource(newDataSource);
4825
- await fetchMissingEntities(newEntityStore, newDataSource, tree);
4826
- }
4827
- else if (changedValueType === 'UnboundValue') {
4828
- setUnboundValues({
4829
- ...unboundValues,
4830
- ...changedNode.data.unboundValues,
4831
- });
4832
- }
4833
- }
4834
- else {
4835
- const { dataSource, unboundValues } = getDataFromTree(tree);
4836
- setDataSource(dataSource);
4837
- setUnboundValues(unboundValues);
4838
- await fetchMissingEntities(newEntityStore, dataSource, tree);
4839
- }
4840
- // Update the tree when all necessary data is fetched and ready for rendering.
4841
- updateTree(tree);
4842
- break;
4843
- }
4844
- case INCOMING_EVENTS.AssembliesRegistered: {
4845
- const { assemblies } = eventData.payload;
4846
- assemblies.forEach((definition) => {
4847
- addComponentRegistration({
4848
- component: Assembly,
4849
- definition,
4850
- });
4851
- });
4852
- break;
4853
- }
4854
- case INCOMING_EVENTS.AssembliesAdded: {
4855
- const { assembly, assemblyDefinition, } = eventData.payload;
4856
- entityStore.updateEntity(assembly);
4857
- // Using a Map here to avoid setting state and rerending all existing assemblies when a new assembly is added
4858
- // TODO: Figure out if we can extend this love to data source and unbound values. Maybe that'll solve the blink
4859
- // of all bound and unbound values when new values are added
4860
- assembliesRegistry.set(assembly.sys.id, {
4861
- sys: { id: assembly.sys.id, linkType: 'Entry', type: 'Link' },
4862
- });
4863
- if (assemblyDefinition) {
4864
- addComponentRegistration({
4865
- component: Assembly,
4866
- definition: assemblyDefinition,
4867
- });
4868
- }
4869
- break;
4870
- }
4871
- case INCOMING_EVENTS.UpdatedEntity: {
4872
- const { entity: updatedEntity, shouldRerender } = eventData.payload;
4873
- if (updatedEntity) {
4874
- const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
4875
- const didEntityChange = storedEntity?.sys.version !== updatedEntity.sys.version;
4876
- entityStore.updateEntity(updatedEntity);
4877
- // We traverse the whole tree, so this is a opt-in feature to only use it when required.
4878
- if (shouldRerender && didEntityChange) {
4879
- updateNodesByUpdatedEntity(updatedEntity.sys.id);
4880
- }
4881
- }
4882
- break;
4883
- }
4884
- case INCOMING_EVENTS.RequestEditorMode: {
4885
- break;
4886
- }
4887
- default: {
4888
- const knownEvents = Object.values(INCOMING_EVENTS);
4889
- const isDeprecatedMessage = knownEvents.includes(eventData.eventType);
4890
- if (!isDeprecatedMessage) {
4891
- debug$1.error(`[experiences-visual-editor-react::onMessage] Logic error, unsupported eventType: [${eventData.eventType}]`);
4892
- }
4893
- }
4894
- }
4895
- };
4896
- window.addEventListener('message', onMessage);
4897
- return () => {
4898
- window.removeEventListener('message', onMessage);
4899
- };
4900
- }, [
4901
- entityStore,
4902
- setDataSource,
4903
- setLocale,
4904
- dataSource,
4905
- areEntitiesFetched,
4906
- fetchMissingEntities,
4907
- setUnboundValues,
4908
- unboundValues,
4909
- updateTree,
4910
- updateNodesByUpdatedEntity,
4911
- resetEntityStore,
4912
- ]);
4913
- }
4914
-
4915
- const CircularDependencyErrorPlaceholder = ({ wrappingPatternIds, ...props }) => {
4916
- return (React.createElement("div", { ...props, "data-cf-node-error": "circular-pattern-dependency", style: {
4917
- border: '1px solid red',
4918
- background: 'rgba(255, 0, 0, 0.1)',
4919
- padding: '1rem 1rem 0 1rem',
4920
- width: '100%',
4921
- height: '100%',
4922
- } },
4923
- "Circular usage of patterns detected:",
4924
- React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
4925
- const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
4926
- const entry = inMemoryEntities.maybeResolveLink(entryLink);
4927
- const entryTitle = entry?.fields?.title;
4928
- const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
4929
- return React.createElement("li", { key: patternId }, text);
4930
- }))));
4931
- };
4932
-
4933
- class ImportedComponentError extends Error {
4934
- constructor(message) {
4935
- super(message);
4936
- this.name = 'ImportedComponentError';
4937
- }
4938
- }
4939
- class ExperienceSDKError extends Error {
4940
- constructor(message) {
4941
- super(message);
4942
- this.name = 'ExperienceSDKError';
4943
- }
4944
- }
4945
- class ImportedComponentErrorBoundary extends React.Component {
4946
- componentDidCatch(error, _errorInfo) {
4947
- if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
4948
- // This error was already handled by a nested error boundary and should be passed upwards
4949
- // We have to do this as we wrap every component on every layer with this error boundary and
4950
- // thus an error deep in the tree bubbles through many layers of error boundaries.
4951
- throw error;
4952
- }
4953
- // Differentiate between custom and SDK-provided components for error tracking
4954
- const ErrorClass = isContentfulComponent(this.props.componentId)
4955
- ? ExperienceSDKError
4956
- : ImportedComponentError;
4957
- const err = new ErrorClass(error.message);
4958
- err.stack = error.stack;
4959
- throw err;
4960
- }
4961
- render() {
4962
- return this.props.children;
4963
- }
4964
- }
4965
-
4966
- const MissingComponentPlaceholder = ({ blockId }) => {
4967
- return (React.createElement("div", { style: {
4968
- border: '1px solid red',
4969
- width: '100%',
4970
- height: '100%',
4971
- } },
4972
- "Missing component '",
4973
- blockId,
4974
- "'"));
4975
- };
4976
-
4977
- var css_248z$2 = ".EditorBlock-module_emptySlot__za-Bi {\n min-height: 80px;\n min-width: 80px;\n}\n";
4978
- var styles$1 = {"emptySlot":"EditorBlock-module_emptySlot__za-Bi"};
4979
- styleInject(css_248z$2);
4980
-
4981
4951
  const useComponentRegistration = (node) => {
4982
4952
  return useMemo(() => {
4983
- let registration = componentRegistry.get(node.data.blockId);
4984
- if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
4985
- registration = createAssemblyRegistration({
4986
- definitionId: node.data.blockId,
4953
+ if (node.type === ASSEMBLY_NODE_TYPE) {
4954
+ // The definition and component are the same for all assemblies
4955
+ return {
4987
4956
  component: Assembly,
4988
- });
4957
+ definition: createAssemblyDefinition(node.data.blockId),
4958
+ };
4989
4959
  }
4960
+ const registration = componentRegistry.get(node.data.blockId);
4990
4961
  if (!registration) {
4991
4962
  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.`);
4992
4963
  return undefined;
@@ -5078,7 +5049,9 @@ const checkIsNodeVisible = (node, resolveDesignValue) => {
5078
5049
  return node.children.some((childNode) => checkIsNodeVisible(childNode, resolveDesignValue));
5079
5050
  }
5080
5051
  // Check if the current node is visible (`cfVisibility` is enforced on all nodes)
5081
- return !!resolveDesignValue(node.data.props['cfVisibility'].valuesByBreakpoint);
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);
5082
5055
  };
5083
5056
 
5084
5057
  const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, definition, options, }) => {