@contentful/experiences-visual-editor-react 1.42.4-prerelease-20250702T1207-37b3bfc.0 → 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';
@@ -836,7 +828,13 @@ const ParameterDefinitionSchema$1 = z.object({
836
828
  }),
837
829
  })
838
830
  .optional(),
839
- contentTypes: z.array(z.string()),
831
+ contentTypes: z.record(z.string(), z.object({
832
+ sys: z.object({
833
+ type: z.literal('Link'),
834
+ id: z.string(),
835
+ linkType: z.enum(['ContentType']),
836
+ }),
837
+ })),
840
838
  passToNodes: z.array(PassToNodeSchema$1).optional(),
841
839
  });
842
840
  const ParameterDefinitionsSchema$1 = z.record(propertyKeySchema$1, ParameterDefinitionSchema$1);
@@ -856,7 +854,7 @@ const ComponentSettingsSchema$1 = z
856
854
  variableDefinitions: ComponentVariablesSchema$1,
857
855
  thumbnailId: z.enum(THUMBNAIL_IDS$1).optional(),
858
856
  category: z.string().max(50, 'Category must contain at most 50 characters').optional(),
859
- prebindingDefinitions: z.array(PrebindingDefinitionSchema$1).length(1).optional(),
857
+ prebindingDefinitions: z.array(PrebindingDefinitionSchema$1).max(1).optional(),
860
858
  })
861
859
  .strict();
862
860
  z.object({
@@ -1099,6 +1097,24 @@ propertyName, resolveDesignTokens = true) => {
1099
1097
  return valuesByBreakpoint;
1100
1098
  }
1101
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
+ }
1102
1118
 
1103
1119
  const CF_DEBUG_KEY$1 = 'cf_debug';
1104
1120
  /**
@@ -1220,11 +1236,19 @@ const getElementCoordinates = (element) => {
1220
1236
  });
1221
1237
  };
1222
1238
 
1223
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1224
1239
  const isLinkToAsset = (variable) => {
1225
- 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))
1244
+ return false;
1245
+ if (variable.sys === null || typeof variable.sys !== 'object')
1246
+ return false;
1247
+ if (!('linkType' in variable.sys))
1226
1248
  return false;
1227
- if (typeof variable !== 'object')
1249
+ if (!('id' in variable.sys))
1250
+ return false;
1251
+ if (!('type' in variable.sys))
1228
1252
  return false;
1229
1253
  return (variable.sys?.linkType === 'Asset' &&
1230
1254
  typeof variable.sys?.id === 'string' &&
@@ -1232,20 +1256,20 @@ const isLinkToAsset = (variable) => {
1232
1256
  variable.sys?.type === 'Link');
1233
1257
  };
1234
1258
 
1235
- const isLink = (maybeLink) => {
1259
+ const isLink$1 = (maybeLink) => {
1236
1260
  if (maybeLink === null)
1237
1261
  return false;
1238
1262
  if (typeof maybeLink !== 'object')
1239
1263
  return false;
1240
1264
  const link = maybeLink;
1241
- return Boolean(link.sys?.id) && link.sys?.type === 'Link';
1265
+ return Boolean(link.sys?.id) && link.sys?.type === 'Link' && Boolean(link.sys?.linkType);
1242
1266
  };
1243
1267
 
1244
1268
  /**
1245
1269
  * This module encapsulates format of the path to a deep reference.
1246
1270
  */
