@contentful/experiences-visual-editor-react 3.7.0-dev-20250919T0822-6a47406.0 → 3.7.0-dev-20250919T1400-8d76591.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/renderApp.js CHANGED
@@ -37,8 +37,8 @@ function styleInject(css, ref) {
37
37
  }
38
38
  }
39
39
 
40
- 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";
41
- styleInject(css_248z$a);
40
+ 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";
41
+ styleInject(css_248z$b);
42
42
 
43
43
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
44
44
 
@@ -41408,7 +41408,9 @@ const INCOMING_EVENTS$1 = {
41408
41408
  /** @deprecated will be removed when dropping backward compatibility for old DND */
41409
41409
  HoverComponent: 'hoverComponent',
41410
41410
  UpdatedEntity: 'updatedEntity',
41411
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
41411
41412
  AssembliesAdded: 'assembliesAdded',
41413
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
41412
41414
  AssembliesRegistered: 'assembliesRegistered',
41413
41415
  /** @deprecated will be removed when dropping backward compatibility for old DND */
41414
41416
  MouseMove: 'mouseMove',
@@ -41471,6 +41473,7 @@ const CONTENTFUL_COMPONENTS$1 = {
41471
41473
  name: 'Carousel',
41472
41474
  },
41473
41475
  };
41476
+ const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
41474
41477
  const CF_STYLE_ATTRIBUTES = [
41475
41478
  'cfVisibility',
41476
41479
  'cfHorizontalAlignment',
@@ -43656,6 +43659,19 @@ function getTargetValueInPixels(targetWidthObject) {
43656
43659
  return targetWidthObject.value;
43657
43660
  }
43658
43661
  }
43662
+ /**
43663
+ * Creates a component definition for an assembly. As all assemblies use the same definition in the SDK,
43664
+ * all should be registered via this function.
43665
+ */
43666
+ const createAssemblyDefinition = (definitionId) => {
43667
+ return {
43668
+ id: definitionId,
43669
+ name: 'Component',
43670
+ variables: {},
43671
+ children: true,
43672
+ category: ASSEMBLY_DEFAULT_CATEGORY,
43673
+ };
43674
+ };
43659
43675
 
