@contentful/experiences-visual-editor-react 1.42.4-prerelease-20250702T1207-37b3bfc.0 → 2.0.0-beta.1

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';
@@ -1099,6 +1091,24 @@ propertyName, resolveDesignTokens = true) => {
1099
1091
  return valuesByBreakpoint;
1100
1092
  }
1101
1093
  };
1094
+ /** Overwrites the default value breakpoint by breakpoint. If a breakpoint
1095
+ * is not overwritten, it will fall back to the default. */
1096
+ function mergeDesignValuesByBreakpoint(defaultValue, overwriteValue) {
1097
+ if (!defaultValue || !overwriteValue) {
1098
+ return defaultValue ?? overwriteValue;
1099
+ }
1100
+ const mergedValuesByBreakpoint = { ...defaultValue.valuesByBreakpoint };
1101
+ for (const [breakpointId, value] of Object.entries(overwriteValue.valuesByBreakpoint)) {
1102
+ if (!isValidBreakpointValue(value)) {
1103
+ continue;
1104
+ }
1105
+ mergedValuesByBreakpoint[breakpointId] = value;
1106
+ }
1107
+ return {
1108
+ type: 'DesignValue',
1109
+ valuesByBreakpoint: mergedValuesByBreakpoint,
1110
+ };
1111
+ }
1102
1112
 
1103
1113
  const CF_DEBUG_KEY$1 = 'cf_debug';
1104
1114
  /**
@@ -1220,11 +1230,19 @@ const getElementCoordinates = (element) => {
1220
1230
  });
1221
1231
  };
1222
1232
 
1223
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1224
1233
  const isLinkToAsset = (variable) => {
1225
- if (!variable)
1234
+ if (variable === null || typeof variable !== 'object')
1235
+ return false;
1236
+ // The `'prop' in` pattern is informing TypeScript of the object shape, no need to cast `as`.
1237
+ if (!('sys' in variable))
1226
1238
  return false;
1227
- if (typeof variable !== 'object')
1239
+ if (variable.sys === null || typeof variable.sys !== 'object')
1240
+ return false;
1241
+ if (!('linkType' in variable.sys))
1242
+ return false;
1243
+ if (!('id' in variable.sys))
1244
+ return false;
1245
+ if (!('type' in variable.sys))
1228
1246
  return false;
1229
1247
  return (variable.sys?.linkType === 'Asset' &&
1230
1248
  typeof variable.sys?.id === 'string' &&
@@ -1232,20 +1250,20 @@ const isLinkToAsset = (variable) => {
1232
1250
  variable.sys?.type === 'Link');
1233
1251
  };
1234
1252
 
1235
- const isLink = (maybeLink) => {
1253
+ const isLink$1 = (maybeLink) => {
1236
1254
  if (maybeLink === null)
1237
1255
  return false;
1238
1256
  if (typeof maybeLink !== 'object')
1239
1257
  return false;
1240
1258
  const link = maybeLink;
1241
- return Boolean(link.sys?.id) && link.sys?.type === 'Link';
1259
+ return Boolean(link.sys?.id) && link.sys?.type === 'Link' && Boolean(link.sys?.linkType);
1242
1260
  };
1243
1261
 
1244
1262
  /**
1245
1263
  * This module encapsulates format of the path to a deep reference.
1246
1264
  */
