@contentful/experiences-visual-editor-react 1.42.3 → 2.0.0-beta.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,10 +1,10 @@
1
1
  import styleInject from 'style-inject';
2
2
  import React, { useEffect, useRef, useState, useCallback, forwardRef, useLayoutEffect, useMemo } from 'react';
3
3
  import { z } from 'zod';
4
- import { omit, isArray, isEqual, get as get$1 } from 'lodash-es';
4
+ import { omit, isArray, isEqual, get as get$2 } from 'lodash-es';
5
5
  import md5 from 'md5';
6
6
  import { BLOCKS } from '@contentful/rich-text-types';
7
- import { create } from 'zustand';
7
+ import { create, useStore } from 'zustand';
8
8
  import { Droppable, Draggable, DragDropContext } from '@hello-pangea/dnd';
9
9
  import { produce } from 'immer';
10
10
  import '@contentful/rich-text-react-renderer';
@@ -126,14 +126,6 @@ const CF_STYLE_ATTRIBUTES = [
126
126
  'cfTextBold',
127
127
  'cfTextItalic',
128
128
  'cfTextUnderline',
129
- // For backwards compatibility
130
- // we need to keep those in this constant array
131
- // so that omit() in <VisualEditorBlock> and <CompositionBlock>
132
- // can filter them out and not pass as props
133
- 'cfBackgroundImageScaling',
134
- 'cfBackgroundImageAlignment',
135
- 'cfBackgroundImageAlignmentVertical',
136
- 'cfBackgroundImageAlignmentHorizontal',
137
129
  ];
138
130
  const EMPTY_CONTAINER_HEIGHT$1 = '80px';
139
131
  const DEFAULT_IMAGE_WIDTH = '500px';
@@ -1105,6 +1097,24 @@ propertyName, resolveDesignTokens = true) => {
1105
1097
  return valuesByBreakpoint;
1106
1098
  }
1107
1099
  };
1100
+ /** Overwrites the default value breakpoint by breakpoint. If a breakpoint
1101
+ * is not overwritten, it will fall back to the default. */
1102
+ function mergeDesignValuesByBreakpoint(defaultValue, overwriteValue) {
1103
+ if (!defaultValue || !overwriteValue) {
1104
+ return defaultValue ?? overwriteValue;
1105
+ }
1106
+ const mergedValuesByBreakpoint = { ...defaultValue.valuesByBreakpoint };
1107
+ for (const [breakpointId, value] of Object.entries(overwriteValue.valuesByBreakpoint)) {
1108
+ if (!isValidBreakpointValue(value)) {
1109
+ continue;
1110
+ }
1111
+ mergedValuesByBreakpoint[breakpointId] = value;
1112
+ }
1113
+ return {
1114
+ type: 'DesignValue',
1115
+ valuesByBreakpoint: mergedValuesByBreakpoint,
1116
+ };
1117
+ }
1108
1118
 
1109
1119
  const CF_DEBUG_KEY$1 = 'cf_debug';
1110
1120
  /**
@@ -1226,11 +1236,19 @@ const getElementCoordinates = (element) => {
1226
1236
  });
1227
1237
  };
1228
1238
 
1229
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1230
1239
  const isLinkToAsset = (variable) => {
1231
- if (!variable)
1240
+ if (variable === null || typeof variable !== 'object')
1241
+ return false;
1242
+ // The `'prop' in` pattern is informing TypeScript of the object shape, no need to cast `as`.
1243
+ if (!('sys' in variable))
1232
1244
  return false;
1233
- if (typeof variable !== 'object')
1245
+ if (variable.sys === null || typeof variable.sys !== 'object')
1246
+ return false;
1247
+ if (!('linkType' in variable.sys))
1248
+ return false;
1249
+ if (!('id' in variable.sys))
1250
+ return false;
1251
+ if (!('type' in variable.sys))
1234
1252
  return false;
1235
1253
  return (variable.sys?.linkType === 'Asset' &&
1236
1254
  typeof variable.sys?.id === 'string' &&
@@ -1238,20 +1256,20 @@ const isLinkToAsset = (variable) => {
1238
1256
  variable.sys?.type === 'Link');
1239
1257
  };
1240
1258
 
1241
- const isLink = (maybeLink) => {
1259
+ const isLink$1 = (maybeLink) => {
1242
1260
  if (maybeLink === null)
1243
1261
  return false;
1244
1262
  if (typeof maybeLink !== 'object')
1245
1263
  return false;
1246
1264
  const link = maybeLink;
1247
- return Boolean(link.sys?.id) && link.sys?.type === 'Link';
1265
+ return Boolean(link.sys?.id) && link.sys?.type === 'Link' && Boolean(link.sys?.linkType);
1248
1266
  };
1249
1267
 
1250
1268
  /**
1251
1269
  * This module encapsulates format of the path to a deep reference.
1252
1270
  */