1247
- const parseDataSourcePathIntoFieldset = (path) => {
1248
- const parsedPath = parseDeepPath(path);
1271
+ const parseDataSourcePathIntoFieldset$1 = (path) => {
1272
+ const parsedPath = parseDeepPath$1(path);
1249
1273
  if (null === parsedPath) {
1250
1274
  throw new Error(`Cannot parse path '${path}' as deep path`);
1251
1275
  }
@@ -1258,7 +1282,7 @@ const parseDataSourcePathIntoFieldset = (path) => {
1258
1282
  * @returns
1259
1283
  */
1260
1284
  const parseDataSourcePathWithL1DeepBindings = (path) => {
1261
- const parsedPath = parseDeepPath(path);
1285
+ const parsedPath = parseDeepPath$1(path);
1262
1286
  if (null === parsedPath) {
1263
1287
  throw new Error(`Cannot parse path '${path}' as deep path`);
1264
1288
  }
@@ -1275,14 +1299,14 @@ const parseDataSourcePathWithL1DeepBindings = (path) => {
1275
1299
  * - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
1276
1300
  * @returns
1277
1301
  */
1278
- const isDeepPath = (deepPathCandidate) => {
1279
- const deepPathParsed = parseDeepPath(deepPathCandidate);
1302
+ const isDeepPath$1 = (deepPathCandidate) => {
1303
+ const deepPathParsed = parseDeepPath$1(deepPathCandidate);
1280
1304
  if (!deepPathParsed) {
1281
1305
  return false;
1282
1306
  }
1283
1307
  return deepPathParsed.fields.length > 1;
1284
1308
  };
1285
- const parseDeepPath = (deepPathCandidate) => {
1309
+ const parseDeepPath$1 = (deepPathCandidate) => {
1286
1310
  // ALGORITHM:
1287
1311
  // We start with deep path in form:
1288
1312
  // /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
@@ -1308,7 +1332,7 @@ const parseDeepPath = (deepPathCandidate) => {
1308
1332
  return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
1309
1333
  };
1310
1334
  const deepPathSegments = deepPathCandidate.split('/');
1311
- const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
1335
+ const chunks = chunkSegments$1(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
1312
1336
  if (chunks.length <= 1) {
1313
1337
  return null; // malformed path, even regular paths have at least 2 chunks
1314
1338
  }
@@ -1328,7 +1352,7 @@ const parseDeepPath = (deepPathCandidate) => {
1328
1352
  fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
1329
1353
  };
1330
1354
  };
1331
- const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
1355
+ const chunkSegments$1 = (segments, { startNextChunkOnElementEqualTo }) => {
1332
1356
  const chunks = [];
1333
1357
  let currentChunk = [];
1334
1358
  const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
@@ -1429,11 +1453,6 @@ const transformVisibility = (value) => {
1429
1453
  // Don't explicitly set anything when visible to not overwrite values like `grid` or `flex`.
1430
1454
  return {};
1431
1455
  };
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
1456
  const transformGridColumn = (span) => {
1438
1457
  if (!span) {
1439
1458
  return {};
@@ -1478,34 +1497,6 @@ const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions
1478
1497
  return;
1479
1498
  }
1480
1499
  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
1500
  const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
1510
1501
  const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
1511
1502
  horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
@@ -1590,8 +1581,8 @@ const buildCfStyles = (values) => {
1590
1581
  margin: values.cfMargin,
1591
1582
  padding: values.cfPadding,
1592
1583
  backgroundColor: values.cfBackgroundColor,
1593
- width: transformFill(values.cfWidth || values.cfImageOptions?.width),
1594
- height: transformFill(values.cfHeight || values.cfImageOptions?.height),
1584
+ width: values.cfWidth || values.cfImageOptions?.width,
1585
+ height: values.cfHeight || values.cfImageOptions?.height,
1595
1586
  maxWidth: values.cfMaxWidth,
1596
1587
  ...transformGridColumn(values.cfColumnSpan),
1597
1588
  ...transformBorderStyle(values.cfBorder),
@@ -1697,13 +1688,13 @@ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', fo
1697
1688
  };
1698
1689
 
1699
1690
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1700
- function get(obj, path) {
1691
+ function get$1(obj, path) {
1701
1692
  if (!path.length) {
1702
1693
  return obj;
1703
1694
  }
1704
1695
  try {
1705
1696
  const [currentPath, ...nextPath] = path;
1706
- return get(obj[currentPath], nextPath);
1697
+ return get$1(obj[currentPath], nextPath);
1707
1698
  }
1708
1699
  catch (err) {
1709
1700
  return undefined;
@@ -1711,7 +1702,7 @@ function get(obj, path) {
1711
1702
  }
1712
1703
 
1713
1704
  const getBoundValue = (entryOrAsset, path) => {
1714
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1705
+ const value = get$1(entryOrAsset, path.split('/').slice(2, -1));
1715
1706
  return value && typeof value == 'object' && value.url
1716
1707
  ? value.url
1717
1708
  : value;
@@ -1741,7 +1732,9 @@ const transformRichText = (entryOrAsset, entityStore, path) => {
1741
1732
  }
1742
1733
  if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
1743
1734
  // resolve any links to assets/entries/hyperlinks
1744
- 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);
1745
1738
  resolveLinks(richTextDocument, entityStore);
1746
1739
  return richTextDocument;
1747
1740
  }
@@ -1796,7 +1789,7 @@ const transformMedia = (asset, variables, resolveDesignValue, variableName, path
1796
1789
  let value;
1797
1790
  // If it is not a deep path and not pointing to the file of the asset,
1798
1791
  // 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')) {
1792
+ if (!isDeepPath$1(path) && !lastPathNamedSegmentEq(path, 'file')) {
1800
1793
  return getBoundValue(asset, path);
1801
1794
  }
1802
1795
  //TODO: this will be better served by injectable type transformers instead of if statement
@@ -1854,104 +1847,97 @@ const transformMedia = (asset, variables, resolveDesignValue, variableName, path
1854
1847
  }
1855
1848
  return asset.fields.file?.url;
1856
1849
  };
1857
-
1858
- const isAsset = (value) => {
1850
+ const isEntry$1 = (value) => {
1859
1851
  return (null !== value &&
1860
1852
  typeof value === 'object' &&
1861
1853
  'sys' in value &&
1862
- value.sys?.type === 'Asset');
1854
+ value.sys?.type === 'Entry');
1863
1855
  };
1864
- const isEntry = (value) => {
1856
+ const isAsset$1 = (value) => {
1865
1857
  return (null !== value &&
1866
1858
  typeof value === 'object' &&
1867
1859
  'sys' in value &&
1868
- value.sys?.type === 'Entry');
1860
+ value.sys?.type === 'Asset');
1869
1861
  };
1870
1862
 
1871
1863
  function getResolvedEntryFromLink(entryOrAsset, path, entityStore) {
1872
- if (isAsset(entryOrAsset)) {
1864
+ if (isAsset$1(entryOrAsset)) {
1873
1865
  return entryOrAsset;
1874
1866
  }
1875
- else if (!isEntry(entryOrAsset)) {
1867
+ else if (!isEntry$1(entryOrAsset)) {
1876
1868
  throw new Error(`Expected an Entry or Asset, but got: ${JSON.stringify(entryOrAsset)}`);
1877
1869
  }
1878
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1870
+ const fieldName = path.split('/').slice(2, -1);
1871
+ const value = get$1(entryOrAsset, fieldName);
1879
1872
  let resolvedEntity;
1880
- if (isAsset(value) || isEntry(value)) {
1873
+ if (isAsset$1(value) || isEntry$1(value)) {
1881
1874
  // In some cases, reference fields are already resolved
1882
1875
  resolvedEntity = value;
1883
1876
  }
1884
1877
  else if (value?.sys.type === 'Link') {
1885
1878
  // Look up the reference in the entity store
1886
1879
  resolvedEntity = entityStore.getEntityFromLink(value);
1887
- if (!resolvedEntity) {
1888
- return;
1889
- }
1890
1880
  }
1891
1881
  else {
1892
- 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) {
1893
1891
  return;
1894
1892
  }
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
1893
  return resolvedEntity;
1917
1894
  }
1918
1895
 
1896
+ const excludeUndefined = (value) => {
1897
+ return value !== undefined;
1898
+ };
1919
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.
1920
1903
  if (entryOrAsset.sys.type === 'Asset') {
1921
1904
  return entryOrAsset;
1922
1905
  }
1923
- const arrayValue = get(entryOrAsset, path.split('/').slice(2, -1));
1906
+ const fieldName = path.split('/').slice(2, -1);
1907
+ const arrayValue = get$1(entryOrAsset, fieldName);
1924
1908
  if (!isArray(arrayValue)) {
1925
- 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 });
1926
1910
  return;
1927
1911
  }
1928
- const result = arrayValue.map((value) => {
1912
+ const result = arrayValue
1913
+ .map((value) => {
1929
1914
  if (typeof value === 'string') {
1930
- 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)
1931
1916
  }
1932
1917
  else if (value?.sys?.type === 'Link') {
1933
1918
  const resolvedEntity = entityStore.getEntityFromLink(value);
1934
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)
1935
1924
  return;
1936
1925
  }
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
1926
  return resolvedEntity;
1948
1927
  }
1949
1928
  else {
1950
1929
  console.warn(`Expected value to be a string or Link, but got: ${JSON.stringify(value)}`);
1951
1930
  return undefined;
1952
1931
  }
1953
- });
1954
- 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;
1955
1941
  }
1956
1942
 
1957
1943
  const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableType, path) => {
@@ -2110,11 +2096,22 @@ const sendMessage = (eventType, data) => {
2110
2096
  }, '*');
2111
2097
  };
2112
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
+
2113
2110
  /**
2114
2111
  * Base Store for entities
2115
2112
  * Can be extended for the different loading behaviours (editor, production, ..)
2116
2113
  */
2117
- class EntityStoreBase {
2114
+ let EntityStoreBase$1 = class EntityStoreBase {
2118
2115
  constructor({ entities, locale }) {
2119
2116
  /* serialized */ this.entryMap = new Map();
2120
2117
  /* serialized */ this.assetMap = new Map();
@@ -2130,11 +2127,11 @@ class EntityStoreBase {
2130
2127
  this.addEntity(entity);
2131
2128
  }
2132
2129
  getEntryOrAsset(linkOrEntryOrAsset, path) {
2133
- if (isDeepPath(path)) {
2130
+ if (isDeepPath$1(path)) {
2134
2131
  return this.getDeepEntry(linkOrEntryOrAsset, path);
2135
2132
  }
2136
2133
  let entity;
2137
- if (isLink(linkOrEntryOrAsset)) {
2134
+ if (isLink$1(linkOrEntryOrAsset)) {
2138
2135
  const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
2139
2136
  ? this.entryMap.get(linkOrEntryOrAsset.sys.id)
2140
2137
  : this.assetMap.get(linkOrEntryOrAsset.sys.id);
@@ -2144,7 +2141,7 @@ class EntityStoreBase {
2144
2141
  }
2145
2142
  entity = resolvedEntity;
2146
2143
  }
2147
- else if (isAsset(linkOrEntryOrAsset) || isEntry(linkOrEntryOrAsset)) {
2144
+ else if (isAsset$1(linkOrEntryOrAsset) || isEntry$1(linkOrEntryOrAsset)) {
2148
2145
  // We already have the complete entity in preview & delivery (resolved by the CMA client)
2149
2146
  entity = linkOrEntryOrAsset;
2150
2147
  }
@@ -2166,7 +2163,7 @@ class EntityStoreBase {
2166
2163
  console.warn(`Unresolved entity reference: ${entityLink.sys.linkType} with ID ${entityLink.sys.id}`);
2167
2164
  return;
2168
2165
  }
2169
- return get(entity, path);
2166
+ return get$1(entity, path);
2170
2167
  }
2171
2168
  getEntityFromLink(link) {
2172
2169
  const resolvedEntity = link.sys.linkType === 'Entry'
@@ -2178,6 +2175,22 @@ class EntityStoreBase {
2178
2175
  }
2179
2176
  return resolvedEntity;
2180
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
+ }
2181
2194
  getEntitiesFromMap(type, ids) {
2182
2195
  const resolved = [];
2183
2196
  const missing = [];
@@ -2196,11 +2209,13 @@ class EntityStoreBase {
2196
2209
  };
2197
2210
  }
2198
2211
  addEntity(entity) {
2199
- if (isAsset(entity)) {
2200
- 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)));
2201
2215
  }
2202
- else if (isEntry(entity)) {
2203
- 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)));
2204
2219
  }
2205
2220
  else {
2206
2221
  throw new Error(`Attempted to add an entity to the store that is neither Asset nor Entry: '${JSON.stringify(entity)}'`);
@@ -2253,7 +2268,7 @@ class EntityStoreBase {
2253
2268
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2254
2269
  break;
2255
2270
  }
2256
- const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
2271
+ const fieldValue = get$1(entityToResolveFieldsFrom, ['fields', field]);
2257
2272
  if (undefined === fieldValue) {
2258
2273
  return {
2259
2274
  resolvedFieldset,
@@ -2261,7 +2276,7 @@ class EntityStoreBase {
2261
2276
  reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
2262
2277
  };
2263
2278
  }
2264
- else if (isLink(fieldValue)) {
2279
+ else if (isLink$1(fieldValue)) {
2265
2280
  const entity = this.getEntityFromLink(fieldValue);
2266
2281
  if (entity === undefined) {
2267
2282
  return {
@@ -2273,7 +2288,7 @@ class EntityStoreBase {
2273
2288
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2274
2289
  entityToResolveFieldsFrom = entity; // we move up
2275
2290
  }
2276
- else if (isAsset(fieldValue) || isEntry(fieldValue)) {
2291
+ else if (isAsset$1(fieldValue) || isEntry$1(fieldValue)) {
2277
2292
  resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
2278
2293
  entityToResolveFieldsFrom = fieldValue; // we move up
2279
2294
  }
@@ -2290,13 +2305,13 @@ class EntityStoreBase {
2290
2305
  isFullyResolved: true,
2291
2306
  };
2292
2307
  };
2293
- const headEntity = isLink(linkOrEntryOrAsset)
2308
+ const headEntity = isLink$1(linkOrEntryOrAsset)
2294
2309
  ? this.getEntityFromLink(linkOrEntryOrAsset)
2295
2310
  : linkOrEntryOrAsset;
2296
2311
  if (undefined === headEntity) {
2297
2312
  return;
2298
2313
  }
2299
- const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
2314
+ const unresolvedFieldset = parseDataSourcePathIntoFieldset$1(path);
2300
2315
  // The purpose here is to take this intermediate representation of the deep-path
2301
2316
  // and to follow the links to the leaf-entity and field
2302
2317
  // in case we can't follow till the end, we should signal that there was null-reference in the path
@@ -2322,13 +2337,13 @@ class EntityStoreBase {
2322
2337
  locale: this.locale,
2323
2338
  };
2324
2339
  }
2325
- }
2340
+ };
2326
2341
 
2327
2342
  /**
2328
2343
  * EntityStore which resolves entries and assets from the editor
2329
2344
  * over the sendMessage and subscribe functions.
2330
2345
  */
2331
- class EditorEntityStore extends EntityStoreBase {
2346
+ class EditorEntityStore extends EntityStoreBase$1 {
2332
2347
  constructor({ entities, locale, sendMessage, subscribe, timeoutDuration = 3000, }) {
2333
2348
  super({ entities, locale });
2334
2349
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2465,7 +2480,6 @@ class EditorModeEntityStore extends EditorEntityStore {
2465
2480
  };
2466
2481
  };
2467
2482
  super({ entities, sendMessage, subscribe, locale, timeoutDuration: REQUEST_TIMEOUT });
2468
- this.locale = locale;
2469
2483
  }
2470
2484
  /**
2471
2485
  * This function collects and returns the list of requested entries and assets. Additionally, it checks
@@ -2495,11 +2509,91 @@ class EditorModeEntityStore extends EditorEntityStore {
2495
2509
  if (!entity) {
2496
2510
  return;
2497
2511
  }
2498
- const fieldValue = get(entity, path);
2512
+ const fieldValue = get$1(entity, path);
2499
2513
  return transformAssetFileToUrl(fieldValue);
2500
2514
  }
2501
2515
  }
2502
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
+
2503
2597
  var VisualEditorMode$1;
2504
2598
  (function (VisualEditorMode) {
2505
2599
  VisualEditorMode["LazyLoad"] = "lazyLoad";
@@ -2529,7 +2623,7 @@ class DeepReference {
2529
2623
  // field references nothing (or even field doesn't exist)
2530
2624
  return undefined;
2531
2625
  }
2532
- if (!isLink(maybeReferentLink)) {
2626
+ if (!isLink$1(maybeReferentLink)) {
2533
2627
  // Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
2534
2628
  // Under normal circumstance we expect field to be a Link, but it could be an "impostor"
2535
2629
  // eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
@@ -2549,7 +2643,7 @@ function gatherDeepReferencesFromTree(startingNode, dataSource) {
2549
2643
  for (const [, variableMapping] of Object.entries(node.data.props)) {
2550
2644
  if (variableMapping.type !== 'BoundValue')
2551
2645
  continue;
2552
- if (!isDeepPath(variableMapping.path))
2646
+ if (!isDeepPath$1(variableMapping.path))
2553
2647
  continue;
2554
2648
  deepReferences.push(DeepReference.from({
2555
2649
  path: variableMapping.path,
@@ -3158,6 +3252,26 @@ const useTreeStore = create((set, get) => ({
3158
3252
  reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
3159
3253
  }));
3160
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
+ },
3161
3275
  }));
3162
3276
  const hasBreakpointDiffs = (currentTree, newTree) => {
3163
3277
  const currentBreakpoints = currentTree?.root?.data?.breakpoints ?? [];
@@ -3706,7 +3820,13 @@ const ParameterDefinitionSchema = z.object({
3706
3820
  }),
3707
3821
  })
3708
3822
  .optional(),
3709
- contentTypes: z.array(z.string()),
3823
+ contentTypes: z.record(z.string(), z.object({
3824
+ sys: z.object({
3825
+ type: z.literal('Link'),
3826
+ id: z.string(),
3827
+ linkType: z.enum(['ContentType']),
3828
+ }),
3829
+ })),
3710
3830
  passToNodes: z.array(PassToNodeSchema).optional(),
3711
3831
  });
3712
3832
  const ParameterDefinitionsSchema = z.record(propertyKeySchema, ParameterDefinitionSchema);
@@ -3726,7 +3846,7 @@ const ComponentSettingsSchema = z
3726
3846
  variableDefinitions: ComponentVariablesSchema,
3727
3847
  thumbnailId: z.enum(THUMBNAIL_IDS).optional(),
3728
3848
  category: z.string().max(50, 'Category must contain at most 50 characters').optional(),
3729
- prebindingDefinitions: z.array(PrebindingDefinitionSchema).length(1).optional(),
3849
+ prebindingDefinitions: z.array(PrebindingDefinitionSchema).max(1).optional(),
3730
3850
  })
3731
3851
  .strict();
3732
3852
  z.object({
@@ -3932,124 +4052,520 @@ class DebugLogger {
3932
4052
  DebugLogger.instance = null;
3933
4053
  DebugLogger.getInstance();
3934
4054
 
3935
- var VisualEditorMode;
3936
- (function (VisualEditorMode) {
3937
- VisualEditorMode["LazyLoad"] = "lazyLoad";
3938
- VisualEditorMode["InjectScript"] = "injectScript";
3939
- })(VisualEditorMode || (VisualEditorMode = {}));
3940
-
3941
- var css_248z$2$1 = ".contentful-container{display:flex;pointer-events:all;position:relative}.contentful-container::-webkit-scrollbar{display:none}.cf-single-column-wrapper{position:relative}.cf-container-wrapper{position:relative;width:100%}.contentful-container:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.contentful-section-label:after{content:\"Section\"}.contentful-container-label:after{content:\"Container\"}.contentful-container-link,.contentful-container-link:active,.contentful-container-link:focus-visible,.contentful-container-link:hover,.contentful-container-link:read-write,.contentful-container-link:visited{color:inherit;outline:unset;text-decoration:unset}";
3942
- styleInject(css_248z$2$1);
3943
-
3944
- const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave, onMouseDown, onClick, flex, flexBasis, flexShrink, flexDirection, gap, justifyContent, justifyItems, justifySelf, alignItems, alignSelf, alignContent, order, flexWrap, flexGrow, className, cssStyles, ...props }, ref) => {
3945
- return (React.createElement("div", { id: id, ref: ref, style: {
3946
- display: 'flex',
3947
- flex,
3948
- flexBasis,
3949
- flexShrink,
3950
- flexDirection,
3951
- gap,
3952
- justifyContent,
3953
- justifyItems,
3954
- justifySelf,
3955
- alignItems,
3956
- alignSelf,
3957
- alignContent,
3958
- order,
3959
- flexWrap,
3960
- flexGrow,
3961
- ...cssStyles,
3962
- }, className: className, onMouseEnter: onMouseEnter, onMouseUp: onMouseUp, onMouseDown: onMouseDown, onMouseLeave: onMouseLeave, onClick: onClick, ...props }, children));
3963
- });
3964
- Flex.displayName = 'Flex';
3965
-
3966
- var css_248z$1$1 = ".cf-divider{display:contents;height:100%;position:relative;width:100%}.cf-divider hr{border:none}[data-ctfl-zone-id=root] .cf-divider:before{bottom:-5px;content:\"\";left:-5px;pointer-events:all;position:absolute;right:-5px;top:-5px}";
3967
- styleInject(css_248z$1$1);
3968
-
3969
- var css_248z$9 = ".cf-columns{display:flex;flex-direction:column;gap:24px;grid-template-columns:repeat(12,1fr);min-height:0;min-width:0}@media (min-width:768px){.cf-columns{display:grid}}.cf-single-column-wrapper{display:flex;position:relative}.cf-single-column{pointer-events:all}.cf-single-column-wrapper:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.cf-single-column-label:after{content:\"Column\"}";
3970
- styleInject(css_248z$9);
3971
-
3972
- const ColumnWrapper = forwardRef((props, ref) => {
3973
- return (React.createElement("div", { ref: ref, ...props, style: {
3974
- ...(props.style || {}),
3975
- display: 'grid',
3976
- gridTemplateColumns: 'repeat(12, [col-start] 1fr)',
3977
- } }, props.children));
3978
- });
3979
- ColumnWrapper.displayName = 'ColumnWrapper';
3980
-
3981
- const assemblyStyle = { display: 'contents' };
3982
- // Feel free to do any magic as regards variable definitions for assemblies
3983
- // Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
3984
- const Assembly = (props) => {
3985
- if (props.editorMode) {
3986
- const { node, dragProps, ...editorModeProps } = props;
3987
- return props.renderDropzone(node, {
3988
- ...editorModeProps,
3989
- ['data-test-id']: 'contentful-assembly',
3990
- className: props.className,
3991
- dragProps,
3992
- });
3993
- }
3994
- // Using a display contents so assembly content/children
3995
- // can appear as if they are direct children of the div wrapper's parent
3996
- return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
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);
3997
4062
  };
3998
4063
 
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
- class DragState {
4017
- constructor() {
4018
- this.isDragStartedOnParent = false;
4019
- this.isDraggingItem = false;
4020
- }
4021
- get isDragging() {
4022
- return this.isDraggingItem;
4023
- }
4024
- get isDraggingOnParent() {
4025
- return this.isDragStartedOnParent;
4026
- }
4027
- updateIsDragging(isDraggingItem) {
4028
- this.isDraggingItem = isDraggingItem;
4029
- }
4030
- updateIsDragStartedOnParent(isDragStartedOnParent) {
4031
- this.isDragStartedOnParent = isDragStartedOnParent;
4032
- }
4033
- resetState() {
4034
- this.isDraggingItem = false;
4035
- this.isDragStartedOnParent = false;
4036
- }
4037
- }
4038
-
4039
- class SimulateDnD extends DragState {
4040
- constructor() {
4041
- super();
4042
- this.draggingElement = null;
4043
- }
4044
- setupDrag() {
4045
- this.updateIsDragStartedOnParent(true);
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`);
4046
4071
  }
4047
- startDrag(coordX, coordY) {
4048
- this.draggingElement = document.getElementById(NEW_COMPONENT_ID);
4049
- this.updateIsDragging(true);
4050
- this.simulateMouseEvent(coordX, coordY, 'mousedown');
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;
4051
4085
  }
4052
- updateDrag(coordX, coordY) {
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
+
4468
+ var VisualEditorMode;
4469
+ (function (VisualEditorMode) {
4470
+ VisualEditorMode["LazyLoad"] = "lazyLoad";
4471
+ VisualEditorMode["InjectScript"] = "injectScript";
4472
+ })(VisualEditorMode || (VisualEditorMode = {}));
4473
+
4474
+ var css_248z$2$1 = ".contentful-container{display:flex;pointer-events:all;position:relative}.contentful-container::-webkit-scrollbar{display:none}.cf-single-column-wrapper{position:relative}.cf-container-wrapper{position:relative;width:100%}.contentful-container:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.contentful-section-label:after{content:\"Section\"}.contentful-container-label:after{content:\"Container\"}.contentful-container-link,.contentful-container-link:active,.contentful-container-link:focus-visible,.contentful-container-link:hover,.contentful-container-link:read-write,.contentful-container-link:visited{color:inherit;outline:unset;text-decoration:unset}";
4475
+ styleInject(css_248z$2$1);
4476
+
4477
+ const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave, onMouseDown, onClick, flex, flexBasis, flexShrink, flexDirection, gap, justifyContent, justifyItems, justifySelf, alignItems, alignSelf, alignContent, order, flexWrap, flexGrow, className, cssStyles, ...props }, ref) => {
4478
+ return (React.createElement("div", { id: id, ref: ref, style: {
4479
+ display: 'flex',
4480
+ flex,
4481
+ flexBasis,
4482
+ flexShrink,
4483
+ flexDirection,
4484
+ gap,
4485
+ justifyContent,
4486
+ justifyItems,
4487
+ justifySelf,
4488
+ alignItems,
4489
+ alignSelf,
4490
+ alignContent,
4491
+ order,
4492
+ flexWrap,
4493
+ flexGrow,
4494
+ ...cssStyles,
4495
+ }, className: className, onMouseEnter: onMouseEnter, onMouseUp: onMouseUp, onMouseDown: onMouseDown, onMouseLeave: onMouseLeave, onClick: onClick, ...props }, children));
4496
+ });
4497
+ Flex.displayName = 'Flex';
4498
+
4499
+ var css_248z$1$1 = ".cf-divider{display:contents;height:100%;position:relative;width:100%}.cf-divider hr{border:none}[data-ctfl-zone-id=root] .cf-divider:before{bottom:-5px;content:\"\";left:-5px;pointer-events:all;position:absolute;right:-5px;top:-5px}";
4500
+ styleInject(css_248z$1$1);
4501
+
4502
+ var css_248z$9 = ".cf-columns{display:flex;flex-direction:column;gap:24px;grid-template-columns:repeat(12,1fr);min-height:0;min-width:0}@media (min-width:768px){.cf-columns{display:grid}}.cf-single-column-wrapper{display:flex;position:relative}.cf-single-column{pointer-events:all}.cf-single-column-wrapper:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.cf-single-column-label:after{content:\"Column\"}";
4503
+ styleInject(css_248z$9);
4504
+
4505
+ const ColumnWrapper = forwardRef((props, ref) => {
4506
+ return (React.createElement("div", { ref: ref, ...props, style: {
4507
+ ...(props.style || {}),
4508
+ display: 'grid',
4509
+ gridTemplateColumns: 'repeat(12, [col-start] 1fr)',
4510
+ } }, props.children));
4511
+ });
4512
+ ColumnWrapper.displayName = 'ColumnWrapper';
4513
+
4514
+ const assemblyStyle = { display: 'contents' };
4515
+ // Feel free to do any magic as regards variable definitions for assemblies
4516
+ // Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
4517
+ const Assembly = (props) => {
4518
+ if (props.editorMode) {
4519
+ const { node, dragProps, ...editorModeProps } = props;
4520
+ return props.renderDropzone(node, {
4521
+ ...editorModeProps,
4522
+ ['data-test-id']: 'contentful-assembly',
4523
+ className: props.className,
4524
+ dragProps,
4525
+ });
4526
+ }
4527
+ // Using a display contents so assembly content/children
4528
+ // can appear as if they are direct children of the div wrapper's parent
4529
+ return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
4530
+ };
4531
+
4532
+ class DragState {
4533
+ constructor() {
4534
+ this.isDragStartedOnParent = false;
4535
+ this.isDraggingItem = false;
4536
+ }
4537
+ get isDragging() {
4538
+ return this.isDraggingItem;
4539
+ }
4540
+ get isDraggingOnParent() {
4541
+ return this.isDragStartedOnParent;
4542
+ }
4543
+ updateIsDragging(isDraggingItem) {
4544
+ this.isDraggingItem = isDraggingItem;
4545
+ }
4546
+ updateIsDragStartedOnParent(isDragStartedOnParent) {
4547
+ this.isDragStartedOnParent = isDragStartedOnParent;
4548
+ }
4549
+ resetState() {
4550
+ this.isDraggingItem = false;
4551
+ this.isDragStartedOnParent = false;
4552
+ }
4553
+ }
4554
+
4555
+ class SimulateDnD extends DragState {
4556
+ constructor() {
4557
+ super();
4558
+ this.draggingElement = null;
4559
+ }
4560
+ setupDrag() {
4561
+ this.updateIsDragStartedOnParent(true);
4562
+ }
4563
+ startDrag(coordX, coordY) {
4564
+ this.draggingElement = document.getElementById(NEW_COMPONENT_ID);
4565
+ this.updateIsDragging(true);
4566
+ this.simulateMouseEvent(coordX, coordY, 'mousedown');
4567
+ }
4568
+ updateDrag(coordX, coordY) {
4053
4569
  if (!this.draggingElement) {
4054
4570
  this.draggingElement = document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`);
4055
4571
  }
@@ -4082,10 +4598,11 @@ class SimulateDnD extends DragState {
4082
4598
  }
4083
4599
  var SimulateDnD$1 = new SimulateDnD();
4084
4600
 
4085
- function useEditorSubscriber() {
4086
- const entityStore = useEntityStore((state) => state.entityStore);
4087
- const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
4088
- 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);
4089
4606
  const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
4090
4607
  updateTree: state.updateTree,
4091
4608
  updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
@@ -4097,7 +4614,6 @@ function useEditorSubscriber() {
4097
4614
  const setDataSource = useEditorStore((state) => state.setDataSource);
4098
4615
  const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
4099
4616
  const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
4100
- const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
4101
4617
  const setComponentId = useDraggedItemStore((state) => state.setComponentId);
4102
4618
  const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
4103
4619
  const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
@@ -4148,7 +4664,7 @@ function useEditorSubscriber() {
4148
4664
  const isMissingL2Entities = (deepReferences) => {
4149
4665
  const referentLinks = deepReferences
4150
4666
  .map((deepReference) => deepReference.extractReferent(entityStore))
4151
- .filter(isLink);
4667
+ .filter(isLink$1);
4152
4668
  const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4153
4669
  return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
4154
4670
  };
@@ -4165,7 +4681,7 @@ function useEditorSubscriber() {
4165
4681
  const fillupL2 = async ({ deepReferences }) => {
4166
4682
  const referentLinks = deepReferences
4167
4683
  .map((deepReference) => deepReference.extractReferent(entityStore))
4168
- .filter(isLink);
4684
+ .filter(isLink$1);
4169
4685
  const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
4170
4686
  await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
4171
4687
  };
@@ -4218,8 +4734,9 @@ function useEditorSubscriber() {
4218
4734
  }
4219
4735
  let newEntityStore = entityStore;
4220
4736
  if (entityStore.locale !== locale) {
4221
- newEntityStore = resetEntityStore(locale);
4737
+ newEntityStore = new EditorModeEntityStore({ locale, entities: [] });
4222
4738
  setLocale(locale);
4739
+ resetEntityStore(newEntityStore);
4223
4740
  }
4224
4741
  // Below are mutually exclusive cases
4225
4742
  if (changedNode) {
@@ -4749,17 +5266,49 @@ const addStylesTag = (className, styleRules) => {
4749
5266
 
4750
5267
  const getUnboundValues = ({ key, fallback, unboundValues, }) => {
4751
5268
  const lodashPath = `${key}.value`;
4752
- return get$1(unboundValues, lodashPath, fallback);
5269
+ return get$2(unboundValues, lodashPath, fallback);
4753
5270
  };
4754
5271
 
4755
- const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
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;
5302
+ };
5303
+
5304
+ const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
4756
5305
  const unboundValues = useEditorStore((state) => state.unboundValues);
4757
5306
  const hyperlinkPattern = useEditorStore((state) => state.hyperLinkPattern);
4758
5307
  const locale = useEditorStore((state) => state.locale);
4759
5308
  const dataSource = useEditorStore((state) => state.dataSource);
4760
- const entityStore = useEntityStore((state) => state.entityStore);
4761
5309
  const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
4762
5310
  const nodeRect = useDraggedItemStore((state) => state.domRect);
5311
+ const findNodeById = useTreeStore((state) => state.findNodeById);
4763
5312
  const isEmptyZone = !node.children.length;
4764
5313
  const props = useMemo(() => {
4765
5314
  const propsBase = {
@@ -4783,7 +5332,13 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
4783
5332
  };
4784
5333
  }
4785
5334
  if (variableMapping.type === 'DesignValue') {
4786
- 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);
4787
5342
  const designValue = variableName === 'cfHeight'
4788
5343
  ? calculateNodeDefaultHeight({
4789
5344
  blockId: node.data.blockId,
@@ -4874,6 +5429,7 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
4874
5429
  unboundValues,
4875
5430
  entityStore,
4876
5431
  renderDropzone,
5432
+ findNodeById,
4877
5433
  ]);
4878
5434
  const cfStyles = useMemo(() => ({
4879
5435
  ...buildCfStyles(props),
@@ -5046,7 +5602,6 @@ const MissingComponentPlaceholder = ({ blockId }) => {
5046
5602
  };
5047
5603
 
5048
5604
  const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...props }, ref) => {
5049
- const entityStore = useEntityStore((state) => state.entityStore);
5050
5605
  return (React.createElement("div", { ...props,
5051
5606
  // Pass through ref to avoid DND errors being logged
5052
5607
  ref: ref, "data-cf-node-error": "circular-pattern-dependency", style: {
@@ -5059,7 +5614,7 @@ const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...
5059
5614
  "Circular usage of patterns detected:",
5060
5615
  React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
5061
5616
  const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
5062
- const entry = entityStore.getEntityFromLink(entryLink);
5617
+ const entry = inMemoryEntities.maybeResolveLink(entryLink);
5063
5618
  const entryTitle = entry?.fields?.title;
5064
5619
  const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
5065
5620
  return React.createElement("li", { key: patternId }, text);
@@ -5067,8 +5622,7 @@ const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...
5067
5622
  });
5068
5623
  CircularDependencyErrorPlaceholder.displayName = 'CircularDependencyErrorPlaceholder';
5069
5624
 
5070
- const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
5071
- const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
5625
+ const useComponent = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
5072
5626
  const tree = useTreeStore((state) => state.tree);
5073
5627
  const componentRegistration = useMemo(() => {
5074
5628
  let registration = componentRegistry.get(node.data.blockId);
@@ -5094,6 +5648,7 @@ const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging
5094
5648
  const requiresDragWrapper = !isPatternNode && !isStructureComponent && !componentRegistration?.options?.wrapComponent;
5095
5649
  const { componentProps, wrapperStyles } = useComponentProps({
5096
5650
  node,
5651
+ entityStore,
5097
5652
  areEntitiesFetched,
5098
5653
  resolveDesignValue,
5099
5654
  renderDropzone,
@@ -5568,13 +6123,15 @@ function getStyle$1(style, snapshot) {
5568
6123
  transitionDuration: `0.001s`,
5569
6124
  };
5570
6125
  }
5571
- 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, }) => {
5572
6127
  const { slotId } = parseZoneId(zoneId);
5573
6128
  const ref = useRef(null);
5574
6129
  const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
5575
6130
  const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
5576
6131
  const { node, componentId, elementToRender, definition, isPatternNode, isPatternComponent, isNestedPattern, } = useComponent({
5577
6132
  node: rawNode,
6133
+ entityStore,
6134
+ areEntitiesFetched,
5578
6135
  resolveDesignValue,
5579
6136
  renderDropzone,
5580
6137
  userIsDragging,
@@ -5715,10 +6272,12 @@ function getStyle(style = {}, snapshot) {
5715
6272
  transitionDuration: `0.001s`,
5716
6273
  };
5717
6274
  }
5718
- const EditorBlockClone = ({ node: rawNode, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
6275
+ const EditorBlockClone = ({ node: rawNode, entityStore, areEntitiesFetched, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
5719
6276
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5720
6277
  const { node, elementToRender } = useComponent({
5721
6278
  node: rawNode,
6279
+ entityStore,
6280
+ areEntitiesFetched,
5722
6281
  resolveDesignValue,
5723
6282
  renderDropzone,
5724
6283
  userIsDragging,
@@ -5755,7 +6314,7 @@ const getHtmlComponentProps = (props) => {
5755
6314
  return {};
5756
6315
  };
5757
6316
 
5758
- 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 }) {
5759
6318
  const tree = useTreeStore((state) => state.tree);
5760
6319
  const content = node?.children || tree.root?.children || [];
5761
6320
  const { slotId } = parseZoneId(zoneId);
@@ -5776,11 +6335,11 @@ function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'd
5776
6335
  .filter((node) => node.data.slotId === slotId)
5777
6336
  .map((item) => {
5778
6337
  const componentId = item.data.id;
5779
- 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 }));
5780
6339
  })));
5781
6340
  }
5782
6341
 
5783
- 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 }) {
5784
6343
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5785
6344
  const draggedItem = useDraggedItemStore((state) => state.draggedItem);
5786
6345
  const isDraggingNewComponent = useDraggedItemStore((state) => Boolean(state.componentId));
@@ -5815,11 +6374,11 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5815
6374
  }, [isRootAssembly, node, parentWrappingPatternIds, tree.root.data.blockId]);
5816
6375
  // To avoid a circular dependency, we create the recursive rendering function here and trickle it down
5817
6376
  const renderDropzone = useCallback((node, props) => {
5818
- return (React.createElement(Dropzone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, ...props }));
5819
- }, [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]);
5820
6379
  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]);
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]);
5823
6382
  const isDropzoneEnabled = useMemo(() => {
5824
6383
  const isColumns = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
5825
6384
  const isDraggingSingleColumn = draggedNode?.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
@@ -5861,7 +6420,7 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5861
6420
  ? node.children.length === 1 &&
5862
6421
  resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
5863
6422
  : 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) => {
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) => {
5865
6424
  return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
5866
6425
  if (dragProps?.innerRef) {
5867
6426
  dragProps.innerRef(refNode);
@@ -5879,7 +6438,7 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5879
6438
  }) },
5880
6439
  isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
5881
6440
  .filter((node) => node.data.slotId === slotId)
5882
- .map((item, i) => (React.createElement(EditorBlock, { placeholder: {
6441
+ .map((item, i) => (React.createElement(EditorBlock, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, placeholder: {
5883
6442
  isDraggingOver: snapshot?.isDraggingOver,
5884
6443
  totalIndexes: content.length,
5885
6444
  elementIndex: i,
@@ -5891,8 +6450,8 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5891
6450
  }));
5892
6451
  }
5893
6452
 
5894
- const RootRenderer = ({ onChange }) => {
5895
- useEditorSubscriber();
6453
+ const RootRenderer = ({ onChange, inMemoryEntitiesStore, }) => {
6454
+ useEditorSubscriber(inMemoryEntitiesStore);
5896
6455
  const dragItem = useDraggedItemStore((state) => state.componentId);
5897
6456
  const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5898
6457
  const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
@@ -5972,28 +6531,28 @@ const RootRenderer = ({ onChange }) => {
5972
6531
  handleResizeCanvas();
5973
6532
  // eslint-disable-next-line react-hooks/exhaustive-deps
5974
6533
  }, [containerRef.current]);
6534
+ const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
6535
+ const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
5975
6536
  return (React.createElement(DNDProvider, null,
5976
6537
  dragItem && React.createElement(DraggableContainer, { id: dragItem }),
5977
6538
  React.createElement("div", { "data-ctfl-root": true, className: styles$3.container, ref: containerRef, style: containerStyles },
5978
6539
  userIsDragging && React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitbox }),
5979
- React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue }),
6540
+ React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched }),
5980
6541
  userIsDragging && (React.createElement(React.Fragment, null,
5981
6542
  React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitboxLower }),
5982
6543
  React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.canvasBottomSpacer })))),
5983
6544
  React.createElement("div", { "data-ctfl-hitboxes": true })));
5984
6545
  };
5985
6546
 
5986
- const useInitializeEditor = () => {
6547
+ const useInitializeEditor = (inMemoryEntitiesStore) => {
5987
6548
  const initializeEditor = useEditorStore((state) => state.initializeEditor);
5988
6549
  const [initialized, setInitialized] = useState(false);
5989
- const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
6550
+ const resetEntityStore = useStore(inMemoryEntitiesStore, (state) => state.resetEntityStore);
5990
6551
  useEffect(() => {
5991
6552
  const onVisualEditorInitialize = (event) => {
5992
6553
  if (!event.detail)
5993
6554
  return;
5994
- const { componentRegistry, designTokens, locale: initialLocale,
5995
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5996
- entities, } = event.detail;
6555
+ const { componentRegistry, designTokens, locale: initialLocale, entities } = event.detail;
5997
6556
  initializeEditor({
5998
6557
  initialLocale,
5999
6558
  componentRegistry,
@@ -6001,7 +6560,7 @@ const useInitializeEditor = () => {
6001
6560
  });
6002
6561
  // if entities is set to [], then everything will still work as EntityStore will
6003
6562
  // request entities on demand via ▲REQUEST_ENTITY
6004
- resetEntityStore(initialLocale, entities);
6563
+ resetEntityStore(new EditorModeEntityStore({ locale: initialLocale, entities }));
6005
6564
  setInitialized(true);
6006
6565
  };
6007
6566
  // Listen for VisualEditorComponents internal event
@@ -6021,8 +6580,8 @@ const useInitializeEditor = () => {
6021
6580
  return initialized;
6022
6581
  };
6023
6582
 
6024
- const VisualEditorRoot = ({ experience }) => {
6025
- const initialized = useInitializeEditor();
6583
+ const VisualEditorRoot = ({ experience, inMemoryEntitiesStore: inMemoryEntitiesStore$1 = inMemoryEntitiesStore, }) => {
6584
+ const initialized = useInitializeEditor(inMemoryEntitiesStore$1);
6026
6585
  const setHyperLinkPattern = useEditorStore((state) => state.setHyperLinkPattern);
6027
6586
  const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
6028
6587
  const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
@@ -6058,7 +6617,7 @@ const VisualEditorRoot = ({ experience }) => {
6058
6617
  }, [setHoveringZone, setMousePosition]);
6059
6618
  if (!initialized)
6060
6619
  return null;
6061
- return React.createElement(RootRenderer, null);
6620
+ return React.createElement(RootRenderer, { inMemoryEntitiesStore: inMemoryEntitiesStore$1 });
6062
6621
  };
6063
6622
 
6064
6623
  export { VisualEditorRoot as default };