1247
- const parseDataSourcePathIntoFieldset = (path) => {
1248
- const parsedPath = parseDeepPath(path);
1265
+ const parseDataSourcePathIntoFieldset$1 = (path) => {
1266
+ const parsedPath = parseDeepPath$1(path);
1249
1267
  if (null === parsedPath) {
1250
1268
  throw new Error(`Cannot parse path '${path}' as deep path`);
1251
1269
  }
@@ -1258,7 +1276,7 @@ const parseDataSourcePathIntoFieldset = (path) => {
1258
1276
  * @returns
1259
1277
  */
1260
1278
  const parseDataSourcePathWithL1DeepBindings = (path) => {
1261
- const parsedPath = parseDeepPath(path);
1279
+ const parsedPath = parseDeepPath$1(path);
1262
1280
  if (null === parsedPath) {
1263
1281
  throw new Error(`Cannot parse path '${path}' as deep path`);
1264
1282
  }
@@ -1275,14 +1293,14 @@ const parseDataSourcePathWithL1DeepBindings = (path) => {
1275
1293
  * - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
1276
1294
  * @returns
1277
1295
  */
1278
- const isDeepPath = (deepPathCandidate) => {
1279
- const deepPathParsed = parseDeepPath(deepPathCandidate);
1296
+ const isDeepPath$1 = (deepPathCandidate) => {
1297
+ const deepPathParsed = parseDeepPath$1(deepPathCandidate);
1280
1298
  if (!deepPathParsed) {
1281
1299
  return false;
1282
1300
  }
1283
1301
  return deepPathParsed.fields.length > 1;
1284
1302
  };
1285
- const parseDeepPath = (deepPathCandidate) => {
1303
+ const parseDeepPath$1 = (deepPathCandidate) => {
1286
1304
  // ALGORITHM:
1287
1305
  // We start with deep path in form:
1288
1306
  // /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
@@ -1308,7 +1326,7 @@ const parseDeepPath = (deepPathCandidate) => {
1308
1326
  return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
1309
1327
  };
1310
1328
  const deepPathSegments = deepPathCandidate.split('/');
1311
- const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
1329
+ const chunks = chunkSegments$1(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
1312
1330
  if (chunks.length <= 1) {
1313
1331
  return null; // malformed path, even regular paths have at least 2 chunks
1314
1332
  }
@@ -1328,7 +1346,7 @@ const parseDeepPath = (deepPathCandidate) => {
1328
1346
  fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
1329
1347
  };
1330
1348
  };
1331
- const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
1349
+ const chunkSegments$1 = (segments, { startNextChunkOnElementEqualTo }) => {
1332
1350
  const chunks = [];
1333
1351
  let currentChunk = [];
1334
1352
  const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
@@ -1429,11 +1447,6 @@ const transformVisibility = (value) => {
1429
1447
  // Don't explicitly set anything when visible to not overwrite values like `grid` or `flex`.
1430
1448
  return {};
1431
1449
  };
1432
- // TODO: Remove in next major version v2 since the change is 17 months old
1433
- // Keep this for backwards compatibility - deleting this would be a breaking change
1434
- // because existing components on a users experience will have the width value as fill
1435
- // rather than 100%
1436
- const transformFill = (value) => (value === 'fill' ? '100%' : value);
1437
1450
  const transformGridColumn = (span) => {
1438
1451
  if (!span) {
1439
1452
  return {};
@@ -1478,34 +1491,6 @@ const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions
1478
1491
  return;
1479
1492
  }
1480
1493
  let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
1481
- // Special case for handling single values
1482
- // for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
1483
- if (horizontalAlignment && !verticalAlignment) {
1484
- const singleValue = horizontalAlignment;
1485
- switch (singleValue) {
1486
- case 'left':
1487
- horizontalAlignment = 'left';
1488
- verticalAlignment = 'center';
1489
- break;
1490
- case 'right':
1491
- horizontalAlignment = 'right';
1492
- verticalAlignment = 'center';
1493
- break;
1494
- case 'center':
1495
- horizontalAlignment = 'center';
1496
- verticalAlignment = 'center';
1497
- break;
1498
- case 'top':
1499
- horizontalAlignment = 'center';
1500
- verticalAlignment = 'top';
1501
- break;
1502
- case 'bottom':
1503
- horizontalAlignment = 'center';
1504
- verticalAlignment = 'bottom';
1505
- break;
1506
- // just fall down to the normal validation logic for horiz and vert
1507
- }
1508
- }
1509
1494
  const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
1510
1495
  const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
1511
1496
  horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
@@ -1590,8 +1575,8 @@ const buildCfStyles = (values) => {
1590
1575
  margin: values.cfMargin,
1591
1576
  padding: values.cfPadding,
1592
1577
  backgroundColor: values.cfBackgroundColor,
1593
- width: transformFill(values.cfWidth || values.cfImageOptions?.width),
1594
- height: transformFill(values.cfHeight || values.cfImageOptions?.height),
1578
+ width: values.cfWidth || values.cfImageOptions?.width,
1579
+ height: values.cfHeight || values.cfImageOptions?.height,
1595
1580
  maxWidth: values.cfMaxWidth,
1596
1581
  ...transformGridColumn(values.cfColumnSpan),
1597
1582
  ...transformBorderStyle(values.cfBorder),
@@ -1697,13 +1682,13 @@ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', fo
1697
1682
  };
1698
1683
 
1699
1684
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1700
- function get(obj, path) {
1685
+ function get$1(obj, path) {
1701
1686
  if (!path.length) {
1702
1687
  return obj;
1703
1688
  }
1704
1689
  try {
1705
1690
  const [currentPath, ...nextPath] = path;
1706
- return get(obj[currentPath], nextPath);
1691
+ return get$1(obj[currentPath], nextPath);
1707
1692
  }
1708
1693
  catch (err) {
1709
1694
  return undefined;
@@ -1711,7 +1696,7 @@ function get(obj, path) {
1711
1696
  }
1712
1697
 
1713
1698
  const getBoundValue = (entryOrAsset, path) => {
1714
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1699
+ const value = get$1(entryOrAsset, path.split('/').slice(2, -1));
1715
1700
  return value && typeof value == 'object' && value.url
1716
1701
  ? value.url
1717
1702
  : value;
@@ -1741,7 +1726,9 @@ const transformRichText = (entryOrAsset, entityStore, path) => {
1741
1726
  }
1742
1727
  if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
1743
1728
  // resolve any links to assets/entries/hyperlinks
1744
- const richTextDocument = value;
1729
+ // we need to clone, as we want to keep the original Entity in the EntityStore intact,
1730
+ // and resolveLinks() is mutating the node object.
1731
+ const richTextDocument = structuredClone(value);
1745
1732
  resolveLinks(richTextDocument, entityStore);
1746
1733
  return richTextDocument;
1747
1734
  }
@@ -1796,7 +1783,7 @@ const transformMedia = (asset, variables, resolveDesignValue, variableName, path
1796
1783
  let value;
1797
1784
  // If it is not a deep path and not pointing to the file of the asset,
1798
1785
  // it is just pointing to a normal field and therefore we just resolve the value as normal field
1799
- if (!isDeepPath(path) && !lastPathNamedSegmentEq(path, 'file')) {
1786
+ if (!isDeepPath$1(path) && !lastPathNamedSegmentEq(path, 'file')) {
1800
1787
  return getBoundValue(asset, path);
1801
1788
  }
1802
1789
  //TODO: this will be better served by injectable type transformers instead of if statement
@@ -1854,104 +1841,97 @@ const transformMedia = (asset, variables, resolveDesignValue, variableName, path
1854
1841
  }
1855
1842
  return asset.fields.file?.url;
1856
1843
  };
1857
-
1858
- const isAsset = (value) => {
1844
+ const isEntry$1 = (value) => {
1859
1845
  return (null !== value &&
1860
1846
  typeof value === 'object' &&
1861
1847
  'sys' in value &&
1862
- value.sys?.type === 'Asset');
1848
+ value.sys?.type === 'Entry');
1863
1849
  };
1864
- const isEntry = (value) => {
1850
+ const isAsset$1 = (value) => {
1865
1851
  return (null !== value &&
1866
1852
  typeof value === 'object' &&
1867
1853
  'sys' in value &&
1868
- value.sys?.type === 'Entry');
1854
+ value.sys?.type === 'Asset');
1869
1855
  };
1870
1856
 
1871
1857
  function getResolvedEntryFromLink(entryOrAsset, path, entityStore) {
1872
- if (isAsset(entryOrAsset)) {
1858
+ if (isAsset$1(entryOrAsset)) {
1873
1859
  return entryOrAsset;
1874
1860
  }
1875
- else if (!isEntry(entryOrAsset)) {
1861
+ else if (!isEntry$1(entryOrAsset)) {
1876
1862
  throw new Error(`Expected an Entry or Asset, but got: ${JSON.stringify(entryOrAsset)}`);
1877
1863
  }
1878
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1864
+ const fieldName = path.split('/').slice(2, -1);
1865
+ const value = get$1(entryOrAsset, fieldName);
1879
1866
  let resolvedEntity;
1880
- if (isAsset(value) || isEntry(value)) {
1867
+ if (isAsset$1(value) || isEntry$1(value)) {
1881
1868
  // In some cases, reference fields are already resolved
1882
1869
  resolvedEntity = value;
1883
1870
  }
1884
1871
  else if (value?.sys.type === 'Link') {
1885
1872
  // Look up the reference in the entity store
1886
1873
  resolvedEntity = entityStore.getEntityFromLink(value);
1887
- if (!resolvedEntity) {
1888
- return;
1889
- }
1890
1874
  }
1891
1875
  else {
1892
- console.warn(`Expected a link to a reference, but got: ${JSON.stringify(value)}`);
1876
+ 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 });
1877
+ return;
1878
+ }
1879
+ // no need to make structuredClone(entityStore.getEntityFromLink(value)) because
1880
+ // we provide component with the original Object.frozen object of the entity.
1881
+ // As we don't resolve L3 and don't mutate the entity before returning anymore,
1882
+ // we don't need to make a copy of the entity. And even provide better referential integrity
1883
+ // for the component for the same entity.
1884
+ if (!resolvedEntity) {
1893
1885
  return;
1894
1886
  }
1895
- //resolve any embedded links - we currently only support 2 levels deep
1896
- const fields = resolvedEntity.fields || {};
1897
- Object.entries(fields).forEach(([fieldKey, field]) => {
1898
- if (field && field.sys?.type === 'Link') {
1899
- const entity = entityStore.getEntityFromLink(field);
1900
- if (entity) {
1901
- resolvedEntity.fields[fieldKey] = entity;
1902
- }
1903
- }
1904
- else if (field && Array.isArray(field)) {
1905
- resolvedEntity.fields[fieldKey] = field.map((innerField) => {
1906
- if (innerField && innerField.sys?.type === 'Link') {
1907
- const entity = entityStore.getEntityFromLink(innerField);
1908
- if (entity) {
1909
- return entity;
1910
- }
1911
- }
1912
- return innerField;
1913
- });
1914
- }
1915
- });
1916
1887
  return resolvedEntity;
1917
1888
  }
1918
1889
 
1890
+ const excludeUndefined = (value) => {
1891
+ return value !== undefined;
1892
+ };
1919
1893
  function getArrayValue(entryOrAsset, path, entityStore) {
1894
+ // NOTE: Not sure if we need this if-statement,
1895
+ // as it is NOT possible to bind to Array variable an Asset
1896
+ // (as Assets don't have multi-reference fields) unless it's a degenerate case.
1920
1897
  if (entryOrAsset.sys.type === 'Asset') {
1921
1898
  return entryOrAsset;
1922
1899
  }
1923
- const arrayValue = get(entryOrAsset, path.split('/').slice(2, -1));
1900
+ const fieldName = path.split('/').slice(2, -1);
1901
+ const arrayValue = get$1(entryOrAsset, fieldName);
1924
1902
  if (!isArray(arrayValue)) {
1925
- console.warn(`Expected a value to be an array, but got: ${JSON.stringify(arrayValue)}`);
1903
+ 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 });
1926
1904
  return;
1927
1905
  }
1928
- const result = arrayValue.map((value) => {
1906
+ const result = arrayValue
1907
+ .map((value) => {
1929
1908
  if (typeof value === 'string') {
1930
- return value;
1909
+ return value; // handles case where Text array is bound (in [Content Model] tab of the platform, select Text and make it a list)
1931
1910
  }
1932
1911
  else if (value?.sys?.type === 'Link') {
1933
1912
  const resolvedEntity = entityStore.getEntityFromLink(value);
1934
1913
  if (!resolvedEntity) {
1914
+ // We return undefined, which means that entity wasn't availble in the Entity Store due to:
1915
+ // - because it's archived entity (and they normally wouldn't be sent to the Entity Store)
1916
+ // - bug where some entity wasn't added to the Entity Store
1917
+ // 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)
1935
1918
  return;
1936
1919
  }
1937
- //resolve any embedded links - we currently only support 2 levels deep
1938
- const fields = resolvedEntity.fields || {};
1939
- Object.entries(fields).forEach(([fieldKey, field]) => {
1940
- if (field && field.sys?.type === 'Link') {
1941
- const entity = entityStore.getEntityFromLink(field);
1942
- if (entity) {
1943
- resolvedEntity.fields[fieldKey] = entity;
1944
- }
1945
- }
1946
- });
1947
1920
  return resolvedEntity;
1948
1921
  }
1949
1922
  else {
1950
1923
  console.warn(`Expected value to be a string or Link, but got: ${JSON.stringify(value)}`);
1951
1924
  return undefined;
1952
1925
  }
1953
- });
1954
- return result;
1926
+ })
1927
+ .filter(excludeUndefined);
1928
+ // eg. imagine you have multi-referene field with 3 links to archived entries,
1929
+ // all of them will be undefined on previous step and will be filtered out
1930
+ // of resultWithoutUndefined. Instead of passing to component an empty array,
1931
+ // we pass undefined. This means that develloper making custom component
1932
+ // does not have to handle empty array case. But only undefiened, which signals:
1933
+ // user didn't bind anything; user bound to reference field which is unset; all references are archived
1934
+ return result.length > 0 ? result : undefined;
1955
1935
  }
1956
1936
 
1957
1937
  const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableType, path) => {
@@ -2110,11 +2090,22 @@ const sendMessage = (eventType, data) => {
2110
2090
  }, '*');
2111
2091
  };
2112
2092
 
2093
+ function deepFreeze$1(obj) {
2094
+ const propNames = Object.getOwnPropertyNames(obj);
2095
+ for (const name of propNames) {
2096
+ const value = obj[name];
2097
+ if (value && typeof value === 'object') {
2098
+ deepFreeze$1(value);
2099
+ }
2100
+ }
2101
+ return Object.freeze(obj);
2102
+ }
2103
+
2113
2104
  /**
2114
2105
  * Base Store for entities
2115
2106
  * Can be extended for the different loading behaviours (editor, production, ..)
2116
2107
  */
2117
- class EntityStoreBase {
2108
+ let EntityStoreBase$1 = class EntityStoreBase {
2118
2109
  constructor({ entities, locale }) {
2119
2110
  /* serialized */ this.entryMap = new Map();
2120
2111
  /* serialized */ this.assetMap = new Map();
@@ -2130,11 +2121,11 @@ class EntityStoreBase {
2130
2121
  this.addEntity(entity);
2131
2122
  }
2132
2123
  getEntryOrAsset(linkOrEntryOrAsset, path) {
2133
- if (isDeepPath(path)) {
2124
+ if (isDeepPath$1(path)) {
2134
2125
  return this.getDeepEntry(linkOrEntryOrAsset, path);
2135
2126
  }
2136
2127
  let entity;
2137
- if (isLink(linkOrEntryOrAsset)) {
2128
+ if (isLink$1(linkOrEntryOrAsset)) {
2138
2129
  const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
2139
2130
  ? this.entryMap.get(linkOrEntryOrAsset.sys.id)
2140
2131
  : this.assetMap.get(linkOrEntryOrAsset.sys.id);
@@ -2144,7 +2135,7 @@ class EntityStoreBase {
2144
2135
  }
2145
2136
  entity = resolvedEntity;
2146
2137
  }
2147
- else if (isAsset(linkOrEntryOrAsset) || isEntry(linkOrEntryOrAsset)) {
2138
+ else if (isAsset$1(linkOrEntryOrAsset) || isEntry$1(linkOrEntryOrAsset)) {
2148
2139
  // We already have the complete entity in preview & delivery (resolved by the CMA client)
2149
2140
  entity = linkOrEntryOrAsset;
2150
2141
  }
@@ -2166,7 +2157,7 @@ class EntityStoreBase {
2166
2157
  console.warn(`Unresolved entity reference: ${entityLink.sys.linkType} with ID ${entityLink.sys.id}`);
2167
2158
  return;
2168
2159
  }
2169
- return get(entity, path);
2160
+ return get$1(entity, path);
2170
2161
  }
2171
2162
  getEntityFromLink(link) {
2172
2163
  const resolvedEntity = link.sys.linkType === 'Entry'
@@ -2178,6 +2169,22 @@ class EntityStoreBase {
2178
2169
  }
2179
2170
  return resolvedEntity;
2180
2171
  }
2172
+ getAssetById(assetId) {
2173
+ const asset = this.assetMap.get(assetId);
2174
+ if (!asset) {
2175
+ console.warn(`Asset with ID "${assetId}" is not found in the store`);
2176
+ return;
2177
+ }
2178
+ return asset;
2179
+ }
2180
+ getEntryById(entryId) {
2181
+ const entry = this.entryMap.get(entryId);
2182
+ if (!entry) {
2183
+ console.warn(`Entry with ID "${entryId}" is not found in the store`);
2184
+ return;
2185
+ }
2186
+ return entry;
2187
+ }
2181
2188
  getEntitiesFromMap(type, ids) {
2182
2189
  const resolved = [];
2183
2190
  const missing = [];
@@ -2196,11 +2203,13 @@ class EntityStoreBase {
2196
2203
  };
2197
2204
  }
2198
2205
  addEntity(entity) {
2199
- if (isAsset(entity)) {
2200
- this.assetMap.set(entity.sys.id, entity);
2206
+ if (isAsset$1(entity)) {
2207
+ // cloned and frozen
2208
+ this.assetMap.set(entity.sys.id, deepFreeze$1(structuredClone(entity)));
2201
2209
  }
2202
- else if (isEntry(entity)) {
2203
- this.entryMap.set(entity.sys.id, entity);
2210
+ else if (isEntry$1(entity)) {
2211
+ // cloned and frozen
2212
+ this.entryMap.set(entity.sys.id, deepFreeze$1(structuredClone(entity)));
2204
2213
  }
2205
2214
  else {
2206
2215
  throw new Error(`Attempted to add an entity to the store that is neither Asset nor Entry: '${JSON.stringify(entity)}'`);
@@ -2253,7 +2262,7 @@ class EntityStoreBase {
2253
2262
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2254
2263
  break;
2255
2264
  }
2256
- const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
2265
+ const fieldValue = get$1(entityToResolveFieldsFrom, ['fields', field]);
2257
2266
  if (undefined === fieldValue) {
2258
2267
  return {
2259
2268
  resolvedFieldset,
@@ -2261,7 +2270,7 @@ class EntityStoreBase {
2261
2270
  reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
2262
2271
  };
2263
2272
  }
2264
- else if (isLink(fieldValue)) {
2273
+ else if (isLink$1(fieldValue)) {
2265
2274
  const entity = this.getEntityFromLink(fieldValue);
2266
2275
  if (entity === undefined) {
2267
2276
  return {
@@ -2273,7 +2282,7 @@ class EntityStoreBase {
2273
2282
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2274
2283
  entityToResolveFieldsFrom = entity; // we move up
2275
2284
  }
2276
- else if (isAsset(fieldValue) || isEntry(fieldValue)) {
2285
+ else if (isAsset$1(fieldValue) || isEntry$1(fieldValue)) {
2277
2286
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2278
2287
  entityToResolveFieldsFrom = fieldValue; // we move up
2279
2288
  }
@@ -2290,13 +2299,13 @@ class EntityStoreBase {
2290
2299
  isFullyResolved: true,
2291
2300
  };
2292
2301
  };
2293
- const headEntity = isLink(linkOrEntryOrAsset)
2302
+ const headEntity = isLink$1(linkOrEntryOrAsset)
2294
2303
  ? this.getEntityFromLink(linkOrEntryOrAsset)
2295
2304
  : linkOrEntryOrAsset;
2296
2305
  if (undefined === headEntity) {
2297
2306
  return;
2298
2307
  }
2299
- const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
2308
+ const unresolvedFieldset = parseDataSourcePathIntoFieldset$1(path);
2300
2309
  // The purpose here is to take this intermediate representation of the deep-path
2301
2310
  // and to follow the links to the leaf-entity and field
2302
2311
  // in case we can't follow till the end, we should signal that there was null-reference in the path
@@ -2322,13 +2331,13 @@ class EntityStoreBase {
2322
2331
  locale: this.locale,
2323
2332
  };
2324
2333
  }
2325
- }
2334
+ };
2326
2335
 
2327
2336
  /**
2328
2337
  * EntityStore which resolves entries and assets from the editor
2329
2338
  * over the sendMessage and subscribe functions.
2330
2339
  */
2331
- class EditorEntityStore extends EntityStoreBase {
2340
+ class EditorEntityStore extends EntityStoreBase$1 {
2332
2341
  constructor({ entities, locale, sendMessage, subscribe, timeoutDuration = 3000, }) {
2333
2342
  super({ entities, locale });
2334
2343
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2465,7 +2474,6 @@ class EditorModeEntityStore extends EditorEntityStore {
2465
2474
  };
2466
2475
  };
2467
2476
  super({ entities, sendMessage, subscribe, locale, timeoutDuration: REQUEST_TIMEOUT });
2468
- this.locale = locale;
2469
2477
  }
2470
2478
  /**
2471
2479
  * This function collects and returns the list of requested entries and assets. Additionally, it checks
@@ -2495,11 +2503,91 @@ class EditorModeEntityStore extends EditorEntityStore {
2495
2503
  if (!entity) {
2496
2504
  return;
2497
2505
  }
2498
- const fieldValue = get(entity, path);
2506
+ const fieldValue = get$1(entity, path);
2499
2507
  return transformAssetFileToUrl(fieldValue);
2500
2508
  }
2501
2509
  }
2502
2510
 
2511
+ let UninitializedEntityStore$1 = class UninitializedEntityStore extends EntityStoreBase$1 {
2512
+ constructor() {
2513
+ super({ entities: [], locale: 'uninitialized-locale-in-uninitialized-entity-store' });
2514
+ }
2515
+ };
2516
+
2517
+ const inMemoryEntitiesStore = create((set, get) => ({
2518
+ // The UninitializedEntityStore is a placeholder instance and is here to highlight the
2519
+ // // fact that it's not used by anything until during loading lifecycle it'sreplaced by real entity store:
2520
+ // - in Preview+Delivery mode: right after we fetch Expereince and it entities
2521
+ // - in EDITOR (VisualEditor) mode: right after the VisualEditor is async imported and initialize event happens
2522
+ entityStore: new UninitializedEntityStore$1(),
2523
+ areEntitiesFetched: false,
2524
+ setEntitiesFetched(fetched) {
2525
+ set({ areEntitiesFetched: fetched });
2526
+ },
2527
+ resolveAssetById(assetId) {
2528
+ if (!assetId)
2529
+ return undefined;
2530
+ const { entityStore } = get();
2531
+ return entityStore.getAssetById(assetId);
2532
+ },
2533
+ resolveEntryById(entryId) {
2534
+ if (!entryId)
2535
+ return undefined;
2536
+ const { entityStore } = get();
2537
+ return entityStore.getEntryById(entryId);
2538
+ },
2539
+ resolveEntity(link) {
2540
+ if (!link)
2541
+ return undefined;
2542
+ const { entityStore } = get();
2543
+ return entityStore.getEntityFromLink(link);
2544
+ },
2545
+ resetEntityStore(entityStore) {
2546
+ set({
2547
+ entityStore,
2548
+ areEntitiesFetched: false,
2549
+ });
2550
+ },
2551
+ }));
2552
+
2553
+ function maybeResolveLink(maybeLink) {
2554
+ if (!isLink$1(maybeLink)) {
2555
+ console.warn('maybeResolveLink function must receive Link shape. Provided argument does not match the Link shape: ', maybeLink);
2556
+ return undefined;
2557
+ }
2558
+ return inMemoryEntitiesStore.getState().resolveEntity(maybeLink);
2559
+ }
2560
+ function maybeResolveByAssetId(assetId) {
2561
+ return inMemoryEntitiesStore.getState().resolveAssetById(assetId);
2562
+ }
2563
+ function maybeResolveByEntryId(entryId) {
2564
+ return inMemoryEntitiesStore.getState().resolveEntryById(entryId);
2565
+ }
2566
+ function hasEntry(entryId) {
2567
+ return Boolean(maybeResolveByEntryId(entryId));
2568
+ }
2569
+ function hasAsset(assetId) {
2570
+ return Boolean(maybeResolveByAssetId(assetId));
2571
+ }
2572
+ function addEntities(entities) {
2573
+ if (!Array.isArray(entities) || entities.length === 0) {
2574
+ return;
2575
+ }
2576
+ const { entityStore } = inMemoryEntitiesStore.getState();
2577
+ const definedEntities = entities.filter(Boolean);
2578
+ for (const entity of definedEntities) {
2579
+ entityStore.updateEntity(entity);
2580
+ }
2581
+ }
2582
+ const inMemoryEntities = {
2583
+ maybeResolveLink,
2584
+ maybeResolveByAssetId,
2585
+ maybeResolveByEntryId,
2586
+ hasEntry,
2587
+ hasAsset,
2588
+ addEntities,
2589
+ };
2590
+
2503
2591
  var VisualEditorMode$1;
2504
2592
  (function (VisualEditorMode) {
2505
2593
  VisualEditorMode["LazyLoad"] = "lazyLoad";
@@ -2529,7 +2617,7 @@ class DeepReference {
2529
2617
  // field references nothing (or even field doesn't exist)
2530
2618
  return undefined;
2531
2619
  }
2532
- if (!isLink(maybeReferentLink)) {
2620
+ if (!isLink$1(maybeReferentLink)) {
2533
2621
  // Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
2534
2622
  // Under normal circumstance we expect field to be a Link, but it could be an "impostor"
2535
2623
  // eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
@@ -2549,7 +2637,7 @@ function gatherDeepReferencesFromTree(startingNode, dataSource) {
2549
2637
  for (const [, variableMapping] of Object.entries(node.data.props)) {
2550
2638
  if (variableMapping.type !== 'BoundValue')
2551
2639
  continue;
2552
- if (!isDeepPath(variableMapping.path))
2640
+ if (!isDeepPath$1(variableMapping.path))
2553
2641
  continue;
2554
2642
  deepReferences.push(DeepReference.from({
2555
2643
  path: variableMapping.path,
@@ -3158,6 +3246,26 @@ const useTreeStore = create((set, get) => ({
3158
3246
  reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
3159
3247
  }));
3160
3248
  },
3249
+ // breadth first search
3250
+ findNodeById(nodeId) {
3251
+ if (!nodeId) {
3252
+ return null;
3253
+ }
3254
+ const rootNode = get().tree.root;
3255
+ const visitedNodeIds = [];
3256
+ const queue = [];
3257
+ let currentNode = rootNode;
3258
+ queue.push(currentNode);
3259
+ while (queue.length) {
3260
+ currentNode = queue.shift();
3261
+ visitedNodeIds.push(currentNode.data.id);
3262
+ if (currentNode.data.id === nodeId) {
3263
+ return currentNode;
3264
+ }
3265
+ queue.push(...currentNode.children);
3266
+ }
3267
+ return null;
3268
+ },
3161
3269
  }));
3162
3270
  const hasBreakpointDiffs = (currentTree, newTree) => {
3163
3271
  const currentBreakpoints = currentTree?.root?.data?.breakpoints ?? [];
@@ -3932,6 +4040,419 @@ class DebugLogger {
3932
4040
  DebugLogger.instance = null;
3933
4041
  DebugLogger.getInstance();
3934
4042
 
4043
+ const isLink = (maybeLink) => {
4044
+ if (maybeLink === null)
4045
+ return false;
4046
+ if (typeof maybeLink !== 'object')
4047
+ return false;
4048
+ const link = maybeLink;
4049
+ return Boolean(link.sys?.id) && link.sys?.type === 'Link' && Boolean(link.sys?.linkType);
4050
+ };
4051
+
4052
+ /**
4053
+ * This module encapsulates format of the path to a deep reference.
4054
+ */
4055
+ const parseDataSourcePathIntoFieldset = (path) => {
4056
+ const parsedPath = parseDeepPath(path);
4057
+ if (null === parsedPath) {
4058
+ throw new Error(`Cannot parse path '${path}' as deep path`);
4059
+ }
4060
+ return parsedPath.fields.map((field) => [null, field, '~locale']);
4061
+ };
4062
+ /**
4063
+ * Detects if paths is valid deep-path, like:
4064
+ * - /gV6yKXp61hfYrR7rEyKxY/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
4065
+ * or regular, like:
4066
+ * - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
4067
+ * @returns
4068
+ */
4069
+ const isDeepPath = (deepPathCandidate) => {
4070
+ const deepPathParsed = parseDeepPath(deepPathCandidate);
4071
+ if (!deepPathParsed) {
4072
+ return false;
4073
+ }
4074
+ return deepPathParsed.fields.length > 1;
4075
+ };
4076
+ const parseDeepPath = (deepPathCandidate) => {
4077
+ // ALGORITHM:
4078
+ // We start with deep path in form:
4079
+ // /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
4080
+ // First turn string into array of segments
4081
+ // ['', 'uuid123', 'fields', 'mainStory', '~locale', 'fields', 'cover', '~locale', 'fields', 'title', '~locale']
4082
+ // Then group segments into intermediate represenatation - chunks, where each non-initial chunk starts with 'fields'
4083
+ // [
4084
+ // [ "", "uuid123" ],
4085
+ // [ "fields", "mainStory", "~locale" ],
4086
+ // [ "fields", "cover", "~locale" ],
4087
+ // [ "fields", "title", "~locale" ]
4088
+ // ]
4089
+ // Then check "initial" chunk for corretness
4090
+ // Then check all "field-leading" chunks for correctness
4091
+ const isValidInitialChunk = (initialChunk) => {
4092
+ // must have start with '' and have at least 2 segments, second non-empty
4093
+ // eg. /-_432uuid123123
4094
+ return /^\/([^/^~]+)$/.test(initialChunk.join('/'));
4095
+ };
4096
+ const isValidFieldChunk = (fieldChunk) => {
4097
+ // must start with 'fields' and have at least 3 segments, second non-empty and last segment must be '~locale'
4098
+ // eg. fields/-32234mainStory/~locale
4099
+ return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
4100
+ };
4101
+ const deepPathSegments = deepPathCandidate.split('/');
4102
+ const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
4103
+ if (chunks.length <= 1) {
4104
+ return null; // malformed path, even regular paths have at least 2 chunks
4105
+ }
4106
+ else if (chunks.length === 2) {
4107
+ return null; // deep paths have at least 3 chunks
4108
+ }
4109
+ // With 3+ chunks we can now check for deep path correctness
4110
+ const [initialChunk, ...fieldChunks] = chunks;
4111
+ if (!isValidInitialChunk(initialChunk)) {
4112
+ return null;
4113
+ }
4114
+ if (!fieldChunks.every(isValidFieldChunk)) {
4115
+ return null;
4116
+ }
4117
+ return {
4118
+ key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
4119
+ fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
4120
+ };
4121
+ };
4122
+ const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
4123
+ const chunks = [];
4124
+ let currentChunk = [];
4125
+ const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
4126
+ const excludeEmptyChunks = (chunk) => chunk.length > 0;
4127
+ for (let i = 0; i < segments.length; i++) {
4128
+ const isInitialElement = i === 0;
4129
+ const segment = segments[i];
4130
+ if (isInitialElement) {
4131
+ currentChunk = [segment];
4132
+ }
4133
+ else if (isSegmentBeginningOfChunk(segment)) {
4134
+ chunks.push(currentChunk);
4135
+ currentChunk = [segment];
4136
+ }
4137
+ else {
4138
+ currentChunk.push(segment);
4139
+ }
4140
+ }
4141
+ chunks.push(currentChunk);
4142
+ return chunks.filter(excludeEmptyChunks);
4143
+ };
4144
+
4145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4146
+ function get(obj, path) {
4147
+ if (!path.length) {
4148
+ return obj;
4149
+ }
4150
+ try {
4151
+ const [currentPath, ...nextPath] = path;
4152
+ return get(obj[currentPath], nextPath);
4153
+ }
4154
+ catch (err) {
4155
+ return undefined;
4156
+ }
4157
+ }
4158
+ const isEntry = (value) => {
4159
+ return (null !== value &&
4160
+ typeof value === 'object' &&
4161
+ 'sys' in value &&
4162
+ value.sys?.type === 'Entry');
4163
+ };
4164
+ const isAsset = (value) => {
4165
+ return (null !== value &&
4166
+ typeof value === 'object' &&
4167
+ 'sys' in value &&
4168
+ value.sys?.type === 'Asset');
4169
+ };
4170
+
4171
+ function deepFreeze(obj) {
4172
+ const propNames = Object.getOwnPropertyNames(obj);
4173
+ for (const name of propNames) {
4174
+ const value = obj[name];
4175
+ if (value && typeof value === 'object') {
4176
+ deepFreeze(value);
4177
+ }
4178
+ }
4179
+ return Object.freeze(obj);
4180
+ }
4181
+
4182
+ /**
4183
+ * Base Store for entities
4184
+ * Can be extended for the different loading behaviours (editor, production, ..)
4185
+ */
4186
+ class EntityStoreBase {
4187
+ constructor({ entities, locale }) {
4188
+ /* serialized */ this.entryMap = new Map();
4189
+ /* serialized */ this.assetMap = new Map();
4190
+ this.locale = locale;
4191
+ for (const entity of entities) {
4192
+ this.addEntity(entity);
4193
+ }
4194
+ }
4195
+ get entities() {
4196
+ return [...this.entryMap.values(), ...this.assetMap.values()];
4197
+ }
4198
+ updateEntity(entity) {
4199
+ this.addEntity(entity);
4200
+ }
4201
+ getEntryOrAsset(linkOrEntryOrAsset, path) {
4202
+ if (isDeepPath(path)) {
4203
+ return this.getDeepEntry(linkOrEntryOrAsset, path);
4204
+ }
4205
+ let entity;
4206
+ if (isLink(linkOrEntryOrAsset)) {
4207
+ const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
4208
+ ? this.entryMap.get(linkOrEntryOrAsset.sys.id)
4209
+ : this.assetMap.get(linkOrEntryOrAsset.sys.id);
4210
+ if (!resolvedEntity || resolvedEntity.sys.type !== linkOrEntryOrAsset.sys.linkType) {
4211
+ console.warn(`Experience references unresolved entity: ${JSON.stringify(linkOrEntryOrAsset)}`);
4212
+ return;
4213
+ }
4214
+ entity = resolvedEntity;
4215
+ }
4216
+ else if (isAsset(linkOrEntryOrAsset) || isEntry(linkOrEntryOrAsset)) {
4217
+ // We already have the complete entity in preview & delivery (resolved by the CMA client)
4218
+ entity = linkOrEntryOrAsset;
4219
+ }
4220
+ else {
4221
+ throw new Error(`Unexpected object when resolving entity: ${JSON.stringify(linkOrEntryOrAsset)}`);
4222
+ }
4223
+ return entity;
4224
+ }
4225
+ /**
4226
+ * @deprecated in the base class this should be simply an abstract method
4227
+ * @param entityLink
4228
+ * @param path
4229
+ * @returns
4230
+ */
4231
+ getValue(entityLink, path) {
4232
+ const entity = this.getEntity(entityLink.sys.linkType, entityLink.sys.id);
4233
+ if (!entity) {
4234
+ // TODO: move to `debug` utils once it is extracted
4235
+ console.warn(`Unresolved entity reference: ${entityLink.sys.linkType} with ID ${entityLink.sys.id}`);
4236
+ return;
4237
+ }
4238
+ return get(entity, path);
4239
+ }
4240
+ getEntityFromLink(link) {
4241
+ const resolvedEntity = link.sys.linkType === 'Entry'
4242
+ ? this.entryMap.get(link.sys.id)
4243
+ : this.assetMap.get(link.sys.id);
4244
+ if (!resolvedEntity || resolvedEntity.sys.type !== link.sys.linkType) {
4245
+ console.warn(`Experience references unresolved entity: ${JSON.stringify(link)}`);
4246
+ return;
4247
+ }
4248
+ return resolvedEntity;
4249
+ }
4250
+ getAssetById(assetId) {
4251
+ const asset = this.assetMap.get(assetId);
4252
+ if (!asset) {
4253
+ console.warn(`Asset with ID "${assetId}" is not found in the store`);
4254
+ return;
4255
+ }
4256
+ return asset;
4257
+ }
4258
+ getEntryById(entryId) {
4259
+ const entry = this.entryMap.get(entryId);
4260
+ if (!entry) {
4261
+ console.warn(`Entry with ID "${entryId}" is not found in the store`);
4262
+ return;
4263
+ }
4264
+ return entry;
4265
+ }
4266
+ getEntitiesFromMap(type, ids) {
4267
+ const resolved = [];
4268
+ const missing = [];
4269
+ for (const id of ids) {
4270
+ const entity = this.getEntity(type, id);
4271
+ if (entity) {
4272
+ resolved.push(entity);
4273
+ }
4274
+ else {
4275
+ missing.push(id);
4276
+ }
4277
+ }
4278
+ return {
4279
+ resolved,
4280
+ missing,
4281
+ };
4282
+ }
4283
+ addEntity(entity) {
4284
+ if (isAsset(entity)) {
4285
+ // cloned and frozen
4286
+ this.assetMap.set(entity.sys.id, deepFreeze(structuredClone(entity)));
4287
+ }
4288
+ else if (isEntry(entity)) {
4289
+ // cloned and frozen
4290
+ this.entryMap.set(entity.sys.id, deepFreeze(structuredClone(entity)));
4291
+ }
4292
+ else {
4293
+ throw new Error(`Attempted to add an entity to the store that is neither Asset nor Entry: '${JSON.stringify(entity)}'`);
4294
+ }
4295
+ }
4296
+ async fetchAsset(id) {
4297
+ const { resolved, missing } = this.getEntitiesFromMap('Asset', [id]);
4298
+ if (missing.length) {
4299
+ // TODO: move to `debug` utils once it is extracted
4300
+ console.warn(`Asset "${id}" is not in the store`);
4301
+ return;
4302
+ }
4303
+ return resolved[0];
4304
+ }
4305
+ async fetchAssets(ids) {
4306
+ const { resolved, missing } = this.getEntitiesFromMap('Asset', ids);
4307
+ if (missing.length) {
4308
+ throw new Error(`Missing assets in the store (${missing.join(',')})`);
4309
+ }
4310
+ return resolved;
4311
+ }
4312
+ async fetchEntry(id) {
4313
+ const { resolved, missing } = this.getEntitiesFromMap('Entry', [id]);
4314
+ if (missing.length) {
4315
+ // TODO: move to `debug` utils once it is extracted
4316
+ console.warn(`Entry "${id}" is not in the store`);
4317
+ return;
4318
+ }
4319
+ return resolved[0];
4320
+ }
4321
+ async fetchEntries(ids) {
4322
+ const { resolved, missing } = this.getEntitiesFromMap('Entry', ids);
4323
+ if (missing.length) {
4324
+ throw new Error(`Missing assets in the store (${missing.join(',')})`);
4325
+ }
4326
+ return resolved;
4327
+ }
4328
+ getDeepEntry(linkOrEntryOrAsset, path) {
4329
+ const resolveFieldset = (unresolvedFieldset, headEntry) => {
4330
+ const resolvedFieldset = [];
4331
+ let entityToResolveFieldsFrom = headEntry;
4332
+ for (let i = 0; i < unresolvedFieldset.length; i++) {
4333
+ const isLeaf = i === unresolvedFieldset.length - 1; // with last row, we are not expecting a link, but a value
4334
+ const row = unresolvedFieldset[i];
4335
+ const [, field, _localeQualifier] = row;
4336
+ if (!entityToResolveFieldsFrom) {
4337
+ throw new Error(`Logic Error: Cannot resolve field ${field} of a fieldset as there is no entity to resolve it from.`);
4338
+ }
4339
+ if (isLeaf) {
4340
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
4341
+ break;
4342
+ }
4343
+ const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
4344
+ if (undefined === fieldValue) {
4345
+ return {
4346
+ resolvedFieldset,
4347
+ isFullyResolved: false,
4348
+ reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
4349
+ };
4350
+ }
4351
+ else if (isLink(fieldValue)) {
4352
+ const entity = this.getEntityFromLink(fieldValue);
4353
+ if (entity === undefined) {
4354
+ return {
4355
+ resolvedFieldset,
4356
+ isFullyResolved: false,
4357
+ reason: `Field reference Link (sys.id=${fieldValue.sys.id}) not found in the EntityStore, waiting...`,
4358
+ };
4359
+ }
4360
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
4361
+ entityToResolveFieldsFrom = entity; // we move up
4362
+ }
4363
+ else if (isAsset(fieldValue) || isEntry(fieldValue)) {
4364
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
4365
+ entityToResolveFieldsFrom = fieldValue; // we move up
4366
+ }
4367
+ else {
4368
+ return {
4369
+ resolvedFieldset,
4370
+ isFullyResolved: false,
4371
+ reason: `Deep path points to an invalid field value of type '${typeof fieldValue}' (value=${fieldValue})`,
4372
+ };
4373
+ }
4374
+ }
4375
+ return {
4376
+ resolvedFieldset,
4377
+ isFullyResolved: true,
4378
+ };
4379
+ };
4380
+ const headEntity = isLink(linkOrEntryOrAsset)
4381
+ ? this.getEntityFromLink(linkOrEntryOrAsset)
4382
+ : linkOrEntryOrAsset;
4383
+ if (undefined === headEntity) {
4384
+ return;
4385
+ }
4386
+ const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
4387
+ // The purpose here is to take this intermediate representation of the deep-path
4388
+ // and to follow the links to the leaf-entity and field
4389
+ // in case we can't follow till the end, we should signal that there was null-reference in the path
4390
+ const { resolvedFieldset, isFullyResolved, reason } = resolveFieldset(unresolvedFieldset, headEntity);
4391
+ if (!isFullyResolved) {
4392
+ reason &&
4393
+ console.debug(`[exp-builder.sdk::EntityStoreBased::getValueDeep()] Deep path wasn't resolved till leaf node, falling back to undefined, because: ${reason}`);
4394
+ return;
4395
+ }
4396
+ const [leafEntity] = resolvedFieldset[resolvedFieldset.length - 1];
4397
+ return leafEntity;
4398
+ }
4399
+ getEntity(type, id) {
4400
+ if (type === 'Asset') {
4401
+ return this.assetMap.get(id);
4402
+ }
4403
+ return this.entryMap.get(id);
4404
+ }
4405
+ toJSON() {
4406
+ return {
4407
+ entryMap: Object.fromEntries(this.entryMap),
4408
+ assetMap: Object.fromEntries(this.assetMap),
4409
+ locale: this.locale,
4410
+ };
4411
+ }
4412
+ }
4413
+
4414
+ class UninitializedEntityStore extends EntityStoreBase {
4415
+ constructor() {
4416
+ super({ entities: [], locale: 'uninitialized-locale-in-uninitialized-entity-store' });
4417
+ }
4418
+ }
4419
+
4420
+ create((set, get) => ({
4421
+ // The UninitializedEntityStore is a placeholder instance and is here to highlight the
4422
+ // // fact that it's not used by anything until during loading lifecycle it'sreplaced by real entity store:
4423
+ // - in Preview+Delivery mode: right after we fetch Expereince and it entities
4424
+ // - in EDITOR (VisualEditor) mode: right after the VisualEditor is async imported and initialize event happens
4425
+ entityStore: new UninitializedEntityStore(),
4426
+ areEntitiesFetched: false,
4427
+ setEntitiesFetched(fetched) {
4428
+ set({ areEntitiesFetched: fetched });
4429
+ },
4430
+ resolveAssetById(assetId) {
4431
+ if (!assetId)
4432
+ return undefined;
4433
+ const { entityStore } = get();
4434
+ return entityStore.getAssetById(assetId);
4435
+ },
4436
+ resolveEntryById(entryId) {
4437
+ if (!entryId)
4438
+ return undefined;
4439
+ const { entityStore } = get();
4440
+ return entityStore.getEntryById(entryId);
4441
+ },
4442
+ resolveEntity(link) {
4443
+ if (!link)
4444
+ return undefined;
4445
+ const { entityStore } = get();
4446
+ return entityStore.getEntityFromLink(link);
4447
+ },
4448
+ resetEntityStore(entityStore) {
4449
+ set({
4450
+ entityStore,
4451
+ areEntitiesFetched: false,
4452
+ });
4453
+ },
4454
+ }));
4455
+
3935
4456
  var VisualEditorMode;
3936
4457
  (function (VisualEditorMode) {
3937
4458
  VisualEditorMode["LazyLoad"] = "lazyLoad";
@@ -3996,23 +4517,6 @@ const Assembly = (props) => {
3996
4517
  return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
3997
4518
  };
3998
4519
 
3999
- const useEntityStore = create((set) => ({
4000
- entityStore: new EditorModeEntityStore({ locale: 'en-US', entities: [] }),
4001
- areEntitiesFetched: false,
4002
- setEntitiesFetched(fetched) {
4003
- set({ areEntitiesFetched: fetched });
4004
- },
4005
- resetEntityStore(locale, entities = []) {
4006
- console.debug(`[experiences-sdk-react] Resetting entity store because the locale changed to '${locale}'.`);
4007
- const newEntityStore = new EditorModeEntityStore({ locale, entities });
4008
- set({
4009
- entityStore: newEntityStore,
4010
- areEntitiesFetched: false,
4011
- });
4012
- return newEntityStore;
4013
- },
4014
- }));
4015
-
4016
4520
  class DragState {
4017
4521
  constructor() {
4018
4522
  this.isDragStartedOnParent = false;
@@ -4082,10 +4586,11 @@ class SimulateDnD extends DragState {
4082
4586
  }
4083
4587
  var SimulateDnD$1 = new SimulateDnD();
4084
4588
 
4085
- function useEditorSubscriber() {
4086
- const entityStore = useEntityStore((state) => state.entityStore);
4087
- const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
4088
- const setEntitiesFetched = useEntityStore((state) => state.setEntitiesFetched);
4589
+ function useEditorSubscriber(entityCache) {
4590
+ const entityStore = entityCache((state) => state.entityStore);
4591
+ const areEntitiesFetched = entityCache((state) => state.areEntitiesFetched);
4592
+ const setEntitiesFetched = entityCache((state) => state.setEntitiesFetched);
4593
+ const resetEntityStore = entityCache((state) => state.resetEntityStore);
4089
4594
  const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
4090
4595
  updateTree: state.updateTree,
4091
4596
  updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
@@ -4097,7 +4602,6 @@ function useEditorSubscriber() {
4097
4602
  const setDataSource = useEditorStore((state) => state.setDataSource);
4098
4603
  const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
4099
4604
  const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
4100
- const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
4101
4605
  const setComponentId = useDraggedItemStore((state) => state.setComponentId);
4102
4606
  const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
4103
4607
  const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
@@ -4148,7 +4652,7 @@ function useEditorSubscriber() {
4148
4652
  const isMissingL2Entities = (deepReferences) => {
4149
4653
  const referentLinks = deepReferences
4150
4654
  .map((deepReference) => deepReference.extractReferent(entityStore))
4151
- .filter(isLink);
4655
+ .filter(isLink$1);
4152
4656
  const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4153
4657
  return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
4154
4658
  };
@@ -4165,7 +4669,7 @@ function useEditorSubscriber() {
4165
4669
  const fillupL2 = async ({ deepReferences }) => {
4166
4670
  const referentLinks = deepReferences
4167
4671
  .map((deepReference) => deepReference.extractReferent(entityStore))
4168
- .filter(isLink);
4672
+ .filter(isLink$1);
4169
4673
  const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4170
4674
  await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
4171
4675
  };
@@ -4218,8 +4722,9 @@ function useEditorSubscriber() {
4218
4722
  }
4219
4723
  let newEntityStore = entityStore;
4220
4724
  if (entityStore.locale !== locale) {
4221
- newEntityStore = resetEntityStore(locale);
4725
+ newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
4222
4726
  setLocale(locale);
4727
+ resetEntityStore(newEntityStore);
4223
4728
  }
4224
4729
  // Below are mutually exclusive cases
4225
4730
  if (changedNode) {
@@ -4749,17 +5254,49 @@ const addStylesTag = (className, styleRules) => {
4749
5254
 
4750
5255
  const getUnboundValues = ({ key, fallback, unboundValues, }) => {
4751
5256
  const lodashPath = `${key}.value`;
4752
- return get$1(unboundValues, lodashPath, fallback);
5257
+ return get$2(unboundValues, lodashPath, fallback);
5258
+ };
5259
+
5260
+ /**
5261
+ * When node is a pattern block, we need to look up for default values of the pattern variables
5262
+ * and merge them with the updated node props.
5263
+ * While loop is making sure that we look up for the updated default values in the parent pattern
5264
+ * component settings as well.
5265
+ */
5266
+ const maybeMergePatternDefaultDesignValues = ({ variableName, variableMapping, node, findNodeById, }) => {
5267
+ if (node.type === ASSEMBLY_BLOCK_NODE_TYPE) {
5268
+ const patternId = node.data.pattern?.id;
5269
+ const exposedPropertyName = node['exposedPropertyNameToKeyMap'][variableName];
5270
+ if (!exposedPropertyName || !patternId) {
5271
+ return variableMapping.valuesByBreakpoint;
5272
+ }
5273
+ const exposedVariableDefinition = componentRegistry.get(patternId)?.definition.variables[exposedPropertyName];
5274
+ let exposedDefaultValue = exposedVariableDefinition?.defaultValue;
5275
+ let parentPatternNode = findNodeById(node.data.pattern?.nodeId);
5276
+ while (parentPatternNode) {
5277
+ const parentPatternId = parentPatternNode.data.pattern?.id;
5278
+ const nextKey = parentPatternNode['exposedPropertyNameToKeyMap'][exposedPropertyName];
5279
+ if (!parentPatternId || !nextKey) {
5280
+ break;
5281
+ }
5282
+ const parentPatternVariableDefinition = componentRegistry.get(parentPatternId)?.definition.variables[nextKey];
5283
+ exposedDefaultValue = mergeDesignValuesByBreakpoint(parentPatternVariableDefinition?.defaultValue, exposedDefaultValue);
5284
+ parentPatternNode = findNodeById(parentPatternNode.data.pattern?.nodeId);
5285
+ }
5286
+ const mergedDesignValue = mergeDesignValuesByBreakpoint(exposedDefaultValue, variableMapping);
5287
+ return mergedDesignValue?.valuesByBreakpoint;
5288
+ }
5289
+ return variableMapping.valuesByBreakpoint;
4753
5290
  };
4754
5291
 
4755
- const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
5292
+ const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
4756
5293
  const unboundValues = useEditorStore((state) => state.unboundValues);
4757
5294
  const hyperlinkPattern = useEditorStore((state) => state.hyperLinkPattern);
4758
5295
  const locale = useEditorStore((state) => state.locale);
4759
5296
  const dataSource = useEditorStore((state) => state.dataSource);
4760
- const entityStore = useEntityStore((state) => state.entityStore);
4761
5297
  const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
4762
5298
  const nodeRect = useDraggedItemStore((state) => state.domRect);
5299
+ const findNodeById = useTreeStore((state) => state.findNodeById);
4763
5300
  const isEmptyZone = !node.children.length;
4764
5301
  const props = useMemo(() => {
4765
5302
  const propsBase = {
@@ -4783,7 +5320,13 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
4783
5320
  };
4784
5321
  }
4785
5322
  if (variableMapping.type === 'DesignValue') {
4786
- const valuesByBreakpoint = resolveDesignValue(variableMapping.valuesByBreakpoint, variableName);
5323
+ const value = maybeMergePatternDefaultDesignValues({
5324
+ variableName,
5325
+ variableMapping,
5326
+ node,
5327
+ findNodeById,
5328
+ });
5329
+ const valuesByBreakpoint = resolveDesignValue(value, variableName);
4787
5330
  const designValue = variableName === 'cfHeight'
4788
5331
  ? calculateNodeDefaultHeight({
4789
5332
  blockId: node.data.blockId,
@@ -4874,6 +5417,7 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
4874
5417
  unboundValues,
4875
5418
  entityStore,
4876
5419
  renderDropzone,
5420
+ findNodeById,
4877
5421
  ]);
4878
5422
  const cfStyles = useMemo(() => ({
4879
5423
  ...buildCfStyles(props),
@@ -5046,7 +5590,6 @@ const MissingComponentPlaceholder = ({ blockId }) => {
5046
5590
  };
5047
5591
 
5048
5592
  const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...props }, ref) => {
5049
- const entityStore = useEntityStore((state) => state.entityStore);
5050
5593
  return (React.createElement("div", { ...props,
5051
5594
  // Pass through ref to avoid DND errors being logged
5052
5595
  ref: ref, "data-cf-node-error": "circular-pattern-dependency", style: {
@@ -5059,7 +5602,7 @@ const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...
5059
5602
  "Circular usage of patterns detected:",
5060
5603
  React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
5061
5604
  const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
5062
- const entry = entityStore.getEntityFromLink(entryLink);
5605
+ const entry = inMemoryEntities.maybeResolveLink(entryLink);
5063
5606
  const entryTitle = entry?.fields?.title;
5064
5607
  const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
5065
5608
  return React.createElement("li", { key: patternId }, text);
@@ -5067,8 +5610,7 @@ const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...
5067
5610
  });
5068
5611
  CircularDependencyErrorPlaceholder.displayName = 'CircularDependencyErrorPlaceholder';
5069
5612
 
5070
- const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
5071
- const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
5613
+ const useComponent = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
5072
5614
  const tree = useTreeStore((state) => state.tree);
5073
5615
  const componentRegistration = useMemo(() => {
5074
5616
  let registration = componentRegistry.get(node.data.blockId);
@@ -5094,6 +5636,7 @@ const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging
5094
5636
  const requiresDragWrapper = !isPatternNode && !isStructureComponent && !componentRegistration?.options?.wrapComponent;
5095
5637
  const { componentProps, wrapperStyles } = useComponentProps({
5096
5638
  node,
5639
+ entityStore,
5097
5640
  areEntitiesFetched,
5098
5641
  resolveDesignValue,
5099
5642
  renderDropzone,
@@ -5568,13 +6111,15 @@ function getStyle$1(style, snapshot) {
5568
6111
  transitionDuration: `0.001s`,
5569
6112
  };
5570
6113
  }
5571
- const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
6114
+ const EditorBlock = ({ node: rawNode, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
5572
6115
  const { slotId } = parseZoneId(zoneId);
5573
6116
  const ref = useRef(null);
5574
6117
  const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
5575
6118
  const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
5576
6119
  const { node, componentId, elementToRender, definition, isPatternNode, isPatternComponent, isNestedPattern, } = useComponent({
5577
6120
  node: rawNode,
6121
+ entityStore,
6122
+ areEntitiesFetched,
5578
6123
  resolveDesignValue,
5579
6124
  renderDropzone,
5580
6125
  userIsDragging,
@@ -5715,10 +6260,12 @@ function getStyle(style = {}, snapshot) {
5715
6260
  transitionDuration: `0.001s`,
5716
6261
  };
5717
6262
  }
5718
- const EditorBlockClone = ({ node: rawNode, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
6263
+ const EditorBlockClone = ({ node: rawNode, entityStore, areEntitiesFetched, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
5719
6264
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5720
6265
  const { node, elementToRender } = useComponent({
5721
6266
  node: rawNode,
6267
+ entityStore,
6268
+ areEntitiesFetched,
5722
6269
  resolveDesignValue,
5723
6270
  renderDropzone,
5724
6271
  userIsDragging,
@@ -5755,7 +6302,7 @@ const getHtmlComponentProps = (props) => {
5755
6302
  return {};
5756
6303
  };
5757
6304
 
5758
- function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, ...rest }) {
6305
+ function DropzoneClone({ node, entityStore, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, areEntitiesFetched, ...rest }) {
5759
6306
  const tree = useTreeStore((state) => state.tree);
5760
6307
  const content = node?.children || tree.root?.children || [];
5761
6308
  const { slotId } = parseZoneId(zoneId);
@@ -5776,11 +6323,11 @@ function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'd
5776
6323
  .filter((node) => node.data.slotId === slotId)
5777
6324
  .map((item) => {
5778
6325
  const componentId = item.data.id;
5779
- return (React.createElement(EditorBlockClone, { key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
6326
+ return (React.createElement(EditorBlockClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
5780
6327
  })));
5781
6328
  }
5782
6329
 
5783
- function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
6330
+ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, entityStore, areEntitiesFetched, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
5784
6331
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5785
6332
  const draggedItem = useDraggedItemStore((state) => state.draggedItem);
5786
6333
  const isDraggingNewComponent = useDraggedItemStore((state) => Boolean(state.componentId));
@@ -5815,11 +6362,11 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5815
6362
  }, [isRootAssembly, node, parentWrappingPatternIds, tree.root.data.blockId]);
5816
6363
  // To avoid a circular dependency, we create the recursive rendering function here and trickle it down
5817
6364
  const renderDropzone = useCallback((node, props) => {
5818
- return (React.createElement(Dropzone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, ...props }));
5819
- }, [wrappingPatternIds, resolveDesignValue]);
6365
+ return (React.createElement(Dropzone, { zoneId: node.data.id, entityStore: entityStore, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, areEntitiesFetched: areEntitiesFetched, ...props }));
6366
+ }, [wrappingPatternIds, resolveDesignValue, entityStore, areEntitiesFetched]);
5820
6367
  const renderClonedDropzone = useCallback((node, props) => {
5821
- return (React.createElement(DropzoneClone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
5822
- }, [resolveDesignValue, wrappingPatternIds]);
6368
+ return (React.createElement(DropzoneClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
6369
+ }, [resolveDesignValue, wrappingPatternIds, entityStore, areEntitiesFetched]);
5823
6370
  const isDropzoneEnabled = useMemo(() => {
5824
6371
  const isColumns = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
5825
6372
  const isDraggingSingleColumn = draggedNode?.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
@@ -5861,7 +6408,7 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5861
6408
  ? node.children.length === 1 &&
5862
6409
  resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
5863
6410
  : false;
5864
- 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) => {
6411
+ 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) => {
5865
6412
  return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
5866
6413
  if (dragProps?.innerRef) {
5867
6414
  dragProps.innerRef(refNode);
@@ -5879,7 +6426,7 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5879
6426
  }) },
5880
6427
  isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
5881
6428
  .filter((node) => node.data.slotId === slotId)
5882
- .map((item, i) => (React.createElement(EditorBlock, { placeholder: {
6429
+ .map((item, i) => (React.createElement(EditorBlock, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, placeholder: {
5883
6430
  isDraggingOver: snapshot?.isDraggingOver,
5884
6431
  totalIndexes: content.length,
5885
6432
  elementIndex: i,
@@ -5891,8 +6438,8 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5891
6438
  }));
5892
6439
  }
5893
6440
 
5894
- const RootRenderer = ({ onChange }) => {
5895
- useEditorSubscriber();
6441
+ const RootRenderer = ({ onChange, inMemoryEntitiesStore, }) => {
6442
+ useEditorSubscriber(inMemoryEntitiesStore);
5896
6443
  const dragItem = useDraggedItemStore((state) => state.componentId);
5897
6444
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5898
6445
  const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
@@ -5972,28 +6519,28 @@ const RootRenderer = ({ onChange }) => {
5972
6519
  handleResizeCanvas();
5973
6520
  // eslint-disable-next-line react-hooks/exhaustive-deps
5974
6521
  }, [containerRef.current]);
6522
+ const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
6523
+ const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
5975
6524
  return (React.createElement(DNDProvider, null,
5976
6525
  dragItem && React.createElement(DraggableContainer, { id: dragItem }),
5977
6526
  React.createElement("div", { "data-ctfl-root": true, className: styles$3.container, ref: containerRef, style: containerStyles },
5978
6527
  userIsDragging && React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitbox }),
5979
- React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue }),
6528
+ React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched }),
5980
6529
  userIsDragging && (React.createElement(React.Fragment, null,
5981
6530
  React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitboxLower }),
5982
6531
  React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.canvasBottomSpacer })))),
5983
6532
  React.createElement("div", { "data-ctfl-hitboxes": true })));
5984
6533
  };
5985
6534
 
5986
- const useInitializeEditor = () => {
6535
+ const useInitializeEditor = (inMemoryEntitiesStore) => {
5987
6536
  const initializeEditor = useEditorStore((state) => state.initializeEditor);
5988
6537
  const [initialized, setInitialized] = useState(false);
5989
- const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
6538
+ const resetEntityStore = useStore(inMemoryEntitiesStore, (state) => state.resetEntityStore);
5990
6539
  useEffect(() => {
5991
6540
  const onVisualEditorInitialize = (event) => {
5992
6541
  if (!event.detail)
5993
6542
  return;
5994
- const { componentRegistry, designTokens, locale: initialLocale,
5995
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5996
- entities, } = event.detail;
6543
+ const { componentRegistry, designTokens, locale: initialLocale, entities } = event.detail;
5997
6544
  initializeEditor({
5998
6545
  initialLocale,
5999
6546
  componentRegistry,
@@ -6001,7 +6548,7 @@ const useInitializeEditor = () => {
6001
6548
  });
6002
6549
  // if entities is set to [], then everything will still work as EntityStore will
6003
6550
  // request entities on demand via ▲REQUEST_ENTITY
6004
- resetEntityStore(initialLocale, entities);
6551
+ resetEntityStore(new EditorModeEntityStore({ locale: initialLocale, entities }));
6005
6552
  setInitialized(true);
6006
6553
  };
6007
6554
  // Listen for VisualEditorComponents internal event
@@ -6021,8 +6568,8 @@ const useInitializeEditor = () => {
6021
6568
  return initialized;
6022
6569
  };
6023
6570
 
6024
- const VisualEditorRoot = ({ experience }) => {
6025
- const initialized = useInitializeEditor();
6571
+ const VisualEditorRoot = ({ experience, inMemoryEntitiesStore: inMemoryEntitiesStore$1 = inMemoryEntitiesStore, }) => {
6572
+ const initialized = useInitializeEditor(inMemoryEntitiesStore$1);
6026
6573
  const setHyperLinkPattern = useEditorStore((state) => state.setHyperLinkPattern);
6027
6574
  const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
6028
6575
  const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
@@ -6058,7 +6605,7 @@ const VisualEditorRoot = ({ experience }) => {
6058
6605
  }, [setHoveringZone, setMousePosition]);
6059
6606
  if (!initialized)
6060
6607
  return null;
6061
- return React.createElement(RootRenderer, null);
6608
+ return React.createElement(RootRenderer, { inMemoryEntitiesStore: inMemoryEntitiesStore$1 });
6062
6609
  };
6063
6610
 
6064
6611
  export { VisualEditorRoot as default };