43660
43676
  class ParseError extends Error {
43661
43677
  constructor(message) {
@@ -47909,7 +47925,9 @@ const INCOMING_EVENTS = {
47909
47925
  /** @deprecated will be removed when dropping backward compatibility for old DND */
47910
47926
  HoverComponent: 'hoverComponent',
47911
47927
  UpdatedEntity: 'updatedEntity',
47928
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
47912
47929
  AssembliesAdded: 'assembliesAdded',
47930
+ /** @deprecated not needed after `patternResolution` was introduced. Will be removed in the next major version. */
47913
47931
  AssembliesRegistered: 'assembliesRegistered',
47914
47932
  /** @deprecated will be removed when dropping backward compatibility for old DND */
47915
47933
  MouseMove: 'mouseMove',
@@ -47935,7 +47953,6 @@ var StudioCanvasMode$2;
47935
47953
  })(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
47936
47954
  const VISUAL_EDITOR_CONTAINER_ID = 'cf-visual-editor';
47937
47955
  const ASSEMBLY_NODE_TYPE = 'assembly';
47938
- const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
47939
47956
  const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
47940
47957
  const EMPTY_CONTAINER_SIZE = '80px';
47941
47958
  const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
@@ -48094,34 +48111,7 @@ const useBreakpoints = (breakpoints) => {
48094
48111
  return { resolveDesignValue };
48095
48112
  };
48096
48113
 
48097
- // Note: During development, the hot reloading might empty this and it
48098
- // stays empty leading to not rendering assemblies. Ideally, this is
48099
- // integrated into the state machine to keep track of its state.
48100
- const assembliesRegistry = new Map([]);
48101
- const setAssemblies = (assemblies) => {
48102
- for (const assembly of assemblies) {
48103
- assembliesRegistry.set(assembly.sys.id, assembly);
48104
- }
48105
- };
48106
48114
  const componentRegistry = new Map();
48107
- const addComponentRegistration = (componentRegistration) => {
48108
- componentRegistry.set(componentRegistration.definition.id, componentRegistration);
48109
- };
48110
- const createAssemblyRegistration = ({ definitionId, definitionName, component, }) => {
48111
- const componentRegistration = componentRegistry.get(definitionId);
48112
- if (componentRegistration) {
48113
- return componentRegistration;
48114
- }
48115
- const definition = {
48116
- id: definitionId,
48117
- name: definitionName || 'Component',
48118
- variables: {},
48119
- children: true,
48120
- category: ASSEMBLY_DEFAULT_CATEGORY,
48121
- };
48122
- addComponentRegistration({ component, definition });
48123
- return componentRegistry.get(definitionId);
48124
- };
48125
48115
 
48126
48116
  const useEditorStore = create((set, get) => ({
48127
48117
  dataSource: {},
@@ -48161,6 +48151,278 @@ const useEditorStore = create((set, get) => ({
48161
48151
  },
48162
48152
  }));
48163
48153
 
48154
+ function useEditorSubscriber(inMemoryEntitiesStore) {
48155
+ const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
48156
+ const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
48157
+ const setEntitiesFetched = inMemoryEntitiesStore((state) => state.setEntitiesFetched);
48158
+ const resetEntityStore = inMemoryEntitiesStore((state) => state.resetEntityStore);
48159
+ const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
48160
+ updateTree: state.updateTree,
48161
+ updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
48162
+ }));
48163
+ const unboundValues = useEditorStore((state) => state.unboundValues);
48164
+ const dataSource = useEditorStore((state) => state.dataSource);
48165
+ const setLocale = useEditorStore((state) => state.setLocale);
48166
+ const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
48167
+ const setDataSource = useEditorStore((state) => state.setDataSource);
48168
+ const reloadApp = () => {
48169
+ sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
48170
+ // Wait a moment to ensure that the message was sent
48171
+ setTimeout(() => {
48172
+ // Received a hot reload message from webpack dev server -> reload the canvas
48173
+ window.location.reload();
48174
+ }, 50);
48175
+ };
48176
+ reactExports.useEffect(() => {
48177
+ sendMessage(OUTGOING_EVENTS.RequestComponentTreeUpdate, undefined);
48178
+ }, []);
48179
+ /**
48180
+ * Fills up entityStore with entities from newDataSource and from the tree.
48181
+ * Also manages "entity status" variables (areEntitiesFetched, isFetchingEntities)
48182
+ */
48183
+ const fetchMissingEntities = reactExports.useCallback(async (entityStore, newDataSource, tree) => {
48184
+ // if we realize that there's nothing missing and nothing to fill-fetch before we do any async call,
48185
+ // then we can simply return and not lock the EntityStore at all.
48186
+ const startFetching = () => {
48187
+ setEntitiesFetched(false);
48188
+ };
48189
+ const endFetching = () => {
48190
+ setEntitiesFetched(true);
48191
+ };
48192
+ // Prepare L1 entities and deepReferences
48193
+ const entityLinksL1 = Object.values(newDataSource);
48194
+ /**
48195
+ * Checks only for _missing_ L1 entities
48196
+ * WARNING: Does NOT check for entity staleness/versions. If an entity is stale, it will NOT be considered missing.
48197
+ * If ExperienceBuilder wants to update stale entities, it should post `▼UPDATED_ENTITY` message to SDK.
48198
+ */
48199
+ const isMissingL1Entities = (entityLinks) => {
48200
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinks);
48201
+ return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
48202
+ };
48203
+ /**
48204
+ * PRECONDITION: all L1 entities are fetched
48205
+ */
48206
+ const isMissingL2Entities = (deepReferences) => {
48207
+ const referentLinks = deepReferences
48208
+ .map((deepReference) => deepReference.extractReferent(entityStore))
48209
+ .filter(isLink$1);
48210
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
48211
+ return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
48212
+ };
48213
+ /**
48214
+ * POST_CONDITION: entityStore is has all L1 entities (aka headEntities)
48215
+ */
48216
+ const fillupL1 = async ({ entityLinksL1, }) => {
48217
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinksL1);
48218
+ await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
48219
+ };
48220
+ /**
48221
+ * PRECONDITION: all L1 entites are fetched
48222
+ */
48223
+ const fillupL2 = async ({ deepReferences }) => {
48224
+ const referentLinks = deepReferences
48225
+ .map((deepReference) => deepReference.extractReferent(entityStore))
48226
+ .filter(isLink$1);
48227
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
48228
+ await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
48229
+ };
48230
+ try {
48231
+ if (isMissingL1Entities(entityLinksL1)) {
48232
+ startFetching();
48233
+ await fillupL1({ entityLinksL1 });
48234
+ }
48235
+ const deepReferences = gatherDeepReferencesFromTree(tree.root, newDataSource, entityStore.getEntityFromLink.bind(entityStore));
48236
+ if (isMissingL2Entities(deepReferences)) {
48237
+ startFetching();
48238
+ await fillupL2({ deepReferences });
48239
+ }
48240
+ }
48241
+ catch (error) {
48242
+ debug$1.error('[experiences-visual-editor-react::useEditorSubscriber] Failed fetching entities', { error });
48243
+ throw error; // TODO: The original catch didn't let's rethrow; for the moment throw to see if we have any errors
48244
+ }
48245
+ finally {
48246
+ endFetching();
48247
+ }
48248
+ }, [setEntitiesFetched]);
48249
+ reactExports.useEffect(() => {
48250
+ const onMessage = async (event) => {
48251
+ let reason;
48252
+ if ((reason = doesMismatchMessageSchema(event))) {
48253
+ if (event.origin.startsWith('http://localhost') &&
48254
+ `${event.data}`.includes('webpackHotUpdate')) {
48255
+ reloadApp();
48256
+ }
48257
+ else {
48258
+ debug$1.warn(`[experiences-visual-editor-react::onMessage] Ignoring alien incoming message from origin [${event.origin}], due to: [${reason}]`, event);
48259
+ }
48260
+ return;
48261
+ }
48262
+ const eventData = tryParseMessage(event);
48263
+ debug$1.debug(`[experiences-visual-editor-react::onMessage] Received message [${eventData.eventType}]`, eventData);
48264
+ if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
48265
+ // Expected message: This message is handled in the EntityStore to store fetched entities
48266
+ return;
48267
+ }
48268
+ switch (eventData.eventType) {
48269
+ case INCOMING_EVENTS.ExperienceUpdated: {
48270
+ const { tree, locale, changedNode, changedValueType } = eventData.payload;
48271
+ let newEntityStore = entityStore;
48272
+ if (entityStore.locale !== locale) {
48273
+ newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
48274
+ setLocale(locale);
48275
+ resetEntityStore(newEntityStore);
48276
+ }
48277
+ // Below are mutually exclusive cases
48278
+ if (changedNode) {
48279
+ /**
48280
+ * On single node updates, we want to skip the process of getting the data (datasource and unbound values)
48281
+ * from tree. Since we know the updated node, we can skip that recursion everytime the tree updates and
48282
+ * just update the relevant data we need from the relevant node.
48283
+ *
48284
+ * We still update the tree here so we don't have a stale "tree"
48285
+ */
48286
+ if (changedValueType === 'BoundValue') {
48287
+ const newDataSource = { ...dataSource, ...changedNode.data.dataSource };
48288
+ setDataSource(newDataSource);
48289
+ await fetchMissingEntities(newEntityStore, newDataSource, tree);
48290
+ }
48291
+ else if (changedValueType === 'UnboundValue') {
48292
+ setUnboundValues({
48293
+ ...unboundValues,
48294
+ ...changedNode.data.unboundValues,
48295
+ });
48296
+ }
48297
+ }
48298
+ else {
48299
+ const { dataSource, unboundValues } = getDataFromTree(tree);
48300
+ setDataSource(dataSource);
48301
+ setUnboundValues(unboundValues);
48302
+ await fetchMissingEntities(newEntityStore, dataSource, tree);
48303
+ }
48304
+ // Update the tree when all necessary data is fetched and ready for rendering.
48305
+ updateTree(tree);
48306
+ break;
48307
+ }
48308
+ case INCOMING_EVENTS.AssembliesRegistered: {
48309
+ // Not necessary anymore since `patternResolution` which was introduced in 2024.
48310
+ break;
48311
+ }
48312
+ case INCOMING_EVENTS.AssembliesAdded: {
48313
+ // Not necessary anymore since `patternResolution` which was introduced in 2024.
48314
+ break;
48315
+ }
48316
+ case INCOMING_EVENTS.UpdatedEntity: {
48317
+ const { entity: updatedEntity, shouldRerender } = eventData.payload;
48318
+ if (updatedEntity) {
48319
+ const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
48320
+ const didEntityChange = storedEntity?.sys.version !== updatedEntity.sys.version;
48321
+ entityStore.updateEntity(updatedEntity);
48322
+ // We traverse the whole tree, so this is a opt-in feature to only use it when required.
48323
+ if (shouldRerender && didEntityChange) {
48324
+ updateNodesByUpdatedEntity(updatedEntity.sys.id);
48325
+ }
48326
+ }
48327
+ break;
48328
+ }
48329
+ case INCOMING_EVENTS.RequestEditorMode: {
48330
+ break;
48331
+ }
48332
+ default: {
48333
+ const knownEvents = Object.values(INCOMING_EVENTS);
48334
+ const isDeprecatedMessage = knownEvents.includes(eventData.eventType);
48335
+ if (!isDeprecatedMessage) {
48336
+ debug$1.error(`[experiences-visual-editor-react::onMessage] Logic error, unsupported eventType: [${eventData.eventType}]`);
48337
+ }
48338
+ }
48339
+ }
48340
+ };
48341
+ window.addEventListener('message', onMessage);
48342
+ return () => {
48343
+ window.removeEventListener('message', onMessage);
48344
+ };
48345
+ }, [
48346
+ entityStore,
48347
+ setDataSource,
48348
+ setLocale,
48349
+ dataSource,
48350
+ areEntitiesFetched,
48351
+ fetchMissingEntities,
48352
+ setUnboundValues,
48353
+ unboundValues,
48354
+ updateTree,
48355
+ updateNodesByUpdatedEntity,
48356
+ resetEntityStore,
48357
+ ]);
48358
+ }
48359
+
48360
+ const CircularDependencyErrorPlaceholder = ({ wrappingPatternIds, ...props }) => {
48361
+ return (React$1.createElement("div", { ...props, "data-cf-node-error": "circular-pattern-dependency", style: {
48362
+ border: '1px solid red',
48363
+ background: 'rgba(255, 0, 0, 0.1)',
48364
+ padding: '1rem 1rem 0 1rem',
48365
+ width: '100%',
48366
+ height: '100%',
48367
+ } },
48368
+ "Circular usage of patterns detected:",
48369
+ React$1.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
48370
+ const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
48371
+ const entry = inMemoryEntities.maybeResolveLink(entryLink);
48372
+ const entryTitle = entry?.fields?.title;
48373
+ const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
48374
+ return React$1.createElement("li", { key: patternId }, text);
48375
+ }))));
48376
+ };
48377
+
48378
+ class ImportedComponentError extends Error {
48379
+ constructor(message) {
48380
+ super(message);
48381
+ this.name = 'ImportedComponentError';
48382
+ }
48383
+ }
48384
+ class ExperienceSDKError extends Error {
48385
+ constructor(message) {
48386
+ super(message);
48387
+ this.name = 'ExperienceSDKError';
48388
+ }
48389
+ }
48390
+ class ImportedComponentErrorBoundary extends React$1.Component {
48391
+ componentDidCatch(error, _errorInfo) {
48392
+ if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
48393
+ // This error was already handled by a nested error boundary and should be passed upwards
48394
+ // We have to do this as we wrap every component on every layer with this error boundary and
48395
+ // thus an error deep in the tree bubbles through many layers of error boundaries.
48396
+ throw error;
48397
+ }
48398
+ // Differentiate between custom and SDK-provided components for error tracking
48399
+ const ErrorClass = isContentfulComponent(this.props.componentId)
48400
+ ? ExperienceSDKError
48401
+ : ImportedComponentError;
48402
+ const err = new ErrorClass(error.message);
48403
+ err.stack = error.stack;
48404
+ throw err;
48405
+ }
48406
+ render() {
48407
+ return this.props.children;
48408
+ }
48409
+ }
48410
+
48411
+ const MissingComponentPlaceholder = ({ blockId }) => {
48412
+ return (React$1.createElement("div", { style: {
48413
+ border: '1px solid red',
48414
+ width: '100%',
48415
+ height: '100%',
48416
+ } },
48417
+ "Missing component '",
48418
+ blockId,
48419
+ "'"));
48420
+ };
48421
+
48422
+ var css_248z$a = ".EditorBlock-module_emptySlot__za-Bi {\n min-height: 80px;\n min-width: 80px;\n}\n";
48423
+ var styles$1 = {"emptySlot":"EditorBlock-module_emptySlot__za-Bi"};
48424
+ styleInject(css_248z$a);
48425
+
48164
48426
  var dist$1 = {};