1253
- const parseDataSourcePathIntoFieldset = (path) => {
1254
- const parsedPath = parseDeepPath(path);
1271
+ const parseDataSourcePathIntoFieldset$1 = (path) => {
1272
+ const parsedPath = parseDeepPath$1(path);
1255
1273
  if (null === parsedPath) {
1256
1274
  throw new Error(`Cannot parse path '${path}' as deep path`);
1257
1275
  }
@@ -1264,7 +1282,7 @@ const parseDataSourcePathIntoFieldset = (path) => {
1264
1282
  * @returns
1265
1283
  */
1266
1284
  const parseDataSourcePathWithL1DeepBindings = (path) => {
1267
- const parsedPath = parseDeepPath(path);
1285
+ const parsedPath = parseDeepPath$1(path);
1268
1286
  if (null === parsedPath) {
1269
1287
  throw new Error(`Cannot parse path '${path}' as deep path`);
1270
1288
  }
@@ -1281,14 +1299,14 @@ const parseDataSourcePathWithL1DeepBindings = (path) => {
1281
1299
  * - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
1282
1300
  * @returns
1283
1301
  */
1284
- const isDeepPath = (deepPathCandidate) => {
1285
- const deepPathParsed = parseDeepPath(deepPathCandidate);
1302
+ const isDeepPath$1 = (deepPathCandidate) => {
1303
+ const deepPathParsed = parseDeepPath$1(deepPathCandidate);
1286
1304
  if (!deepPathParsed) {
1287
1305
  return false;
1288
1306
  }
1289
1307
  return deepPathParsed.fields.length > 1;
1290
1308
  };
1291
- const parseDeepPath = (deepPathCandidate) => {
1309
+ const parseDeepPath$1 = (deepPathCandidate) => {
1292
1310
  // ALGORITHM:
1293
1311
  // We start with deep path in form:
1294
1312
  // /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
@@ -1314,7 +1332,7 @@ const parseDeepPath = (deepPathCandidate) => {
1314
1332
  return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
1315
1333
  };
1316
1334
  const deepPathSegments = deepPathCandidate.split('/');
1317
- const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
1335
+ const chunks = chunkSegments$1(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
1318
1336
  if (chunks.length <= 1) {
1319
1337
  return null; // malformed path, even regular paths have at least 2 chunks
1320
1338
  }
@@ -1334,7 +1352,7 @@ const parseDeepPath = (deepPathCandidate) => {
1334
1352
  fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
1335
1353
  };
1336
1354
  };
1337
- const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
1355
+ const chunkSegments$1 = (segments, { startNextChunkOnElementEqualTo }) => {
1338
1356
  const chunks = [];
1339
1357
  let currentChunk = [];
1340
1358
  const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
@@ -1435,11 +1453,6 @@ const transformVisibility = (value) => {
1435
1453
  // Don't explicitly set anything when visible to not overwrite values like `grid` or `flex`.
1436
1454
  return {};
1437
1455
  };
1438
- // TODO: Remove in next major version v2 since the change is 17 months old
1439
- // Keep this for backwards compatibility - deleting this would be a breaking change
1440
- // because existing components on a users experience will have the width value as fill
1441
- // rather than 100%
1442
- const transformFill = (value) => (value === 'fill' ? '100%' : value);
1443
1456
  const transformGridColumn = (span) => {
1444
1457
  if (!span) {
1445
1458
  return {};
@@ -1484,34 +1497,6 @@ const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions
1484
1497
  return;
1485
1498
  }
1486
1499
  let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
1487
- // Special case for handling single values
1488
- // for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
1489
- if (horizontalAlignment && !verticalAlignment) {
1490
- const singleValue = horizontalAlignment;
1491
- switch (singleValue) {
1492
- case 'left':
1493
- horizontalAlignment = 'left';
1494
- verticalAlignment = 'center';
1495
- break;
1496
- case 'right':
1497
- horizontalAlignment = 'right';
1498
- verticalAlignment = 'center';
1499
- break;
1500
- case 'center':
1501
- horizontalAlignment = 'center';
1502
- verticalAlignment = 'center';
1503
- break;
1504
- case 'top':
1505
- horizontalAlignment = 'center';
1506
- verticalAlignment = 'top';
1507
- break;
1508
- case 'bottom':
1509
- horizontalAlignment = 'center';
1510
- verticalAlignment = 'bottom';
1511
- break;
1512
- // just fall down to the normal validation logic for horiz and vert
1513
- }
1514
- }
1515
1500
  const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
1516
1501
  const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
1517
1502
  horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
@@ -1596,8 +1581,8 @@ const buildCfStyles = (values) => {
1596
1581
  margin: values.cfMargin,
1597
1582
  padding: values.cfPadding,
1598
1583
  backgroundColor: values.cfBackgroundColor,
1599
- width: transformFill(values.cfWidth || values.cfImageOptions?.width),
1600
- height: transformFill(values.cfHeight || values.cfImageOptions?.height),
1584
+ width: values.cfWidth || values.cfImageOptions?.width,
1585
+ height: values.cfHeight || values.cfImageOptions?.height,
1601
1586
  maxWidth: values.cfMaxWidth,
1602
1587
  ...transformGridColumn(values.cfColumnSpan),
1603
1588
  ...transformBorderStyle(values.cfBorder),
@@ -1703,13 +1688,13 @@ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', fo
1703
1688
  };
1704
1689
 
1705
1690
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1706
- function get(obj, path) {
1691
+ function get$1(obj, path) {
1707
1692
  if (!path.length) {
1708
1693
  return obj;
1709
1694
  }
1710
1695
  try {
1711
1696
  const [currentPath, ...nextPath] = path;
1712
- return get(obj[currentPath], nextPath);
1697
+ return get$1(obj[currentPath], nextPath);
1713
1698
  }
1714
1699
  catch (err) {
1715
1700
  return undefined;
@@ -1717,7 +1702,7 @@ function get(obj, path) {
1717
1702
  }
1718
1703
 
1719
1704
  const getBoundValue = (entryOrAsset, path) => {
1720
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1705
+ const value = get$1(entryOrAsset, path.split('/').slice(2, -1));
1721
1706
  return value && typeof value == 'object' && value.url
1722
1707
  ? value.url
1723
1708
  : value;
@@ -1747,7 +1732,9 @@ const transformRichText = (entryOrAsset, entityStore, path) => {
1747
1732
  }
1748
1733
  if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
1749
1734
  // resolve any links to assets/entries/hyperlinks
1750
- const richTextDocument = value;
1735
+ // we need to clone, as we want to keep the original Entity in the EntityStore intact,
1736
+ // and resolveLinks() is mutating the node object.
1737
+ const richTextDocument = structuredClone(value);
1751
1738
  resolveLinks(richTextDocument, entityStore);
1752
1739
  return richTextDocument;
1753
1740
  }
@@ -1802,7 +1789,7 @@ const transformMedia = (asset, variables, resolveDesignValue, variableName, path
1802
1789
  let value;
1803
1790
  // If it is not a deep path and not pointing to the file of the asset,
1804
1791
  // it is just pointing to a normal field and therefore we just resolve the value as normal field
1805
- if (!isDeepPath(path) && !lastPathNamedSegmentEq(path, 'file')) {
1792
+ if (!isDeepPath$1(path) && !lastPathNamedSegmentEq(path, 'file')) {
1806
1793
  return getBoundValue(asset, path);
1807
1794
  }
1808
1795
  //TODO: this will be better served by injectable type transformers instead of if statement
@@ -1860,104 +1847,97 @@ const transformMedia = (asset, variables, resolveDesignValue, variableName, path
1860
1847
  }
1861
1848
  return asset.fields.file?.url;
1862
1849
  };
1863
-
1864
- const isAsset = (value) => {
1850
+ const isEntry$1 = (value) => {
1865
1851
  return (null !== value &&
1866
1852
  typeof value === 'object' &&
1867
1853
  'sys' in value &&
1868
- value.sys?.type === 'Asset');
1854
+ value.sys?.type === 'Entry');
1869
1855
  };
1870
- const isEntry = (value) => {
1856
+ const isAsset$1 = (value) => {
1871
1857
  return (null !== value &&
1872
1858
  typeof value === 'object' &&
1873
1859
  'sys' in value &&
1874
- value.sys?.type === 'Entry');
1860
+ value.sys?.type === 'Asset');
1875
1861
  };
1876
1862
 
1877
1863
  function getResolvedEntryFromLink(entryOrAsset, path, entityStore) {
1878
- if (isAsset(entryOrAsset)) {
1864
+ if (isAsset$1(entryOrAsset)) {
1879
1865
  return entryOrAsset;
1880
1866
  }
1881
- else if (!isEntry(entryOrAsset)) {
1867
+ else if (!isEntry$1(entryOrAsset)) {
1882
1868
  throw new Error(`Expected an Entry or Asset, but got: ${JSON.stringify(entryOrAsset)}`);
1883
1869
  }
1884
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1870
+ const fieldName = path.split('/').slice(2, -1);
1871
+ const value = get$1(entryOrAsset, fieldName);
1885
1872
  let resolvedEntity;
1886
- if (isAsset(value) || isEntry(value)) {
1873
+ if (isAsset$1(value) || isEntry$1(value)) {
1887
1874
  // In some cases, reference fields are already resolved
1888
1875
  resolvedEntity = value;
1889
1876
  }
1890
1877
  else if (value?.sys.type === 'Link') {
1891
1878
  // Look up the reference in the entity store
1892
1879
  resolvedEntity = entityStore.getEntityFromLink(value);
1893
- if (!resolvedEntity) {
1894
- return;
1895
- }
1896
1880
  }
1897
1881
  else {
1898
- console.warn(`Expected a link to a reference, but got: ${JSON.stringify(value)}`);
1882
+ console.warn(`When attempting to follow link in field '${fieldName}' of entity, the value is expected to be a link, but got: ${JSON.stringify(value)}`, { entity: entryOrAsset });
1883
+ return;
1884
+ }
1885
+ // no need to make structuredClone(entityStore.getEntityFromLink(value)) because
1886
+ // we provide component with the original Object.frozen object of the entity.
1887
+ // As we don't resolve L3 and don't mutate the entity before returning anymore,
1888
+ // we don't need to make a copy of the entity. And even provide better referential integrity
1889
+ // for the component for the same entity.
1890
+ if (!resolvedEntity) {
1899
1891
  return;
1900
1892
  }
1901
- //resolve any embedded links - we currently only support 2 levels deep
1902
- const fields = resolvedEntity.fields || {};
1903
- Object.entries(fields).forEach(([fieldKey, field]) => {
1904
- if (field && field.sys?.type === 'Link') {
1905
- const entity = entityStore.getEntityFromLink(field);
1906
- if (entity) {
1907
- resolvedEntity.fields[fieldKey] = entity;
1908
- }
1909
- }
1910
- else if (field && Array.isArray(field)) {
1911
- resolvedEntity.fields[fieldKey] = field.map((innerField) => {
1912
- if (innerField && innerField.sys?.type === 'Link') {
1913
- const entity = entityStore.getEntityFromLink(innerField);
1914
- if (entity) {
1915
- return entity;
1916
- }
1917
- }
1918
- return innerField;
1919
- });
1920
- }
1921
- });
1922
1893
  return resolvedEntity;
1923
1894
  }
1924
1895
 
1896
+ const excludeUndefined = (value) => {
1897
+ return value !== undefined;
1898
+ };
1925
1899
  function getArrayValue(entryOrAsset, path, entityStore) {
1900
+ // NOTE: Not sure if we need this if-statement,
1901
+ // as it is NOT possible to bind to Array variable an Asset
1902
+ // (as Assets don't have multi-reference fields) unless it's a degenerate case.
1926
1903
  if (entryOrAsset.sys.type === 'Asset') {
1927
1904
  return entryOrAsset;
1928
1905
  }
1929
- const arrayValue = get(entryOrAsset, path.split('/').slice(2, -1));
1906
+ const fieldName = path.split('/').slice(2, -1);
1907
+ const arrayValue = get$1(entryOrAsset, fieldName);
1930
1908
  if (!isArray(arrayValue)) {
1931
- console.warn(`Expected a value to be an array, but got: ${JSON.stringify(arrayValue)}`);
1909
+ console.warn(`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 });
1932
1910
  return;
1933
1911
  }
1934
- const result = arrayValue.map((value) => {
1912
+ const result = arrayValue
1913
+ .map((value) => {
1935
1914
  if (typeof value === 'string') {
1936
- return value;
1915
+ return value; // handles case where Text array is bound (in [Content Model] tab of the platform, select Text and make it a list)
1937
1916
  }
1938
1917
  else if (value?.sys?.type === 'Link') {
1939
1918
  const resolvedEntity = entityStore.getEntityFromLink(value);
1940
1919
  if (!resolvedEntity) {
1920
+ // We return undefined, which means that entity wasn't availble in the Entity Store due to:
1921
+ // - because it's archived entity (and they normally wouldn't be sent to the Entity Store)
1922
+ // - bug where some entity wasn't added to the Entity Store
1923
+ // BTW, deleted entities shouldn't even be possible here as they require CT deletion first and that shouldn't allow us to load them at all)
1941
1924
  return;
1942
1925
  }
1943
- //resolve any embedded links - we currently only support 2 levels deep
1944
- const fields = resolvedEntity.fields || {};
1945
- Object.entries(fields).forEach(([fieldKey, field]) => {
1946
- if (field && field.sys?.type === 'Link') {
1947
- const entity = entityStore.getEntityFromLink(field);
1948
- if (entity) {
1949
- resolvedEntity.fields[fieldKey] = entity;
1950
- }
1951
- }
1952
- });
1953
1926
  return resolvedEntity;
1954
1927
  }
1955
1928
  else {
1956
1929
  console.warn(`Expected value to be a string or Link, but got: ${JSON.stringify(value)}`);
1957
1930
  return undefined;
1958
1931
  }
1959
- });
1960
- return result;
1932
+ })
1933
+ .filter(excludeUndefined);
1934
+ // eg. imagine you have multi-referene field with 3 links to archived entries,
1935
+ // all of them will be undefined on previous step and will be filtered out
1936
+ // of resultWithoutUndefined. Instead of passing to component an empty array,
1937
+ // we pass undefined. This means that develloper making custom component
1938
+ // does not have to handle empty array case. But only undefiened, which signals:
1939
+ // user didn't bind anything; user bound to reference field which is unset; all references are archived
1940
+ return result.length > 0 ? result : undefined;
1961
1941
  }
1962
1942
 
1963
1943
  const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableType, path) => {
@@ -2116,11 +2096,22 @@ const sendMessage = (eventType, data) => {
2116
2096
  }, '*');
2117
2097
  };
2118
2098
 
2099
+ function deepFreeze$1(obj) {
2100
+ const propNames = Object.getOwnPropertyNames(obj);
2101
+ for (const name of propNames) {
2102
+ const value = obj[name];
2103
+ if (value && typeof value === 'object') {
2104
+ deepFreeze$1(value);
2105
+ }
2106
+ }
2107
+ return Object.freeze(obj);
2108
+ }
2109
+
2119
2110
  /**
2120
2111
  * Base Store for entities
2121
2112
  * Can be extended for the different loading behaviours (editor, production, ..)
2122
2113
  */
2123
- class EntityStoreBase {
2114
+ let EntityStoreBase$1 = class EntityStoreBase {
2124
2115
  constructor({ entities, locale }) {
2125
2116
  /* serialized */ this.entryMap = new Map();
2126
2117
  /* serialized */ this.assetMap = new Map();
@@ -2136,11 +2127,11 @@ class EntityStoreBase {
2136
2127
  this.addEntity(entity);
2137
2128
  }
2138
2129
  getEntryOrAsset(linkOrEntryOrAsset, path) {
2139
- if (isDeepPath(path)) {
2130
+ if (isDeepPath$1(path)) {
2140
2131
  return this.getDeepEntry(linkOrEntryOrAsset, path);
2141
2132
  }
2142
2133
  let entity;
2143
- if (isLink(linkOrEntryOrAsset)) {
2134
+ if (isLink$1(linkOrEntryOrAsset)) {
2144
2135
  const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
2145
2136
  ? this.entryMap.get(linkOrEntryOrAsset.sys.id)
2146
2137
  : this.assetMap.get(linkOrEntryOrAsset.sys.id);
@@ -2150,7 +2141,7 @@ class EntityStoreBase {
2150
2141
  }
2151
2142
  entity = resolvedEntity;
2152
2143
  }
2153
- else if (isAsset(linkOrEntryOrAsset) || isEntry(linkOrEntryOrAsset)) {
2144
+ else if (isAsset$1(linkOrEntryOrAsset) || isEntry$1(linkOrEntryOrAsset)) {
2154
2145
  // We already have the complete entity in preview & delivery (resolved by the CMA client)
2155
2146
  entity = linkOrEntryOrAsset;
2156
2147
  }
@@ -2172,7 +2163,7 @@ class EntityStoreBase {
2172
2163
  console.warn(`Unresolved entity reference: ${entityLink.sys.linkType} with ID ${entityLink.sys.id}`);
2173
2164
  return;
2174
2165
  }
2175
- return get(entity, path);
2166
+ return get$1(entity, path);
2176
2167
  }
2177
2168
  getEntityFromLink(link) {
2178
2169
  const resolvedEntity = link.sys.linkType === 'Entry'
@@ -2184,6 +2175,22 @@ class EntityStoreBase {
2184
2175
  }
2185
2176
  return resolvedEntity;
2186
2177
  }
2178
+ getAssetById(assetId) {
2179
+ const asset = this.assetMap.get(assetId);
2180
+ if (!asset) {
2181
+ console.warn(`Asset with ID "${assetId}" is not found in the store`);
2182
+ return;
2183
+ }
2184
+ return asset;
2185
+ }
2186
+ getEntryById(entryId) {
2187
+ const entry = this.entryMap.get(entryId);
2188
+ if (!entry) {
2189
+ console.warn(`Entry with ID "${entryId}" is not found in the store`);
2190
+ return;
2191
+ }
2192
+ return entry;
2193
+ }
2187
2194
  getEntitiesFromMap(type, ids) {
2188
2195
  const resolved = [];
2189
2196
  const missing = [];
@@ -2202,11 +2209,13 @@ class EntityStoreBase {
2202
2209
  };
2203
2210
  }
2204
2211
  addEntity(entity) {
2205
- if (isAsset(entity)) {
2206
- this.assetMap.set(entity.sys.id, entity);
2212
+ if (isAsset$1(entity)) {
2213
+ // cloned and frozen
2214
+ this.assetMap.set(entity.sys.id, deepFreeze$1(structuredClone(entity)));
2207
2215
  }
2208
- else if (isEntry(entity)) {
2209
- this.entryMap.set(entity.sys.id, entity);
2216
+ else if (isEntry$1(entity)) {
2217
+ // cloned and frozen
2218
+ this.entryMap.set(entity.sys.id, deepFreeze$1(structuredClone(entity)));
2210
2219
  }
2211
2220
  else {
2212
2221
  throw new Error(`Attempted to add an entity to the store that is neither Asset nor Entry: '${JSON.stringify(entity)}'`);
@@ -2259,7 +2268,7 @@ class EntityStoreBase {
2259
2268
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2260
2269
  break;
2261
2270
  }
2262
- const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
2271
+ const fieldValue = get$1(entityToResolveFieldsFrom, ['fields', field]);
2263
2272
  if (undefined === fieldValue) {
2264
2273
  return {
2265
2274
  resolvedFieldset,
@@ -2267,7 +2276,7 @@ class EntityStoreBase {
2267
2276
  reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
2268
2277
  };
2269
2278
  }
2270
- else if (isLink(fieldValue)) {
2279
+ else if (isLink$1(fieldValue)) {
2271
2280
  const entity = this.getEntityFromLink(fieldValue);
2272
2281
  if (entity === undefined) {
2273
2282
  return {
@@ -2279,7 +2288,7 @@ class EntityStoreBase {
2279
2288
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2280
2289
  entityToResolveFieldsFrom = entity; // we move up
2281
2290
  }
2282
- else if (isAsset(fieldValue) || isEntry(fieldValue)) {
2291
+ else if (isAsset$1(fieldValue) || isEntry$1(fieldValue)) {
2283
2292
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2284
2293
  entityToResolveFieldsFrom = fieldValue; // we move up
2285
2294
  }
@@ -2296,13 +2305,13 @@ class EntityStoreBase {
2296
2305
  isFullyResolved: true,
2297
2306
  };
2298
2307
  };
2299
- const headEntity = isLink(linkOrEntryOrAsset)
2308
+ const headEntity = isLink$1(linkOrEntryOrAsset)
2300
2309
  ? this.getEntityFromLink(linkOrEntryOrAsset)
2301
2310
  : linkOrEntryOrAsset;
2302
2311
  if (undefined === headEntity) {
2303
2312
  return;
2304
2313
  }
2305
- const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
2314
+ const unresolvedFieldset = parseDataSourcePathIntoFieldset$1(path);
2306
2315
  // The purpose here is to take this intermediate representation of the deep-path
2307
2316
  // and to follow the links to the leaf-entity and field
2308
2317
  // in case we can't follow till the end, we should signal that there was null-reference in the path
@@ -2328,13 +2337,13 @@ class EntityStoreBase {
2328
2337
  locale: this.locale,
2329
2338
  };
2330
2339
  }
2331
- }
2340
+ };
2332
2341
 
2333
2342
  /**
2334
2343
  * EntityStore which resolves entries and assets from the editor
2335
2344
  * over the sendMessage and subscribe functions.
2336
2345
  */
2337
- class EditorEntityStore extends EntityStoreBase {
2346
+ class EditorEntityStore extends EntityStoreBase$1 {
2338
2347
  constructor({ entities, locale, sendMessage, subscribe, timeoutDuration = 3000, }) {
2339
2348
  super({ entities, locale });
2340
2349
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2471,7 +2480,6 @@ class EditorModeEntityStore extends EditorEntityStore {
2471
2480
  };
2472
2481
  };
2473
2482
  super({ entities, sendMessage, subscribe, locale, timeoutDuration: REQUEST_TIMEOUT });
2474
- this.locale = locale;
2475
2483
  }
2476
2484
  /**
2477
2485
  * This function collects and returns the list of requested entries and assets. Additionally, it checks
@@ -2501,11 +2509,91 @@ class EditorModeEntityStore extends EditorEntityStore {
2501
2509
  if (!entity) {
2502
2510
  return;
2503
2511
  }
2504
- const fieldValue = get(entity, path);
2512
+ const fieldValue = get$1(entity, path);
2505
2513
  return transformAssetFileToUrl(fieldValue);
2506
2514
  }
2507
2515
  }
2508
2516
 
2517
+ let UninitializedEntityStore$1 = class UninitializedEntityStore extends EntityStoreBase$1 {
2518
+ constructor() {
2519
+ super({ entities: [], locale: 'uninitialized-locale-in-uninitialized-entity-store' });
2520
+ }
2521
+ };
2522
+
2523
+ const inMemoryEntitiesStore = create((set, get) => ({
2524
+ // The UninitializedEntityStore is a placeholder instance and is here to highlight the
2525
+ // // fact that it's not used by anything until during loading lifecycle it'sreplaced by real entity store:
2526
+ // - in Preview+Delivery mode: right after we fetch Expereince and it entities
2527
+ // - in EDITOR (VisualEditor) mode: right after the VisualEditor is async imported and initialize event happens
2528
+ entityStore: new UninitializedEntityStore$1(),
2529
+ areEntitiesFetched: false,
2530
+ setEntitiesFetched(fetched) {
2531
+ set({ areEntitiesFetched: fetched });
2532
+ },
2533
+ resolveAssetById(assetId) {
2534
+ if (!assetId)
2535
+ return undefined;
2536
+ const { entityStore } = get();
2537
+ return entityStore.getAssetById(assetId);
2538
+ },
2539
+ resolveEntryById(entryId) {
2540
+ if (!entryId)
2541
+ return undefined;
2542
+ const { entityStore } = get();
2543
+ return entityStore.getEntryById(entryId);
2544
+ },
2545
+ resolveEntity(link) {
2546
+ if (!link)
2547
+ return undefined;
2548
+ const { entityStore } = get();
2549
+ return entityStore.getEntityFromLink(link);
2550
+ },
2551
+ resetEntityStore(entityStore) {
2552
+ set({
2553
+ entityStore,
2554
+ areEntitiesFetched: false,
2555
+ });
2556
+ },
2557
+ }));
2558
+
2559
+ function maybeResolveLink(maybeLink) {
2560
+ if (!isLink$1(maybeLink)) {
2561
+ console.warn('maybeResolveLink function must receive Link shape. Provided argument does not match the Link shape: ', maybeLink);
2562
+ return undefined;
2563
+ }
2564
+ return inMemoryEntitiesStore.getState().resolveEntity(maybeLink);
2565
+ }
2566
+ function maybeResolveByAssetId(assetId) {
2567
+ return inMemoryEntitiesStore.getState().resolveAssetById(assetId);
2568
+ }
2569
+ function maybeResolveByEntryId(entryId) {
2570
+ return inMemoryEntitiesStore.getState().resolveEntryById(entryId);
2571
+ }
2572
+ function hasEntry(entryId) {
2573
+ return Boolean(maybeResolveByEntryId(entryId));
2574
+ }
2575
+ function hasAsset(assetId) {
2576
+ return Boolean(maybeResolveByAssetId(assetId));
2577
+ }
2578
+ function addEntities(entities) {
2579
+ if (!Array.isArray(entities) || entities.length === 0) {
2580
+ return;
2581
+ }
2582
+ const { entityStore } = inMemoryEntitiesStore.getState();
2583
+ const definedEntities = entities.filter(Boolean);
2584
+ for (const entity of definedEntities) {
2585
+ entityStore.updateEntity(entity);
2586
+ }
2587
+ }
2588
+ const inMemoryEntities = {
2589
+ maybeResolveLink,
2590
+ maybeResolveByAssetId,
2591
+ maybeResolveByEntryId,
2592
+ hasEntry,
2593
+ hasAsset,
2594
+ addEntities,
2595
+ };
2596
+
2509
2597
  var VisualEditorMode$1;
2510
2598
  (function (VisualEditorMode) {
2511
2599
  VisualEditorMode["LazyLoad"] = "lazyLoad";
@@ -2535,7 +2623,7 @@ class DeepReference {
2535
2623
  // field references nothing (or even field doesn't exist)
2536
2624
  return undefined;
2537
2625
  }
2538
- if (!isLink(maybeReferentLink)) {
2626
+ if (!isLink$1(maybeReferentLink)) {
2539
2627
  // Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
2540
2628
  // Under normal circumstance we expect field to be a Link, but it could be an "impostor"
2541
2629
  // eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
@@ -2555,7 +2643,7 @@ function gatherDeepReferencesFromTree(startingNode, dataSource) {
2555
2643
  for (const [, variableMapping] of Object.entries(node.data.props)) {
2556
2644
  if (variableMapping.type !== 'BoundValue')
2557
2645
  continue;
2558
- if (!isDeepPath(variableMapping.path))
2646
+ if (!isDeepPath$1(variableMapping.path))
2559
2647
  continue;
2560
2648
  deepReferences.push(DeepReference.from({
2561
2649
  path: variableMapping.path,
@@ -3164,6 +3252,26 @@ const useTreeStore = create((set, get) => ({
3164
3252
  reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
3165
3253
  }));
3166
3254
  },
3255
+ // breadth first search
3256
+ findNodeById(nodeId) {
3257
+ if (!nodeId) {
3258
+ return null;
3259
+ }
3260
+ const rootNode = get().tree.root;
3261
+ const visitedNodeIds = [];
3262
+ const queue = [];
3263
+ let currentNode = rootNode;
3264
+ queue.push(currentNode);
3265
+ while (queue.length) {
3266
+ currentNode = queue.shift();
3267
+ visitedNodeIds.push(currentNode.data.id);
3268
+ if (currentNode.data.id === nodeId) {
3269
+ return currentNode;
3270
+ }
3271
+ queue.push(...currentNode.children);
3272
+ }
3273
+ return null;
3274
+ },
3167
3275
  }));
3168
3276
  const hasBreakpointDiffs = (currentTree, newTree) => {
3169
3277
  const currentBreakpoints = currentTree?.root?.data?.breakpoints ?? [];
@@ -3944,6 +4052,419 @@ class DebugLogger {
3944
4052
  DebugLogger.instance = null;
3945
4053
  DebugLogger.getInstance();
3946
4054
 
4055
+ const isLink = (maybeLink) => {
4056
+ if (maybeLink === null)
4057
+ return false;
4058
+ if (typeof maybeLink !== 'object')
4059
+ return false;
4060
+ const link = maybeLink;
4061
+ return Boolean(link.sys?.id) && link.sys?.type === 'Link' && Boolean(link.sys?.linkType);
4062
+ };
4063
+
4064
+ /**
4065
+ * This module encapsulates format of the path to a deep reference.
4066
+ */
4067
+ const parseDataSourcePathIntoFieldset = (path) => {
4068
+ const parsedPath = parseDeepPath(path);
4069
+ if (null === parsedPath) {
4070
+ throw new Error(`Cannot parse path '${path}' as deep path`);
4071
+ }
4072
+ return parsedPath.fields.map((field) => [null, field, '~locale']);
4073
+ };
4074
+ /**
4075
+ * Detects if paths is valid deep-path, like:
4076
+ * - /gV6yKXp61hfYrR7rEyKxY/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
4077
+ * or regular, like:
4078
+ * - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
4079
+ * @returns
4080
+ */
4081
+ const isDeepPath = (deepPathCandidate) => {
4082
+ const deepPathParsed = parseDeepPath(deepPathCandidate);
4083
+ if (!deepPathParsed) {
4084
+ return false;
4085
+ }
4086
+ return deepPathParsed.fields.length > 1;
4087
+ };
4088
+ const parseDeepPath = (deepPathCandidate) => {
4089
+ // ALGORITHM:
4090
+ // We start with deep path in form:
4091
+ // /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
4092
+ // First turn string into array of segments
4093
+ // ['', 'uuid123', 'fields', 'mainStory', '~locale', 'fields', 'cover', '~locale', 'fields', 'title', '~locale']
4094
+ // Then group segments into intermediate represenatation - chunks, where each non-initial chunk starts with 'fields'
4095
+ // [
4096
+ // [ "", "uuid123" ],
4097
+ // [ "fields", "mainStory", "~locale" ],
4098
+ // [ "fields", "cover", "~locale" ],
4099
+ // [ "fields", "title", "~locale" ]
4100
+ // ]
4101
+ // Then check "initial" chunk for corretness
4102
+ // Then check all "field-leading" chunks for correctness
4103
+ const isValidInitialChunk = (initialChunk) => {
4104
+ // must have start with '' and have at least 2 segments, second non-empty
4105
+ // eg. /-_432uuid123123
4106
+ return /^\/([^/^~]+)$/.test(initialChunk.join('/'));
4107
+ };
4108
+ const isValidFieldChunk = (fieldChunk) => {
4109
+ // must start with 'fields' and have at least 3 segments, second non-empty and last segment must be '~locale'
4110
+ // eg. fields/-32234mainStory/~locale
4111
+ return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
4112
+ };
4113
+ const deepPathSegments = deepPathCandidate.split('/');
4114
+ const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
4115
+ if (chunks.length <= 1) {
4116
+ return null; // malformed path, even regular paths have at least 2 chunks
4117
+ }
4118
+ else if (chunks.length === 2) {
4119
+ return null; // deep paths have at least 3 chunks
4120
+ }
4121
+ // With 3+ chunks we can now check for deep path correctness
4122
+ const [initialChunk, ...fieldChunks] = chunks;
4123
+ if (!isValidInitialChunk(initialChunk)) {
4124
+ return null;
4125
+ }
4126
+ if (!fieldChunks.every(isValidFieldChunk)) {
4127
+ return null;
4128
+ }
4129
+ return {
4130
+ key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
4131
+ fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
4132
+ };
4133
+ };
4134
+ const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
4135
+ const chunks = [];
4136
+ let currentChunk = [];
4137
+ const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
4138
+ const excludeEmptyChunks = (chunk) => chunk.length > 0;
4139
+ for (let i = 0; i < segments.length; i++) {
4140
+ const isInitialElement = i === 0;
4141
+ const segment = segments[i];
4142
+ if (isInitialElement) {
4143
+ currentChunk = [segment];
4144
+ }
4145
+ else if (isSegmentBeginningOfChunk(segment)) {
4146
+ chunks.push(currentChunk);
4147
+ currentChunk = [segment];
4148
+ }
4149
+ else {
4150
+ currentChunk.push(segment);
4151
+ }
4152
+ }
4153
+ chunks.push(currentChunk);
4154
+ return chunks.filter(excludeEmptyChunks);
4155
+ };
4156
+
4157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4158
+ function get(obj, path) {
4159
+ if (!path.length) {
4160
+ return obj;
4161
+ }
4162
+ try {
4163
+ const [currentPath, ...nextPath] = path;
4164
+ return get(obj[currentPath], nextPath);
4165
+ }
4166
+ catch (err) {
4167
+ return undefined;
4168
+ }
4169
+ }
4170
+ const isEntry = (value) => {
4171
+ return (null !== value &&
4172
+ typeof value === 'object' &&
4173
+ 'sys' in value &&
4174
+ value.sys?.type === 'Entry');
4175
+ };
4176
+ const isAsset = (value) => {
4177
+ return (null !== value &&
4178
+ typeof value === 'object' &&
4179
+ 'sys' in value &&
4180
+ value.sys?.type === 'Asset');
4181
+ };
4182
+
4183
+ function deepFreeze(obj) {
4184
+ const propNames = Object.getOwnPropertyNames(obj);
4185
+ for (const name of propNames) {
4186
+ const value = obj[name];
4187
+ if (value && typeof value === 'object') {
4188
+ deepFreeze(value);
4189
+ }
4190
+ }
4191
+ return Object.freeze(obj);
4192
+ }
4193
+
4194
+ /**
4195
+ * Base Store for entities
4196
+ * Can be extended for the different loading behaviours (editor, production, ..)
4197
+ */
4198
+ class EntityStoreBase {
4199
+ constructor({ entities, locale }) {
4200
+ /* serialized */ this.entryMap = new Map();
4201
+ /* serialized */ this.assetMap = new Map();
4202
+ this.locale = locale;
4203
+ for (const entity of entities) {
4204
+ this.addEntity(entity);
4205
+ }
4206
+ }
4207
+ get entities() {
4208
+ return [...this.entryMap.values(), ...this.assetMap.values()];
4209
+ }
4210
+ updateEntity(entity) {
4211
+ this.addEntity(entity);
4212
+ }
4213
+ getEntryOrAsset(linkOrEntryOrAsset, path) {
4214
+ if (isDeepPath(path)) {
4215
+ return this.getDeepEntry(linkOrEntryOrAsset, path);
4216
+ }
4217
+ let entity;
4218
+ if (isLink(linkOrEntryOrAsset)) {
4219
+ const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
4220
+ ? this.entryMap.get(linkOrEntryOrAsset.sys.id)
4221
+ : this.assetMap.get(linkOrEntryOrAsset.sys.id);
4222
+ if (!resolvedEntity || resolvedEntity.sys.type !== linkOrEntryOrAsset.sys.linkType) {
4223
+ console.warn(`Experience references unresolved entity: ${JSON.stringify(linkOrEntryOrAsset)}`);
4224
+ return;
4225
+ }
4226
+ entity = resolvedEntity;
4227
+ }
4228
+ else if (isAsset(linkOrEntryOrAsset) || isEntry(linkOrEntryOrAsset)) {
4229
+ // We already have the complete entity in preview & delivery (resolved by the CMA client)
4230
+ entity = linkOrEntryOrAsset;
4231
+ }
4232
+ else {
4233
+ throw new Error(`Unexpected object when resolving entity: ${JSON.stringify(linkOrEntryOrAsset)}`);
4234
+ }
4235
+ return entity;
4236
+ }
4237
+ /**
4238
+ * @deprecated in the base class this should be simply an abstract method
4239
+ * @param entityLink
4240
+ * @param path
4241
+ * @returns
4242
+ */
4243
+ getValue(entityLink, path) {
4244
+ const entity = this.getEntity(entityLink.sys.linkType, entityLink.sys.id);
4245
+ if (!entity) {
4246
+ // TODO: move to `debug` utils once it is extracted
4247
+ console.warn(`Unresolved entity reference: ${entityLink.sys.linkType} with ID ${entityLink.sys.id}`);
4248
+ return;
4249
+ }
4250
+ return get(entity, path);
4251
+ }
4252
+ getEntityFromLink(link) {
4253
+ const resolvedEntity = link.sys.linkType === 'Entry'
4254
+ ? this.entryMap.get(link.sys.id)
4255
+ : this.assetMap.get(link.sys.id);
4256
+ if (!resolvedEntity || resolvedEntity.sys.type !== link.sys.linkType) {
4257
+ console.warn(`Experience references unresolved entity: ${JSON.stringify(link)}`);
4258
+ return;
4259
+ }
4260
+ return resolvedEntity;
4261
+ }
4262
+ getAssetById(assetId) {
4263
+ const asset = this.assetMap.get(assetId);
4264
+ if (!asset) {
4265
+ console.warn(`Asset with ID "${assetId}" is not found in the store`);
4266
+ return;
4267
+ }
4268
+ return asset;
4269
+ }
4270
+ getEntryById(entryId) {
4271
+ const entry = this.entryMap.get(entryId);
4272
+ if (!entry) {
4273
+ console.warn(`Entry with ID "${entryId}" is not found in the store`);
4274
+ return;
4275
+ }
4276
+ return entry;
4277
+ }
4278
+ getEntitiesFromMap(type, ids) {
4279
+ const resolved = [];
4280
+ const missing = [];
4281
+ for (const id of ids) {
4282
+ const entity = this.getEntity(type, id);
4283
+ if (entity) {
4284
+ resolved.push(entity);
4285
+ }
4286
+ else {
4287
+ missing.push(id);
4288
+ }
4289
+ }
4290
+ return {
4291
+ resolved,
4292
+ missing,
4293
+ };
4294
+ }
4295
+ addEntity(entity) {
4296
+ if (isAsset(entity)) {
4297
+ // cloned and frozen
4298
+ this.assetMap.set(entity.sys.id, deepFreeze(structuredClone(entity)));
4299
+ }
4300
+ else if (isEntry(entity)) {
4301
+ // cloned and frozen
4302
+ this.entryMap.set(entity.sys.id, deepFreeze(structuredClone(entity)));
4303
+ }
4304
+ else {
4305
+ throw new Error(`Attempted to add an entity to the store that is neither Asset nor Entry: '${JSON.stringify(entity)}'`);
4306
+ }
4307
+ }
4308
+ async fetchAsset(id) {
4309
+ const { resolved, missing } = this.getEntitiesFromMap('Asset', [id]);
4310
+ if (missing.length) {
4311
+ // TODO: move to `debug` utils once it is extracted
4312
+ console.warn(`Asset "${id}" is not in the store`);
4313
+ return;
4314
+ }
4315
+ return resolved[0];
4316
+ }
4317
+ async fetchAssets(ids) {
4318
+ const { resolved, missing } = this.getEntitiesFromMap('Asset', ids);
4319
+ if (missing.length) {
4320
+ throw new Error(`Missing assets in the store (${missing.join(',')})`);
4321
+ }
4322
+ return resolved;
4323
+ }
4324
+ async fetchEntry(id) {
4325
+ const { resolved, missing } = this.getEntitiesFromMap('Entry', [id]);
4326
+ if (missing.length) {
4327
+ // TODO: move to `debug` utils once it is extracted
4328
+ console.warn(`Entry "${id}" is not in the store`);
4329
+ return;
4330
+ }
4331
+ return resolved[0];
4332
+ }
4333
+ async fetchEntries(ids) {
4334
+ const { resolved, missing } = this.getEntitiesFromMap('Entry', ids);
4335
+ if (missing.length) {
4336
+ throw new Error(`Missing assets in the store (${missing.join(',')})`);
4337
+ }
4338
+ return resolved;
4339
+ }
4340
+ getDeepEntry(linkOrEntryOrAsset, path) {
4341
+ const resolveFieldset = (unresolvedFieldset, headEntry) => {
4342
+ const resolvedFieldset = [];
4343
+ let entityToResolveFieldsFrom = headEntry;
4344
+ for (let i = 0; i < unresolvedFieldset.length; i++) {
4345
+ const isLeaf = i === unresolvedFieldset.length - 1; // with last row, we are not expecting a link, but a value
4346
+ const row = unresolvedFieldset[i];
4347
+ const [, field, _localeQualifier] = row;
4348
+ if (!entityToResolveFieldsFrom) {
4349
+ throw new Error(`Logic Error: Cannot resolve field ${field} of a fieldset as there is no entity to resolve it from.`);
4350
+ }
4351
+ if (isLeaf) {
4352
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
4353
+ break;
4354
+ }
4355
+ const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
4356
+ if (undefined === fieldValue) {
4357
+ return {
4358
+ resolvedFieldset,
4359
+ isFullyResolved: false,
4360
+ reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
4361
+ };
4362
+ }
4363
+ else if (isLink(fieldValue)) {
4364
+ const entity = this.getEntityFromLink(fieldValue);
4365
+ if (entity === undefined) {
4366
+ return {
4367
+ resolvedFieldset,
4368
+ isFullyResolved: false,
4369
+ reason: `Field reference Link (sys.id=${fieldValue.sys.id}) not found in the EntityStore, waiting...`,
4370
+ };
4371
+ }
4372
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
4373
+ entityToResolveFieldsFrom = entity; // we move up
4374
+ }
4375
+ else if (isAsset(fieldValue) || isEntry(fieldValue)) {
4376
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
4377
+ entityToResolveFieldsFrom = fieldValue; // we move up
4378
+ }
4379
+ else {
4380
+ return {
4381
+ resolvedFieldset,
4382
+ isFullyResolved: false,
4383
+ reason: `Deep path points to an invalid field value of type '${typeof fieldValue}' (value=${fieldValue})`,
4384
+ };
4385
+ }
4386
+ }
4387
+ return {
4388
+ resolvedFieldset,
4389
+ isFullyResolved: true,
4390
+ };
4391
+ };
4392
+ const headEntity = isLink(linkOrEntryOrAsset)
4393
+ ? this.getEntityFromLink(linkOrEntryOrAsset)
4394
+ : linkOrEntryOrAsset;
4395
+ if (undefined === headEntity) {
4396
+ return;
4397
+ }
4398
+ const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
4399
+ // The purpose here is to take this intermediate representation of the deep-path
4400
+ // and to follow the links to the leaf-entity and field
4401
+ // in case we can't follow till the end, we should signal that there was null-reference in the path
4402
+ const { resolvedFieldset, isFullyResolved, reason } = resolveFieldset(unresolvedFieldset, headEntity);
4403
+ if (!isFullyResolved) {
4404
+ reason &&
4405
+ console.debug(`[exp-builder.sdk::EntityStoreBased::getValueDeep()] Deep path wasn't resolved till leaf node, falling back to undefined, because: ${reason}`);
4406
+ return;
4407
+ }
4408
+ const [leafEntity] = resolvedFieldset[resolvedFieldset.length - 1];
4409
+ return leafEntity;
4410
+ }
4411
+ getEntity(type, id) {
4412
+ if (type === 'Asset') {
4413
+ return this.assetMap.get(id);
4414
+ }
4415
+ return this.entryMap.get(id);
4416
+ }
4417
+ toJSON() {
4418
+ return {
4419
+ entryMap: Object.fromEntries(this.entryMap),
4420
+ assetMap: Object.fromEntries(this.assetMap),
4421
+ locale: this.locale,
4422
+ };
4423
+ }
4424
+ }
4425
+
4426
+ class UninitializedEntityStore extends EntityStoreBase {
4427
+ constructor() {
4428
+ super({ entities: [], locale: 'uninitialized-locale-in-uninitialized-entity-store' });
4429
+ }
4430
+ }
4431
+
4432
+ create((set, get) => ({
4433
+ // The UninitializedEntityStore is a placeholder instance and is here to highlight the
4434
+ // // fact that it's not used by anything until during loading lifecycle it'sreplaced by real entity store:
4435
+ // - in Preview+Delivery mode: right after we fetch Expereince and it entities
4436
+ // - in EDITOR (VisualEditor) mode: right after the VisualEditor is async imported and initialize event happens
4437
+ entityStore: new UninitializedEntityStore(),
4438
+ areEntitiesFetched: false,
4439
+ setEntitiesFetched(fetched) {
4440
+ set({ areEntitiesFetched: fetched });
4441
+ },
4442
+ resolveAssetById(assetId) {
4443
+ if (!assetId)
4444
+ return undefined;
4445
+ const { entityStore } = get();
4446
+ return entityStore.getAssetById(assetId);
4447
+ },
4448
+ resolveEntryById(entryId) {
4449
+ if (!entryId)
4450
+ return undefined;
4451
+ const { entityStore } = get();
4452
+ return entityStore.getEntryById(entryId);
4453
+ },
4454
+ resolveEntity(link) {
4455
+ if (!link)
4456
+ return undefined;
4457
+ const { entityStore } = get();
4458
+ return entityStore.getEntityFromLink(link);
4459
+ },
4460
+ resetEntityStore(entityStore) {
4461
+ set({
4462
+ entityStore,
4463
+ areEntitiesFetched: false,
4464
+ });
4465
+ },
4466
+ }));
4467
+
3947
4468
  var VisualEditorMode;
3948
4469
  (function (VisualEditorMode) {
3949
4470
  VisualEditorMode["LazyLoad"] = "lazyLoad";
@@ -4008,23 +4529,6 @@ const Assembly = (props) => {
4008
4529
  return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
4009
4530
  };
4010
4531
 
4011
- const useEntityStore = create((set) => ({
4012
- entityStore: new EditorModeEntityStore({ locale: 'en-US', entities: [] }),
4013
- areEntitiesFetched: false,
4014
- setEntitiesFetched(fetched) {
4015
- set({ areEntitiesFetched: fetched });
4016
- },
4017
- resetEntityStore(locale, entities = []) {
4018
- console.debug(`[experiences-sdk-react] Resetting entity store because the locale changed to '${locale}'.`);
4019
- const newEntityStore = new EditorModeEntityStore({ locale, entities });
4020
- set({
4021
- entityStore: newEntityStore,
4022
- areEntitiesFetched: false,
4023
- });
4024
- return newEntityStore;
4025
- },
4026
- }));
4027
-
4028
4532
  class DragState {
4029
4533
  constructor() {
4030
4534
  this.isDragStartedOnParent = false;
@@ -4094,10 +4598,11 @@ class SimulateDnD extends DragState {
4094
4598
  }
4095
4599
  var SimulateDnD$1 = new SimulateDnD();
4096
4600
 
4097
- function useEditorSubscriber() {
4098
- const entityStore = useEntityStore((state) => state.entityStore);
4099
- const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
4100
- const setEntitiesFetched = useEntityStore((state) => state.setEntitiesFetched);
4601
+ function useEditorSubscriber(entityCache) {
4602
+ const entityStore = entityCache((state) => state.entityStore);
4603
+ const areEntitiesFetched = entityCache((state) => state.areEntitiesFetched);
4604
+ const setEntitiesFetched = entityCache((state) => state.setEntitiesFetched);
4605
+ const resetEntityStore = entityCache((state) => state.resetEntityStore);
4101
4606
  const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
4102
4607
  updateTree: state.updateTree,
4103
4608
  updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
@@ -4109,7 +4614,6 @@ function useEditorSubscriber() {
4109
4614
  const setDataSource = useEditorStore((state) => state.setDataSource);
4110
4615
  const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
4111
4616
  const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
4112
- const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
4113
4617
  const setComponentId = useDraggedItemStore((state) => state.setComponentId);
4114
4618
  const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
4115
4619
  const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
@@ -4160,7 +4664,7 @@ function useEditorSubscriber() {
4160
4664
  const isMissingL2Entities = (deepReferences) => {
4161
4665
  const referentLinks = deepReferences
4162
4666
  .map((deepReference) => deepReference.extractReferent(entityStore))
4163
- .filter(isLink);
4667
+ .filter(isLink$1);
4164
4668
  const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4165
4669
  return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
4166
4670
  };
@@ -4177,7 +4681,7 @@ function useEditorSubscriber() {
4177
4681
  const fillupL2 = async ({ deepReferences }) => {
4178
4682
  const referentLinks = deepReferences
4179
4683
  .map((deepReference) => deepReference.extractReferent(entityStore))
4180
- .filter(isLink);
4684
+ .filter(isLink$1);
4181
4685
  const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4182
4686
  await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
4183
4687
  };
@@ -4230,8 +4734,9 @@ function useEditorSubscriber() {
4230
4734
  }
4231
4735
  let newEntityStore = entityStore;
4232
4736
  if (entityStore.locale !== locale) {
4233
- newEntityStore = resetEntityStore(locale);
4737
+ newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
4234
4738
  setLocale(locale);
4739
+ resetEntityStore(newEntityStore);
4235
4740
  }
4236
4741
  // Below are mutually exclusive cases
4237
4742
  if (changedNode) {
@@ -4761,17 +5266,49 @@ const addStylesTag = (className, styleRules) => {
4761
5266
 
4762
5267
  const getUnboundValues = ({ key, fallback, unboundValues, }) => {
4763
5268
  const lodashPath = `${key}.value`;
4764
- return get$1(unboundValues, lodashPath, fallback);
5269
+ return get$2(unboundValues, lodashPath, fallback);
5270
+ };
5271
+
5272
+ /**
5273
+ * When node is a pattern block, we need to look up for default values of the pattern variables
5274
+ * and merge them with the updated node props.
5275
+ * While loop is making sure that we look up for the updated default values in the parent pattern
5276
+ * component settings as well.
5277
+ */
5278
+ const maybeMergePatternDefaultDesignValues = ({ variableName, variableMapping, node, findNodeById, }) => {
5279
+ if (node.type === ASSEMBLY_BLOCK_NODE_TYPE) {
5280
+ const patternId = node.data.pattern?.id;
5281
+ const exposedPropertyName = node['exposedPropertyNameToKeyMap'][variableName];
5282
+ if (!exposedPropertyName || !patternId) {
5283
+ return variableMapping.valuesByBreakpoint;
5284
+ }
5285
+ const exposedVariableDefinition = componentRegistry.get(patternId)?.definition.variables[exposedPropertyName];
5286
+ let exposedDefaultValue = exposedVariableDefinition?.defaultValue;
5287
+ let parentPatternNode = findNodeById(node.data.pattern?.nodeId);
5288
+ while (parentPatternNode) {
5289
+ const parentPatternId = parentPatternNode.data.pattern?.id;
5290
+ const nextKey = parentPatternNode['exposedPropertyNameToKeyMap'][exposedPropertyName];
5291
+ if (!parentPatternId || !nextKey) {
5292
+ break;
5293
+ }
5294
+ const parentPatternVariableDefinition = componentRegistry.get(parentPatternId)?.definition.variables[nextKey];
5295
+ exposedDefaultValue = mergeDesignValuesByBreakpoint(parentPatternVariableDefinition?.defaultValue, exposedDefaultValue);
5296
+ parentPatternNode = findNodeById(parentPatternNode.data.pattern?.nodeId);
5297
+ }
5298
+ const mergedDesignValue = mergeDesignValuesByBreakpoint(exposedDefaultValue, variableMapping);
5299
+ return mergedDesignValue?.valuesByBreakpoint;
5300
+ }
5301
+ return variableMapping.valuesByBreakpoint;
4765
5302
  };
4766
5303
 
4767
- const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
5304
+ const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
4768
5305
  const unboundValues = useEditorStore((state) => state.unboundValues);
4769
5306
  const hyperlinkPattern = useEditorStore((state) => state.hyperLinkPattern);
4770
5307
  const locale = useEditorStore((state) => state.locale);
4771
5308
  const dataSource = useEditorStore((state) => state.dataSource);
4772
- const entityStore = useEntityStore((state) => state.entityStore);
4773
5309
  const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
4774
5310
  const nodeRect = useDraggedItemStore((state) => state.domRect);
5311
+ const findNodeById = useTreeStore((state) => state.findNodeById);
4775
5312
  const isEmptyZone = !node.children.length;
4776
5313
  const props = useMemo(() => {
4777
5314
  const propsBase = {
@@ -4795,7 +5332,13 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
4795
5332
  };
4796
5333
  }
4797
5334
  if (variableMapping.type === 'DesignValue') {
4798
- const valuesByBreakpoint = resolveDesignValue(variableMapping.valuesByBreakpoint, variableName);
5335
+ const value = maybeMergePatternDefaultDesignValues({
5336
+ variableName,
5337
+ variableMapping,
5338
+ node,
5339
+ findNodeById,
5340
+ });
5341
+ const valuesByBreakpoint = resolveDesignValue(value, variableName);
4799
5342
  const designValue = variableName === 'cfHeight'
4800
5343
  ? calculateNodeDefaultHeight({
4801
5344
  blockId: node.data.blockId,
@@ -4886,6 +5429,7 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
4886
5429
  unboundValues,
4887
5430
  entityStore,
4888
5431
  renderDropzone,
5432
+ findNodeById,
4889
5433
  ]);
4890
5434
  const cfStyles = useMemo(() => ({
4891
5435
  ...buildCfStyles(props),
@@ -5058,7 +5602,6 @@ const MissingComponentPlaceholder = ({ blockId }) => {
5058
5602
  };
5059
5603
 
5060
5604
  const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...props }, ref) => {
5061
- const entityStore = useEntityStore((state) => state.entityStore);
5062
5605
  return (React.createElement("div", { ...props,
5063
5606
  // Pass through ref to avoid DND errors being logged
5064
5607
  ref: ref, "data-cf-node-error": "circular-pattern-dependency", style: {
@@ -5071,7 +5614,7 @@ const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...
5071
5614
  "Circular usage of patterns detected:",
5072
5615
  React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
5073
5616
  const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
5074
- const entry = entityStore.getEntityFromLink(entryLink);
5617
+ const entry = inMemoryEntities.maybeResolveLink(entryLink);
5075
5618
  const entryTitle = entry?.fields?.title;
5076
5619
  const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
5077
5620
  return React.createElement("li", { key: patternId }, text);
@@ -5079,8 +5622,7 @@ const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...
5079
5622
  });
5080
5623
  CircularDependencyErrorPlaceholder.displayName = 'CircularDependencyErrorPlaceholder';
5081
5624
 
5082
- const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
5083
- const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
5625
+ const useComponent = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
5084
5626
  const tree = useTreeStore((state) => state.tree);
5085
5627
  const componentRegistration = useMemo(() => {
5086
5628
  let registration = componentRegistry.get(node.data.blockId);
@@ -5106,6 +5648,7 @@ const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging
5106
5648
  const requiresDragWrapper = !isPatternNode && !isStructureComponent && !componentRegistration?.options?.wrapComponent;
5107
5649
  const { componentProps, wrapperStyles } = useComponentProps({
5108
5650
  node,
5651
+ entityStore,
5109
5652
  areEntitiesFetched,
5110
5653
  resolveDesignValue,
5111
5654
  renderDropzone,
@@ -5580,13 +6123,15 @@ function getStyle$1(style, snapshot) {
5580
6123
  transitionDuration: `0.001s`,
5581
6124
  };
5582
6125
  }
5583
- const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
6126
+ const EditorBlock = ({ node: rawNode, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
5584
6127
  const { slotId } = parseZoneId(zoneId);
5585
6128
  const ref = useRef(null);
5586
6129
  const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
5587
6130
  const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
5588
6131
  const { node, componentId, elementToRender, definition, isPatternNode, isPatternComponent, isNestedPattern, } = useComponent({
5589
6132
  node: rawNode,
6133
+ entityStore,
6134
+ areEntitiesFetched,
5590
6135
  resolveDesignValue,
5591
6136
  renderDropzone,
5592
6137
  userIsDragging,
@@ -5727,10 +6272,12 @@ function getStyle(style = {}, snapshot) {
5727
6272
  transitionDuration: `0.001s`,
5728
6273
  };
5729
6274
  }
5730
- const EditorBlockClone = ({ node: rawNode, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
6275
+ const EditorBlockClone = ({ node: rawNode, entityStore, areEntitiesFetched, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
5731
6276
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5732
6277
  const { node, elementToRender } = useComponent({
5733
6278
  node: rawNode,
6279
+ entityStore,
6280
+ areEntitiesFetched,
5734
6281
  resolveDesignValue,
5735
6282
  renderDropzone,
5736
6283
  userIsDragging,
@@ -5767,7 +6314,7 @@ const getHtmlComponentProps = (props) => {
5767
6314
  return {};
5768
6315
  };
5769
6316
 
5770
- function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, ...rest }) {
6317
+ function DropzoneClone({ node, entityStore, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, areEntitiesFetched, ...rest }) {
5771
6318
  const tree = useTreeStore((state) => state.tree);
5772
6319
  const content = node?.children || tree.root?.children || [];
5773
6320
  const { slotId } = parseZoneId(zoneId);
@@ -5788,11 +6335,11 @@ function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'd
5788
6335
  .filter((node) => node.data.slotId === slotId)
5789
6336
  .map((item) => {
5790
6337
  const componentId = item.data.id;
5791
- return (React.createElement(EditorBlockClone, { key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
6338
+ return (React.createElement(EditorBlockClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
5792
6339
  })));
5793
6340
  }
5794
6341
 
5795
- function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
6342
+ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, entityStore, areEntitiesFetched, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
5796
6343
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5797
6344
  const draggedItem = useDraggedItemStore((state) => state.draggedItem);
5798
6345
  const isDraggingNewComponent = useDraggedItemStore((state) => Boolean(state.componentId));
@@ -5827,11 +6374,11 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5827
6374
  }, [isRootAssembly, node, parentWrappingPatternIds, tree.root.data.blockId]);
5828
6375
  // To avoid a circular dependency, we create the recursive rendering function here and trickle it down
5829
6376
  const renderDropzone = useCallback((node, props) => {
5830
- return (React.createElement(Dropzone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, ...props }));
5831
- }, [wrappingPatternIds, resolveDesignValue]);
6377
+ return (React.createElement(Dropzone, { zoneId: node.data.id, entityStore: entityStore, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, areEntitiesFetched: areEntitiesFetched, ...props }));
6378
+ }, [wrappingPatternIds, resolveDesignValue, entityStore, areEntitiesFetched]);
5832
6379
  const renderClonedDropzone = useCallback((node, props) => {
5833
- return (React.createElement(DropzoneClone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
5834
- }, [resolveDesignValue, wrappingPatternIds]);
6380
+ return (React.createElement(DropzoneClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
6381
+ }, [resolveDesignValue, wrappingPatternIds, entityStore, areEntitiesFetched]);
5835
6382
  const isDropzoneEnabled = useMemo(() => {
5836
6383
  const isColumns = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
5837
6384
  const isDraggingSingleColumn = draggedNode?.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
@@ -5873,7 +6420,7 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5873
6420
  ? node.children.length === 1 &&
5874
6421
  resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
5875
6422
  : false;
5876
- return (React.createElement(Droppable, { droppableId: zoneId, direction: direction, isDropDisabled: !isDropzoneEnabled, renderClone: (provided, snapshot, rubic) => (React.createElement(EditorBlockClone, { node: content[rubic.source.index], resolveDesignValue: resolveDesignValue, provided: provided, snapshot: snapshot, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds })) }, (provided, snapshot) => {
6423
+ return (React.createElement(Droppable, { droppableId: zoneId, direction: direction, isDropDisabled: !isDropzoneEnabled, renderClone: (provided, snapshot, rubic) => (React.createElement(EditorBlockClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, node: content[rubic.source.index], resolveDesignValue: resolveDesignValue, provided: provided, snapshot: snapshot, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds })) }, (provided, snapshot) => {
5877
6424
  return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
5878
6425
  if (dragProps?.innerRef) {
5879
6426
  dragProps.innerRef(refNode);
@@ -5891,7 +6438,7 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5891
6438
  }) },
5892
6439
  isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
5893
6440
  .filter((node) => node.data.slotId === slotId)
5894
- .map((item, i) => (React.createElement(EditorBlock, { placeholder: {
6441
+ .map((item, i) => (React.createElement(EditorBlock, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, placeholder: {
5895
6442
  isDraggingOver: snapshot?.isDraggingOver,
5896
6443
  totalIndexes: content.length,
5897
6444
  elementIndex: i,
@@ -5903,8 +6450,8 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5903
6450
  }));
5904
6451
  }
5905
6452
 
5906
- const RootRenderer = ({ onChange }) => {
5907
- useEditorSubscriber();
6453
+ const RootRenderer = ({ onChange, inMemoryEntitiesStore, }) => {
6454
+ useEditorSubscriber(inMemoryEntitiesStore);
5908
6455
  const dragItem = useDraggedItemStore((state) => state.componentId);
5909
6456
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5910
6457
  const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
@@ -5984,28 +6531,28 @@ const RootRenderer = ({ onChange }) => {
5984
6531
  handleResizeCanvas();
5985
6532
  // eslint-disable-next-line react-hooks/exhaustive-deps
5986
6533
  }, [containerRef.current]);
6534
+ const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
6535
+ const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
5987
6536
  return (React.createElement(DNDProvider, null,
5988
6537
  dragItem && React.createElement(DraggableContainer, { id: dragItem }),
5989
6538
  React.createElement("div", { "data-ctfl-root": true, className: styles$3.container, ref: containerRef, style: containerStyles },
5990
6539
  userIsDragging && React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitbox }),
5991
- React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue }),
6540
+ React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched }),
5992
6541
  userIsDragging && (React.createElement(React.Fragment, null,
5993
6542
  React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitboxLower }),
5994
6543
  React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.canvasBottomSpacer })))),
5995
6544
  React.createElement("div", { "data-ctfl-hitboxes": true })));
5996
6545
  };
5997
6546
 
5998
- const useInitializeEditor = () => {
6547
+ const useInitializeEditor = (inMemoryEntitiesStore) => {
5999
6548
  const initializeEditor = useEditorStore((state) => state.initializeEditor);
6000
6549
  const [initialized, setInitialized] = useState(false);
6001
- const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
6550
+ const resetEntityStore = useStore(inMemoryEntitiesStore, (state) => state.resetEntityStore);
6002
6551
  useEffect(() => {
6003
6552
  const onVisualEditorInitialize = (event) => {
6004
6553
  if (!event.detail)
6005
6554
  return;
6006
- const { componentRegistry, designTokens, locale: initialLocale,
6007
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6008
- entities, } = event.detail;
6555
+ const { componentRegistry, designTokens, locale: initialLocale, entities } = event.detail;
6009
6556
  initializeEditor({
6010
6557
  initialLocale,
6011
6558
  componentRegistry,
@@ -6013,7 +6560,7 @@ const useInitializeEditor = () => {
6013
6560
  });
6014
6561
  // if entities is set to [], then everything will still work as EntityStore will
6015
6562
  // request entities on demand via ▲REQUEST_ENTITY
6016
- resetEntityStore(initialLocale, entities);
6563
+ resetEntityStore(new EditorModeEntityStore({ locale: initialLocale, entities }));
6017
6564
  setInitialized(true);
6018
6565
  };
6019
6566
  // Listen for VisualEditorComponents internal event
@@ -6033,8 +6580,8 @@ const useInitializeEditor = () => {
6033
6580
  return initialized;
6034
6581
  };
6035
6582
 
6036
- const VisualEditorRoot = ({ experience }) => {
6037
- const initialized = useInitializeEditor();
6583
+ const VisualEditorRoot = ({ experience, inMemoryEntitiesStore: inMemoryEntitiesStore$1 = inMemoryEntitiesStore, }) => {
6584
+ const initialized = useInitializeEditor(inMemoryEntitiesStore$1);
6038
6585
  const setHyperLinkPattern = useEditorStore((state) => state.setHyperLinkPattern);
6039
6586
  const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
6040
6587
  const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
@@ -6070,7 +6617,7 @@ const VisualEditorRoot = ({ experience }) => {
6070
6617
  }, [setHoveringZone, setMousePosition]);
6071
6618
  if (!initialized)
6072
6619
  return null;
6073
- return React.createElement(RootRenderer, null);
6620
+ return React.createElement(RootRenderer, { inMemoryEntitiesStore: inMemoryEntitiesStore$1 });
6074
6621
  };
6075
6622
 
6076
6623
  export { VisualEditorRoot as default };