48165
48427
 
48166
48428
  var isPlainObj = value => {
@@ -50594,8 +50856,8 @@ var VisualEditorMode;
50594
50856
  VisualEditorMode["InjectScript"] = "injectScript";
50595
50857
  })(VisualEditorMode || (VisualEditorMode = {}));
50596
50858
 
50597
- 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}";
50598
- styleInject(css_248z$2$1);
50859
+ 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}";
50860
+ styleInject(css_248z$2);
50599
50861
 
50600
50862
  const Flex = reactExports.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) => {
50601
50863
  return (React$1.createElement("div", { id: id, ref: ref, style: {
@@ -50632,315 +50894,16 @@ const Assembly = (props) => {
50632
50894
  return React$1.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
50633
50895
  };
50634
50896
 
50635
- function useEditorSubscriber(inMemoryEntitiesStore) {
50636
- const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
50637
- const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
50638
- const setEntitiesFetched = inMemoryEntitiesStore((state) => state.setEntitiesFetched);
50639
- const resetEntityStore = inMemoryEntitiesStore((state) => state.resetEntityStore);
50640
- const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
50641
- updateTree: state.updateTree,
50642
- updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
50643
- }));
50644
- const unboundValues = useEditorStore((state) => state.unboundValues);
50645
- const dataSource = useEditorStore((state) => state.dataSource);
50646
- const setLocale = useEditorStore((state) => state.setLocale);
50647
- const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
50648
- const setDataSource = useEditorStore((state) => state.setDataSource);
50649
- const reloadApp = () => {
50650
- sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
50651
- // Wait a moment to ensure that the message was sent
50652
- setTimeout(() => {
50653
- // Received a hot reload message from webpack dev server -> reload the canvas
50654
- window.location.reload();
50655
- }, 50);
50656
- };
50657
- reactExports.useEffect(() => {
50658
- sendMessage(OUTGOING_EVENTS.RequestComponentTreeUpdate, undefined);
50659
- }, []);
50660
- /**
50661
- * Fills up entityStore with entities from newDataSource and from the tree.
50662
- * Also manages "entity status" variables (areEntitiesFetched, isFetchingEntities)
50663
- */
50664
- const fetchMissingEntities = reactExports.useCallback(async (entityStore, newDataSource, tree) => {
50665
- // if we realize that there's nothing missing and nothing to fill-fetch before we do any async call,
50666
- // then we can simply return and not lock the EntityStore at all.
50667
- const startFetching = () => {
50668
- setEntitiesFetched(false);
50669
- };
50670
- const endFetching = () => {
50671
- setEntitiesFetched(true);
50672
- };
50673
- // Prepare L1 entities and deepReferences
50674
- const entityLinksL1 = [
50675
- ...Object.values(newDataSource),
50676
- ...assembliesRegistry.values(), // we count assemblies here as "L1 entities", for convenience. Even though they're not headEntities.
50677
- ];
50678
- /**
50679
- * Checks only for _missing_ L1 entities
50680
- * WARNING: Does NOT check for entity staleness/versions. If an entity is stale, it will NOT be considered missing.
50681
- * If ExperienceBuilder wants to update stale entities, it should post `▼UPDATED_ENTITY` message to SDK.
50682
- */
50683
- const isMissingL1Entities = (entityLinks) => {
50684
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinks);
50685
- return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
50686
- };
50687
- /**
50688
- * PRECONDITION: all L1 entities are fetched
50689
- */
50690
- const isMissingL2Entities = (deepReferences) => {
50691
- const referentLinks = deepReferences
50692
- .map((deepReference) => deepReference.extractReferent(entityStore))
50693
- .filter(isLink$1);
50694
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
50695
- return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
50696
- };
50697
- /**
50698
- * POST_CONDITION: entityStore is has all L1 entities (aka headEntities)
50699
- */
50700
- const fillupL1 = async ({ entityLinksL1, }) => {
50701
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinksL1);
50702
- await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
50703
- };
50704
- /**
50705
- * PRECONDITION: all L1 entites are fetched
50706
- */
50707
- const fillupL2 = async ({ deepReferences }) => {
50708
- const referentLinks = deepReferences
50709
- .map((deepReference) => deepReference.extractReferent(entityStore))
50710
- .filter(isLink$1);
50711
- const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
50712
- await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
50713
- };
50714
- try {
50715
- if (isMissingL1Entities(entityLinksL1)) {
50716
- startFetching();
50717
- await fillupL1({ entityLinksL1 });
50718
- }
50719
- const deepReferences = gatherDeepReferencesFromTree(tree.root, newDataSource, entityStore.getEntityFromLink.bind(entityStore));
50720
- if (isMissingL2Entities(deepReferences)) {
50721
- startFetching();
50722
- await fillupL2({ deepReferences });
50723
- }
50724
- }
50725
- catch (error) {
50726
- debug$1.error('[experiences-visual-editor-react::useEditorSubscriber] Failed fetching entities', { error });
50727
- throw error; // TODO: The original catch didn't let's rethrow; for the moment throw to see if we have any errors
50728
- }
50729
- finally {
50730
- endFetching();
50731
- }
50732
- }, [setEntitiesFetched /* setFetchingEntities, assembliesRegistry */]);
50733
- reactExports.useEffect(() => {
50734
- const onMessage = async (event) => {
50735
- let reason;
50736
- if ((reason = doesMismatchMessageSchema(event))) {
50737
- if (event.origin.startsWith('http://localhost') &&
50738
- `${event.data}`.includes('webpackHotUpdate')) {
50739
- reloadApp();
50740
- }
50741
- else {
50742
- debug$1.warn(`[experiences-visual-editor-react::onMessage] Ignoring alien incoming message from origin [${event.origin}], due to: [${reason}]`, event);
50743
- }
50744
- return;
50745
- }
50746
- const eventData = tryParseMessage(event);
50747
- debug$1.debug(`[experiences-visual-editor-react::onMessage] Received message [${eventData.eventType}]`, eventData);
50748
- if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
50749
- // Expected message: This message is handled in the EntityStore to store fetched entities
50750
- return;
50751
- }
50752
- switch (eventData.eventType) {
50753
- case INCOMING_EVENTS.ExperienceUpdated: {
50754
- const { tree, locale, changedNode, changedValueType, assemblies } = eventData.payload;
50755
- // Make sure to first store the assemblies before setting the tree and thus triggering a rerender
50756
- if (assemblies) {
50757
- setAssemblies(assemblies);
50758
- // If the assemblyEntry is not yet fetched, this will be done below by
50759
- // the imperative calls to fetchMissingEntities.
50760
- }
50761
- let newEntityStore = entityStore;
50762
- if (entityStore.locale !== locale) {
50763
- newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
50764
- setLocale(locale);
50765
- resetEntityStore(newEntityStore);
50766
- }
50767
- // Below are mutually exclusive cases
50768
- if (changedNode) {
50769
- /**
50770
- * On single node updates, we want to skip the process of getting the data (datasource and unbound values)
50771
- * from tree. Since we know the updated node, we can skip that recursion everytime the tree updates and
50772
- * just update the relevant data we need from the relevant node.
50773
- *
50774
- * We still update the tree here so we don't have a stale "tree"
50775
- */
50776
- if (changedValueType === 'BoundValue') {
50777
- const newDataSource = { ...dataSource, ...changedNode.data.dataSource };
50778
- setDataSource(newDataSource);
50779
- await fetchMissingEntities(newEntityStore, newDataSource, tree);
50780
- }
50781
- else if (changedValueType === 'UnboundValue') {
50782
- setUnboundValues({
50783
- ...unboundValues,
50784
- ...changedNode.data.unboundValues,
50785
- });
50786
- }
50787
- }
50788
- else {
50789
- const { dataSource, unboundValues } = getDataFromTree(tree);
50790
- setDataSource(dataSource);
50791
- setUnboundValues(unboundValues);
50792
- await fetchMissingEntities(newEntityStore, dataSource, tree);
50793
- }
50794
- // Update the tree when all necessary data is fetched and ready for rendering.
50795
- updateTree(tree);
50796
- break;
50797
- }
50798
- case INCOMING_EVENTS.AssembliesRegistered: {
50799
- const { assemblies } = eventData.payload;
50800
- assemblies.forEach((definition) => {
50801
- addComponentRegistration({
50802
- component: Assembly,
50803
- definition,
50804
- });
50805
- });
50806
- break;
50807
- }
50808
- case INCOMING_EVENTS.AssembliesAdded: {
50809
- const { assembly, assemblyDefinition, } = eventData.payload;
50810
- entityStore.updateEntity(assembly);
50811
- // Using a Map here to avoid setting state and rerending all existing assemblies when a new assembly is added
50812
- // TODO: Figure out if we can extend this love to data source and unbound values. Maybe that'll solve the blink
50813
- // of all bound and unbound values when new values are added
50814
- assembliesRegistry.set(assembly.sys.id, {
50815
- sys: { id: assembly.sys.id, linkType: 'Entry', type: 'Link' },
50816
- });
50817
- if (assemblyDefinition) {
50818
- addComponentRegistration({
50819
- component: Assembly,
50820
- definition: assemblyDefinition,
50821
- });
50822
- }
50823
- break;
50824
- }
50825
- case INCOMING_EVENTS.UpdatedEntity: {
50826
- const { entity: updatedEntity, shouldRerender } = eventData.payload;
50827
- if (updatedEntity) {
50828
- const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
50829
- const didEntityChange = storedEntity?.sys.version !== updatedEntity.sys.version;
50830
- entityStore.updateEntity(updatedEntity);
50831
- // We traverse the whole tree, so this is a opt-in feature to only use it when required.
50832
- if (shouldRerender && didEntityChange) {
50833
- updateNodesByUpdatedEntity(updatedEntity.sys.id);
50834
- }
50835
- }
50836
- break;
50837
- }
50838
- case INCOMING_EVENTS.RequestEditorMode: {
50839
- break;
50840
- }
50841
- default: {
50842
- const knownEvents = Object.values(INCOMING_EVENTS);
50843
- const isDeprecatedMessage = knownEvents.includes(eventData.eventType);
50844
- if (!isDeprecatedMessage) {
50845
- debug$1.error(`[experiences-visual-editor-react::onMessage] Logic error, unsupported eventType: [${eventData.eventType}]`);
50846
- }
50847
- }
50848
- }
50849
- };
50850
- window.addEventListener('message', onMessage);
50851
- return () => {
50852
- window.removeEventListener('message', onMessage);
50853
- };
50854
- }, [
50855
- entityStore,
50856
- setDataSource,
50857
- setLocale,
50858
- dataSource,
50859
- areEntitiesFetched,
50860
- fetchMissingEntities,
50861
- setUnboundValues,
50862
- unboundValues,
50863
- updateTree,
50864
- updateNodesByUpdatedEntity,
50865
- resetEntityStore,
50866
- ]);
50867
- }
50868
-
50869
- const CircularDependencyErrorPlaceholder = ({ wrappingPatternIds, ...props }) => {
50870
- return (React$1.createElement("div", { ...props, "data-cf-node-error": "circular-pattern-dependency", style: {
50871
- border: '1px solid red',
50872
- background: 'rgba(255, 0, 0, 0.1)',
50873
- padding: '1rem 1rem 0 1rem',
50874
- width: '100%',
50875
- height: '100%',
50876
- } },
50877
- "Circular usage of patterns detected:",
50878
- React$1.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
50879
- const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
50880
- const entry = inMemoryEntities.maybeResolveLink(entryLink);
50881
- const entryTitle = entry?.fields?.title;
50882
- const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
50883
- return React$1.createElement("li", { key: patternId }, text);
50884
- }))));
50885
- };
50886
-
50887
- class ImportedComponentError extends Error {
50888
- constructor(message) {
50889
- super(message);
50890
- this.name = 'ImportedComponentError';
50891
- }
50892
- }
50893
- class ExperienceSDKError extends Error {
50894
- constructor(message) {
50895
- super(message);
50896
- this.name = 'ExperienceSDKError';
50897
- }
50898
- }
50899
- class ImportedComponentErrorBoundary extends React$1.Component {
50900
- componentDidCatch(error, _errorInfo) {
50901
- if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
50902
- // This error was already handled by a nested error boundary and should be passed upwards
50903
- // We have to do this as we wrap every component on every layer with this error boundary and
50904
- // thus an error deep in the tree bubbles through many layers of error boundaries.
50905
- throw error;
50906
- }
50907
- // Differentiate between custom and SDK-provided components for error tracking
50908
- const ErrorClass = isContentfulComponent(this.props.componentId)
50909
- ? ExperienceSDKError
50910
- : ImportedComponentError;
50911
- const err = new ErrorClass(error.message);
50912
- err.stack = error.stack;
50913
- throw err;
50914
- }
50915
- render() {
50916
- return this.props.children;
50917
- }
50918
- }
50919
-
50920
- const MissingComponentPlaceholder = ({ blockId }) => {
50921
- return (React$1.createElement("div", { style: {
50922
- border: '1px solid red',
50923
- width: '100%',
50924
- height: '100%',
50925
- } },
50926
- "Missing component '",
50927
- blockId,
50928
- "'"));
50929
- };
50930
-
50931
- var css_248z$2 = ".EditorBlock-module_emptySlot__za-Bi {\n min-height: 80px;\n min-width: 80px;\n}\n";
50932
- var styles$1 = {"emptySlot":"EditorBlock-module_emptySlot__za-Bi"};
50933
- styleInject(css_248z$2);
50934
-
50935
50897
  const useComponentRegistration = (node) => {
50936
50898
  return reactExports.useMemo(() => {
50937
- let registration = componentRegistry.get(node.data.blockId);
50938
- if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
50939
- registration = createAssemblyRegistration({
50940
- definitionId: node.data.blockId,
50899
+ if (node.type === ASSEMBLY_NODE_TYPE) {
50900
+ // The definition and component are the same for all assemblies
50901
+ return {
50941
50902
  component: Assembly,
50942
- });
50903
+ definition: createAssemblyDefinition(node.data.blockId),
50904
+ };
50943
50905
  }
50906
+ const registration = componentRegistry.get(node.data.blockId);
50944
50907
  if (!registration) {
50945
50908
  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.`);
50946
50909
  return undefined;