@contentful/experiences-visual-editor-react 0.0.1-alpha.6 → 0.0.1-alpha.8

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
@@ -11,8 +11,8 @@ import { produce } from 'immer';
11
11
  import { createPortal } from 'react-dom';
12
12
  import { v4 } from 'uuid';
13
13
 
14
- var css_248z$7 = "html,\nbody {\n margin: 0;\n padding: 0;\n}\n\n/*\n * All of these variables are tokens from Forma-36 and should not be adjusted as these\n * are global variables that may affect multiple places.\n * As our customers may use other design libraries, we try to avoid overlapping global\n * variables by always using the prefix `--exp-builder-` inside this SDK.\n */\n\n:root {\n /* Color tokens from Forma 36: https://f36.contentful.com/tokens/color-system */\n --exp-builder-blue100: #e8f5ff;\n --exp-builder-blue200: #ceecff;\n --exp-builder-blue300: #98cbff;\n --exp-builder-blue400: #40a0ff;\n --exp-builder-blue500: #036fe3;\n --exp-builder-blue600: #0059c8;\n --exp-builder-blue700: #0041ab;\n --exp-builder-blue800: #003298;\n --exp-builder-blue900: #002a8e;\n --exp-builder-gray100: #f7f9fa;\n --exp-builder-gray200: #e7ebee;\n --exp-builder-gray300: #cfd9e0;\n --exp-builder-gray400: #aec1cc;\n --exp-builder-gray500: #67728a;\n --exp-builder-gray600: #5a657c;\n --exp-builder-gray700: #414d63;\n --exp-builder-gray800: #1b273a;\n --exp-builder-gray900: #111b2b;\n --exp-builder-purple600: #6c3ecf;\n --exp-builder-red200: #ffe0e0;\n --exp-builder-red800: #7f0010;\n --exp-builder-color-white: #ffffff;\n --exp-builder-glow-primary: 0px 0px 0px 3px #e8f5ff;\n\n /* RGB colors for applying opacity */\n --exp-builder-blue100-rgb: 232, 245, 255;\n --exp-builder-blue300-rgb: 152, 203, 255;\n\n /* Spacing tokens from Forma 36: https://f36.contentful.com/tokens/spacing */\n --exp-builder-spacing-s: 0.75rem;\n --exp-builder-spacing-2xs: 0.25rem;\n\n /* Typography tokens from Forma 36: https://f36.contentful.com/tokens/typography */\n --exp-builder-font-size-l: 1rem;\n --exp-builder-font-size-m: 0.875rem;\n --exp-builder-font-stack-primary: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;\n --exp-builder-line-height-condensed: 1.25;\n}\n";
15
- styleInject(css_248z$7);
14
+ var css_248z$8 = "html,\nbody {\n margin: 0;\n padding: 0;\n}\n\n/*\n * All of these variables are tokens from Forma-36 and should not be adjusted as these\n * are global variables that may affect multiple places.\n * As our customers may use other design libraries, we try to avoid overlapping global\n * variables by always using the prefix `--exp-builder-` inside this SDK.\n */\n\n:root {\n /* Color tokens from Forma 36: https://f36.contentful.com/tokens/color-system */\n --exp-builder-blue100: #e8f5ff;\n --exp-builder-blue200: #ceecff;\n --exp-builder-blue300: #98cbff;\n --exp-builder-blue400: #40a0ff;\n --exp-builder-blue500: #036fe3;\n --exp-builder-blue600: #0059c8;\n --exp-builder-blue700: #0041ab;\n --exp-builder-blue800: #003298;\n --exp-builder-blue900: #002a8e;\n --exp-builder-gray100: #f7f9fa;\n --exp-builder-gray200: #e7ebee;\n --exp-builder-gray300: #cfd9e0;\n --exp-builder-gray400: #aec1cc;\n --exp-builder-gray500: #67728a;\n --exp-builder-gray600: #5a657c;\n --exp-builder-gray700: #414d63;\n --exp-builder-gray800: #1b273a;\n --exp-builder-gray900: #111b2b;\n --exp-builder-purple600: #6c3ecf;\n --exp-builder-red200: #ffe0e0;\n --exp-builder-red800: #7f0010;\n --exp-builder-color-white: #ffffff;\n --exp-builder-glow-primary: 0px 0px 0px 3px #e8f5ff;\n\n /* RGB colors for applying opacity */\n --exp-builder-blue100-rgb: 232, 245, 255;\n --exp-builder-blue300-rgb: 152, 203, 255;\n\n /* Spacing tokens from Forma 36: https://f36.contentful.com/tokens/spacing */\n --exp-builder-spacing-s: 0.75rem;\n --exp-builder-spacing-2xs: 0.25rem;\n\n /* Typography tokens from Forma 36: https://f36.contentful.com/tokens/typography */\n --exp-builder-font-size-l: 1rem;\n --exp-builder-font-size-m: 0.875rem;\n --exp-builder-font-stack-primary: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;\n --exp-builder-line-height-condensed: 1.25;\n}\n";
15
+ styleInject(css_248z$8);
16
16
 
17
17
  const INCOMING_EVENTS$1 = {
18
18
  RequestEditorMode: 'requestEditorMode',
@@ -70,11 +70,13 @@ const CONTENTFUL_COMPONENTS$1 = {
70
70
  },
71
71
  };
72
72
  const EMPTY_CONTAINER_HEIGHT$1 = '80px';
73
+ const DEFAULT_IMAGE_WIDTH = '500px';
73
74
  var PostMessageMethods$2;
74
75
  (function (PostMessageMethods) {
75
76
  PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
76
77
  PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
77
78
  })(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
79
+ const SUPPORTED_IMAGE_FORMATS = ['jpg', 'png', 'webp', 'gif', 'avif'];
78
80
 
79
81
  const structureComponents = new Set([
80
82
  CONTENTFUL_COMPONENTS$1.section.id,
@@ -225,24 +227,18 @@ const transformAlignment = (cfHorizontalAlignment, cfVerticalAlignment, cfFlexDi
225
227
  ? `safe ${cfHorizontalAlignment}`
226
228
  : cfHorizontalAlignment,
227
229
  };
228
- const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageScaling, cfBackgroundImageAlignment) => {
229
- const matchBackgroundSize = (backgroundImageScaling) => {
230
- if ('fill' === backgroundImageScaling)
230
+ const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions) => {
231
+ const matchBackgroundSize = (scaling) => {
232
+ if ('fill' === scaling)
231
233
  return 'cover';
232
- if ('fit' === backgroundImageScaling)
234
+ if ('fit' === scaling)
233
235
  return 'contain';
234
- return undefined;
235
236
  };
236
- const matchBackgroundPosition = (cfBackgroundImageAlignment) => {
237
- if (!cfBackgroundImageAlignment) {
238
- return undefined;
239
- }
240
- if ('string' !== typeof cfBackgroundImageAlignment) {
241
- return undefined;
237
+ const matchBackgroundPosition = (alignment) => {
238
+ if (!alignment || 'string' !== typeof alignment) {
239
+ return;
242
240
  }
243
- let [horizontalAlignment, verticalAlignment] = cfBackgroundImageAlignment
244
- .trim()
245
- .split(/\s+/, 2);
241
+ let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
246
242
  // Special case for handling single values
247
243
  // for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
248
244
  if (horizontalAlignment && !verticalAlignment) {
@@ -278,50 +274,29 @@ const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageScaling
278
274
  return `${horizontalAlignment} ${verticalAlignment}`;
279
275
  };
280
276
  if (!cfBackgroundImageUrl) {
281
- return undefined;
282
- }
283
- return {
284
- backgroundImage: `url(${cfBackgroundImageUrl})`,
285
- backgroundRepeat: cfBackgroundImageScaling === 'tile' ? 'repeat' : 'no-repeat',
286
- backgroundPosition: matchBackgroundPosition(cfBackgroundImageAlignment),
287
- backgroundSize: matchBackgroundSize(cfBackgroundImageScaling),
288
- };
289
- };
290
- const transformContentValue = (value, variableDefinition) => {
291
- if (variableDefinition.type === 'RichText') {
292
- return transformRichText(value);
277
+ return;
293
278
  }
294
- return value;
295
- };
296
- const transformRichText = (value) => {
297
- if (typeof value === 'string') {
298
- return {
299
- data: {},
300
- content: [
301
- {
302
- nodeType: BLOCKS.PARAGRAPH,
303
- data: {},
304
- content: [
305
- {
306
- data: {},
307
- nodeType: 'text',
308
- value: value,
309
- marks: [],
310
- },
311
- ],
312
- },
313
- ],
314
- nodeType: BLOCKS.DOCUMENT,
315
- };
279
+ let backgroundImage;
280
+ let backgroundImageSet;
281
+ if (typeof cfBackgroundImageUrl === 'string') {
282
+ backgroundImage = `url(${cfBackgroundImageUrl})`;
316
283
  }
317
- if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
318
- return value;
284
+ else {
285
+ const imgSet = cfBackgroundImageUrl.srcSet?.join(',');
286
+ backgroundImage = `url(${cfBackgroundImageUrl.url})`;
287
+ backgroundImageSet = `image-set(${imgSet})`;
319
288
  }
320
- return undefined;
289
+ return {
290
+ backgroundImage,
291
+ backgroundImage2: backgroundImageSet,
292
+ backgroundRepeat: cfBackgroundImageOptions?.scaling === 'tile' ? 'repeat' : 'no-repeat',
293
+ backgroundPosition: matchBackgroundPosition(cfBackgroundImageOptions?.alignment),
294
+ backgroundSize: matchBackgroundSize(cfBackgroundImageOptions?.scaling),
295
+ };
321
296
  };
322
297
  const transformWidthSizing = ({ value, cfMargin, }) => {
323
298
  if (!value || !cfMargin)
324
- return undefined;
299
+ return;
325
300
  const transformedValue = transformFill(value);
326
301
  const marginValues = cfMargin.split(' ');
327
302
  const rightMargin = marginValues[1] || '0px';
@@ -339,7 +314,12 @@ const transformWidthSizing = ({ value, cfMargin, }) => {
339
314
  return transformedValue;
340
315
  };
341
316
 
342
- const toCSSAttribute = (key) => key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
317
+ const toCSSAttribute = (key) => {
318
+ let val = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
319
+ // Remove the number from the end of the key to allow for overrides on style properties
320
+ val = val.replace(/\d+$/, '');
321
+ return val;
322
+ };
343
323
  const buildStyleTag = ({ styles, nodeId }) => {
344
324
  const stylesStr = Object.entries(styles)
345
325
  .filter(([, value]) => value !== undefined)
@@ -349,13 +329,13 @@ const buildStyleTag = ({ styles, nodeId }) => {
349
329
  const styleRule = `.${className}{ ${stylesStr} }`;
350
330
  return [className, styleRule];
351
331
  };
352
- const buildCfStyles = ({ cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection, cfFlexWrap, cfMargin, cfPadding, cfBackgroundColor, cfWidth, cfHeight, cfMaxWidth, cfBorder, cfGap, cfBackgroundImageUrl, cfBackgroundImageAlignment, cfBackgroundImageScaling, cfFontSize, cfFontWeight, cfLineHeight, cfLetterSpacing, cfTextColor, cfTextAlign, cfTextTransform, cfTextBold, cfTextItalic, cfTextUnderline, cfColumnSpan, }) => {
332
+ const buildCfStyles = ({ cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection, cfFlexWrap, cfMargin, cfPadding, cfBackgroundColor, cfWidth, cfHeight, cfMaxWidth, cfBorder, cfGap, cfBackgroundImageUrl, cfBackgroundImageOptions, cfFontSize, cfFontWeight, cfImageOptions, cfLineHeight, cfLetterSpacing, cfTextColor, cfTextAlign, cfTextTransform, cfTextBold, cfTextItalic, cfTextUnderline, cfColumnSpan, }) => {
353
333
  return {
354
334
  margin: cfMargin,
355
335
  padding: cfPadding,
356
336
  backgroundColor: cfBackgroundColor,
357
- width: transformWidthSizing({ value: cfWidth, cfMargin }),
358
- height: transformFill(cfHeight),
337
+ width: transformWidthSizing({ value: cfWidth || cfImageOptions?.width, cfMargin }),
338
+ height: transformFill(cfHeight || cfImageOptions?.height),
359
339
  maxWidth: cfMaxWidth,
360
340
  ...transformGridColumn(cfColumnSpan),
361
341
  ...transformBorderStyle(cfBorder),
@@ -363,7 +343,7 @@ const buildCfStyles = ({ cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirec
363
343
  ...transformAlignment(cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection),
364
344
  flexDirection: cfFlexDirection,
365
345
  flexWrap: cfFlexWrap,
366
- ...transformBackgroundImage(cfBackgroundImageUrl, cfBackgroundImageScaling, cfBackgroundImageAlignment),
346
+ ...transformBackgroundImage(cfBackgroundImageUrl, cfBackgroundImageOptions),
367
347
  fontSize: cfFontSize,
368
348
  fontWeight: cfTextBold ? 'bold' : cfFontWeight,
369
349
  fontStyle: cfTextItalic ? 'italic' : 'normal',
@@ -374,10 +354,12 @@ const buildCfStyles = ({ cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirec
374
354
  textTransform: cfTextTransform,
375
355
  textDecoration: cfTextUnderline ? 'underline' : 'none',
376
356
  boxSizing: 'border-box',
357
+ objectFit: cfImageOptions?.objectFit,
358
+ objectPosition: cfImageOptions?.objectPosition,
377
359
  };
378
360
  };
379
361
  /**
380
- * Container/section default behaviour:
362
+ * Container/section default behavior:
381
363
  * Default height => height: EMPTY_CONTAINER_HEIGHT (120px)
382
364
  * If a container component has children => height: 'fit-content'
383
365
  */
@@ -391,6 +373,206 @@ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
391
373
  return EMPTY_CONTAINER_HEIGHT$1;
392
374
  };
393
375
 
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
+ function get(obj, path) {
378
+ if (!path.length) {
379
+ return obj;
380
+ }
381
+ try {
382
+ const [currentPath, ...nextPath] = path;
383
+ return get(obj[currentPath], nextPath);
384
+ }
385
+ catch (err) {
386
+ return undefined;
387
+ }
388
+ }
389
+
390
+ const getBoundValue = (entryOrAsset, path) => {
391
+ const value = get(entryOrAsset, path.split('/').slice(2, -1));
392
+ return value && typeof value == 'object' && value.url
393
+ ? value.url
394
+ : value;
395
+ };
396
+
397
+ const transformRichText = (entryOrAsset, path) => {
398
+ const value = getBoundValue(entryOrAsset, path);
399
+ if (typeof value === 'string') {
400
+ return {
401
+ data: {},
402
+ content: [
403
+ {
404
+ nodeType: BLOCKS.PARAGRAPH,
405
+ data: {},
406
+ content: [
407
+ {
408
+ data: {},
409
+ nodeType: 'text',
410
+ value: value,
411
+ marks: [],
412
+ },
413
+ ],
414
+ },
415
+ ],
416
+ nodeType: BLOCKS.DOCUMENT,
417
+ };
418
+ }
419
+ if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
420
+ return value;
421
+ }
422
+ return undefined;
423
+ };
424
+
425
+ function getOptimizedImageUrl(url, width, quality, format) {
426
+ const params = new URLSearchParams();
427
+ if (width) {
428
+ params.append('w', width.toString());
429
+ }
430
+ if (quality && quality > 0 && quality < 100) {
431
+ params.append('q', quality.toString());
432
+ }
433
+ if (format) {
434
+ params.append('fm', format);
435
+ }
436
+ const queryString = params.toString();
437
+ return `${url}${queryString ? '?' + queryString : ''}`;
438
+ }
439
+
440
+ const MAX_WIDTH_ALLOWED$1 = 2000;
441
+ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = 100, format) => {
442
+ if (!validateParams(file, quality, format)) ;
443
+ const url = file.url;
444
+ const { width1x, width2x } = getWidths(widthStyle, file);
445
+ const imageUrl1x = getOptimizedImageUrl(url, width1x, quality, format);
446
+ const imageUrl2x = getOptimizedImageUrl(url, width2x, quality, format);
447
+ const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
448
+ const returnedUrlImageUrl = getOptimizedImageUrl(url, width2x, quality, format);
449
+ const optimizedBackgroundImageAsset = {
450
+ url: returnedUrlImageUrl,
451
+ srcSet,
452
+ file,
453
+ };
454
+ return optimizedBackgroundImageAsset;
455
+ function validateParams(file, quality, format) {
456
+ if (!file.details.image) {
457
+ throw Error('No image in file asset to transform');
458
+ }
459
+ if (quality < 0 || quality > 100) {
460
+ throw Error('Quality must be between 0 and 100');
461
+ }
462
+ if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
463
+ throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
464
+ }
465
+ return true;
466
+ }
467
+ };
468
+ function getWidths(widthStyle, file) {
469
+ let width1x = 0;
470
+ let width2x = 0;
471
+ const intrinsicImageWidth = file.details.image.width;
472
+ if (widthStyle.endsWith('px')) {
473
+ width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
474
+ }
475
+ else {
476
+ width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
477
+ }
478
+ width2x = Math.min(width1x * 2, intrinsicImageWidth);
479
+ return { width1x, width2x };
480
+ }
481
+
482
+ const MAX_WIDTH_ALLOWED = 4000;
483
+ const getOptimizedImageAsset = (file, sizes, quality = 100, format) => {
484
+ if (!validateParams(file, quality, format)) ;
485
+ const url = file.url;
486
+ const maxWidth = Math.min(file.details.image.width, MAX_WIDTH_ALLOWED);
487
+ const numOfParts = Math.max(2, Math.ceil(maxWidth / 500));
488
+ const widthParts = Array.from({ length: numOfParts }, (_, index) => Math.ceil((index + 1) * (maxWidth / numOfParts)));
489
+ const srcSet = sizes
490
+ ? widthParts.map((width) => `${getOptimizedImageUrl(url, width, quality, format)} ${width}w`)
491
+ : [];
492
+ const intrinsicImageWidth = file.details.image.width;
493
+ if (intrinsicImageWidth > MAX_WIDTH_ALLOWED) {
494
+ srcSet.push(`${getOptimizedImageUrl(url, undefined, quality, format)} ${intrinsicImageWidth}w`);
495
+ }
496
+ const returnedUrl = getOptimizedImageUrl(url, file.details.image.width > 2000 ? 2000 : undefined, quality, format);
497
+ const optimizedImageAsset = {
498
+ url: returnedUrl,
499
+ srcSet,
500
+ sizes,
501
+ file,
502
+ };
503
+ return optimizedImageAsset;
504
+ function validateParams(file, quality, format) {
505
+ if (!file.details.image) {
506
+ throw Error('No image in file asset to transform');
507
+ }
508
+ if (quality < 0 || quality > 100) {
509
+ throw Error('Quality must be between 0 and 100');
510
+ }
511
+ if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
512
+ throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
513
+ }
514
+ return true;
515
+ }
516
+ };
517
+
518
+ const transformMedia = (asset, variables, resolveDesignValue, variableName, path) => {
519
+ let value;
520
+ //TODO: this will be better served by injectable type transformers instead of if statement
521
+ if (variableName === 'cfImageAsset') {
522
+ const optionsVariableName = 'cfImageOptions';
523
+ const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
524
+ ? variables[optionsVariableName].valuesByBreakpoint
525
+ : {}, optionsVariableName);
526
+ if (!options) {
527
+ console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
528
+ return;
529
+ }
530
+ try {
531
+ value = getOptimizedImageAsset(asset.fields.file, options.targetSize, Number(options.quality), options.format);
532
+ return value;
533
+ }
534
+ catch (error) {
535
+ console.error('Error transforming image asset', error);
536
+ }
537
+ return;
538
+ }
539
+ if (variableName === 'cfBackgroundImageUrl') {
540
+ const width = resolveDesignValue(variables['cfWidth']?.type === 'DesignValue' ? variables['cfWidth'].valuesByBreakpoint : {}, 'cfWidth');
541
+ const optionsVariableName = 'cfBackgroundImageOptions';
542
+ const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
543
+ ? variables[optionsVariableName].valuesByBreakpoint
544
+ : {}, optionsVariableName);
545
+ if (!options) {
546
+ console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
547
+ return;
548
+ }
549
+ try {
550
+ value = getOptimizedBackgroundImageAsset(asset.fields.file, width, Number(options.quality), options.format);
551
+ return value;
552
+ }
553
+ catch (error) {
554
+ console.error('Error transforming image asset', error);
555
+ }
556
+ return;
557
+ }
558
+ // return getBoundValue(asset, entityStore, binding, path);
559
+ return getBoundValue(asset, path);
560
+ };
561
+
562
+ const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableDefinition, path) => {
563
+ const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
564
+ if (!entityOrAsset)
565
+ return;
566
+ switch (variableDefinition.type) {
567
+ case 'Media':
568
+ return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
569
+ case 'RichText':
570
+ return transformRichText(entityOrAsset, path);
571
+ default:
572
+ return getBoundValue(entityOrAsset, path);
573
+ }
574
+ };
575
+
394
576
  const getDataFromTree = (tree) => {
395
577
  let dataSource = {};
396
578
  let unboundValues = {};
@@ -529,42 +711,6 @@ const builtInStyles = {
529
711
  description: 'The spacing between the elements of the section',
530
712
  defaultValue: '0px',
531
713
  },
532
- cfBackgroundImageUrl: {
533
- displayName: 'Background image',
534
- type: 'Text',
535
- defaultValue: '',
536
- description: 'Background image for section or container',
537
- },
538
- cfBackgroundImageScaling: {
539
- displayName: 'Image scaling',
540
- type: 'Text',
541
- group: 'style',
542
- description: 'Adjust background image to fit, fill or tile the container',
543
- defaultValue: 'fit',
544
- validations: {
545
- in: [
546
- {
547
- value: 'fill',
548
- displayName: 'Fill',
549
- },
550
- {
551
- value: 'fit',
552
- displayName: 'Fit',
553
- },
554
- {
555
- value: 'tile',
556
- displayName: 'Tile',
557
- },
558
- ],
559
- },
560
- },
561
- cfBackgroundImageAlignment: {
562
- displayName: 'Image alignment',
563
- type: 'Text',
564
- group: 'style',
565
- description: 'Align background image to the edges of the container',
566
- defaultValue: 'left top',
567
- },
568
714
  cfHyperlink: {
569
715
  displayName: 'Hyperlink',
570
716
  type: 'Text',
@@ -612,6 +758,40 @@ const optionalBuiltInStyles = {
612
758
  description: 'The font weight of the element',
613
759
  defaultValue: '400',
614
760
  },
761
+ cfImageAsset: {
762
+ displayName: 'Image',
763
+ type: 'Media',
764
+ description: 'Image to display',
765
+ },
766
+ cfImageOptions: {
767
+ displayName: 'Image options',
768
+ type: 'Object',
769
+ group: 'style',
770
+ defaultValue: {
771
+ width: DEFAULT_IMAGE_WIDTH,
772
+ height: '100%',
773
+ objectFit: 'none',
774
+ objectPosition: 'center center',
775
+ quality: '100',
776
+ targetSize: DEFAULT_IMAGE_WIDTH,
777
+ },
778
+ },
779
+ cfBackgroundImageUrl: {
780
+ displayName: 'Background image',
781
+ type: 'Media',
782
+ description: 'Background image for component',
783
+ },
784
+ cfBackgroundImageOptions: {
785
+ displayName: 'Background image options',
786
+ type: 'Object',
787
+ group: 'style',
788
+ defaultValue: {
789
+ scaling: 'fill',
790
+ alignment: 'left top',
791
+ quality: '100',
792
+ targetSize: '2000px',
793
+ },
794
+ },
615
795
  cfLineHeight: {
616
796
  displayName: 'Line Height',
617
797
  type: 'Text',
@@ -994,29 +1174,9 @@ const sendMessage = (eventType, data) => {
994
1174
  }, '*');
995
1175
  };
996
1176
 
997
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
998
- function get(obj, path) {
999
- if (!path.length) {
1000
- return obj;
1001
- }
1002
- try {
1003
- const [currentPath, ...nextPath] = path;
1004
- return get(obj[currentPath], nextPath);
1005
- }
1006
- catch (err) {
1007
- return undefined;
1008
- }
1009
- }
1010
-
1011
- function transformAssetFileToUrl(fieldValue) {
1012
- return fieldValue && typeof fieldValue == 'object' && fieldValue.url
1013
- ? fieldValue.url
1014
- : fieldValue;
1015
- }
1016
-
1017
1177
  /**
1018
1178
  * Base Store for entities
1019
- * Can be extened for the different loading behaviours (editor, production, ..)
1179
+ * Can be extended for the different loading behaviours (editor, production, ..)
1020
1180
  */
1021
1181
  class EntityStoreBase {
1022
1182
  constructor({ entities, locale }) {
@@ -1033,68 +1193,26 @@ class EntityStoreBase {
1033
1193
  updateEntity(entity) {
1034
1194
  this.addEntity(entity);
1035
1195
  }
1036
- getValueDeep(headLinkOrEntity, deepPath) {
1037
- const resolveFieldset = (unresolvedFieldset, headEntity) => {
1038
- const resolvedFieldset = [];
1039
- let entityToResolveFieldsFrom = headEntity;
1040
- for (let i = 0; i < unresolvedFieldset.length; i++) {
1041
- const isLeaf = i === unresolvedFieldset.length - 1; // with last row, we are not expecting a link, but a value
1042
- const row = unresolvedFieldset[i];
1043
- const [, field, _localeQualifier] = row;
1044
- if (!entityToResolveFieldsFrom) {
1045
- throw new Error(`Logic Error: Cannot resolve field ${field} of a fieldset as there is no entity to resolve it from.`);
1046
- }
1047
- if (isLeaf) {
1048
- resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
1049
- break;
1050
- }
1051
- const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
1052
- if (undefined === fieldValue) {
1053
- return {
1054
- resolvedFieldset,
1055
- isFullyResolved: false,
1056
- reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
1057
- };
1058
- }
1059
- else if (isLink(fieldValue)) {
1060
- const entity = this.getEntityFromLink(fieldValue);
1061
- if (entity === undefined) {
1062
- throw new Error(`Logic Error: Broken Precondition [by the time resolution of deep path happens all referents should be in EntityStore]: Cannot resolve field ${field} of a fieldset row [${JSON.stringify(row)}] as linked entity not found in the EntityStore. ${JSON.stringify({
1063
- link: fieldValue,
1064
- })}`);
1065
- }
1066
- resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
1067
- entityToResolveFieldsFrom = entity; // we move up
1068
- }
1069
- else {
1070
- // TODO: Eg. when someone changed the schema and the field is not a link anymore, what should we return then?
1071
- throw new Error(`LogicError: Invalid value of a field we consider a reference field. Cannot resolve field ${field} of a fieldset as it is not a link, neither undefined.`);
1072
- }
1196
+ getEntryOrAsset(linkOrEntryOrAsset, path) {
1197
+ if (isDeepPath(path)) {
1198
+ return this.getDeepEntry(linkOrEntryOrAsset, path);
1199
+ }
1200
+ let entity;
1201
+ if (isLink(linkOrEntryOrAsset)) {
1202
+ const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
1203
+ ? this.entryMap.get(linkOrEntryOrAsset.sys.id)
1204
+ : this.assetMap.get(linkOrEntryOrAsset.sys.id);
1205
+ if (!resolvedEntity || resolvedEntity.sys.type !== linkOrEntryOrAsset.sys.linkType) {
1206
+ console.warn(`Experience references unresolved entity: ${JSON.stringify(linkOrEntryOrAsset)}`);
1207
+ return;
1073
1208
  }
1074
- return {
1075
- resolvedFieldset,
1076
- isFullyResolved: true,
1077
- };
1078
- };
1079
- const headEntity = isLink(headLinkOrEntity)
1080
- ? this.getEntityFromLink(headLinkOrEntity)
1081
- : headLinkOrEntity;
1082
- if (undefined === headEntity) {
1083
- return;
1209
+ entity = resolvedEntity;
1084
1210
  }
1085
- const unresolvedFieldset = parseDataSourcePathIntoFieldset(deepPath);
1086
- // The purpose here is to take this intermediate representation of the deep-path
1087
- // and to follow the links to the leaf-entity and field
1088
- // in case we can't follow till the end, we should signal that there was null-reference in the path
1089
- const { resolvedFieldset, isFullyResolved, reason } = resolveFieldset(unresolvedFieldset, headEntity);
1090
- if (!isFullyResolved) {
1091
- reason &&
1092
- console.debug(`[experiences-sdk-react::EntityStoreBased::getValueDeep()] Deep path wasn't resolved till leaf node, falling back to undefined, because: ${reason}`);
1093
- return undefined;
1211
+ else {
1212
+ // We already have the complete entity in preview & delivery (resolved by the CMA client)
1213
+ entity = linkOrEntryOrAsset;
1094
1214
  }
1095
- const [leafEntity, field /* localeQualifier */] = resolvedFieldset[resolvedFieldset.length - 1];
1096
- const fieldValue = get(leafEntity, ['fields', field]); // is allowed to be undefined (when non-required field not set; or even when field does NOT exist on the type)
1097
- return transformAssetFileToUrl(fieldValue);
1215
+ return entity;
1098
1216
  }
1099
1217
  /**
1100
1218
  * @deprecated in the base class this should be simply an abstract method
@@ -1151,7 +1269,7 @@ class EntityStoreBase {
1151
1269
  if (missing.length) {
1152
1270
  // TODO: move to `debug` utils once it is extracted
1153
1271
  console.warn(`Asset "${id}" is not in the store`);
1154
- return undefined;
1272
+ return;
1155
1273
  }
1156
1274
  return resolved[0];
1157
1275
  }
@@ -1167,7 +1285,7 @@ class EntityStoreBase {
1167
1285
  if (missing.length) {
1168
1286
  // TODO: move to `debug` utils once it is extracted
1169
1287
  console.warn(`Entry "${id}" is not in the store`);
1170
- return undefined;
1288
+ return;
1171
1289
  }
1172
1290
  return resolved[0];
1173
1291
  }
@@ -1178,6 +1296,70 @@ class EntityStoreBase {
1178
1296
  }
1179
1297
  return resolved;
1180
1298
  }
1299
+ getDeepEntry(linkOrEntryOrAsset, path) {
1300
+ const resolveFieldset = (unresolvedFieldset, headEntry) => {
1301
+ const resolvedFieldset = [];
1302
+ let entityToResolveFieldsFrom = headEntry;
1303
+ for (let i = 0; i < unresolvedFieldset.length; i++) {
1304
+ const isLeaf = i === unresolvedFieldset.length - 1; // with last row, we are not expecting a link, but a value
1305
+ const row = unresolvedFieldset[i];
1306
+ const [, field, _localeQualifier] = row;
1307
+ if (!entityToResolveFieldsFrom) {
1308
+ throw new Error(`Logic Error: Cannot resolve field ${field} of a fieldset as there is no entity to resolve it from.`);
1309
+ }
1310
+ if (isLeaf) {
1311
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
1312
+ break;
1313
+ }
1314
+ const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
1315
+ if (undefined === fieldValue) {
1316
+ return {
1317
+ resolvedFieldset,
1318
+ isFullyResolved: false,
1319
+ reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
1320
+ };
1321
+ }
1322
+ else if (isLink(fieldValue)) {
1323
+ const entity = this.getEntityFromLink(fieldValue);
1324
+ if (entity === undefined) {
1325
+ return {
1326
+ resolvedFieldset,
1327
+ isFullyResolved: false,
1328
+ reason: `Field reference Link (sys.id=${fieldValue.sys.id}) not found in the EntityStore, waiting...`,
1329
+ };
1330
+ }
1331
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
1332
+ entityToResolveFieldsFrom = entity; // we move up
1333
+ }
1334
+ else {
1335
+ // TODO: Eg. when someone changed the schema and the field is not a link anymore, what should we return then?
1336
+ throw new Error(`LogicError: Invalid value of a field we consider a reference field. Cannot resolve field ${field} of a fieldset as it is not a link, neither undefined.`);
1337
+ }
1338
+ }
1339
+ return {
1340
+ resolvedFieldset,
1341
+ isFullyResolved: true,
1342
+ };
1343
+ };
1344
+ const headEntity = isLink(linkOrEntryOrAsset)
1345
+ ? this.getEntityFromLink(linkOrEntryOrAsset)
1346
+ : linkOrEntryOrAsset;
1347
+ if (undefined === headEntity) {
1348
+ return;
1349
+ }
1350
+ const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
1351
+ // The purpose here is to take this intermediate representation of the deep-path
1352
+ // and to follow the links to the leaf-entity and field
1353
+ // in case we can't follow till the end, we should signal that there was null-reference in the path
1354
+ const { resolvedFieldset, isFullyResolved, reason } = resolveFieldset(unresolvedFieldset, headEntity);
1355
+ if (!isFullyResolved) {
1356
+ reason &&
1357
+ console.debug(`[exp-builder.sdk::EntityStoreBased::getValueDeep()] Deep path wasn't resolved till leaf node, falling back to undefined, because: ${reason}`);
1358
+ return;
1359
+ }
1360
+ const [leafEntity] = resolvedFieldset[resolvedFieldset.length - 1];
1361
+ return leafEntity;
1362
+ }
1181
1363
  isAsset(entity) {
1182
1364
  return entity.sys.type === 'Asset';
1183
1365
  }
@@ -1356,11 +1538,16 @@ class EditorModeEntityStore extends EditorEntityStore {
1356
1538
  const { missing: missingAssetIds } = this.getEntitiesFromMap('Asset', uniqueAssetIds);
1357
1539
  return { missingEntryIds, missingAssetIds };
1358
1540
  }
1359
- getValue(entityLink, path) {
1360
- if (!entityLink || !entityLink.sys)
1541
+ getValue(entityLinkOrEntity, path) {
1542
+ const entity = this.getEntryOrAsset(entityLinkOrEntity, path.join('/'));
1543
+ if (!entity) {
1361
1544
  return;
1362
- const fieldValue = super.getValue(entityLink, path);
1363
- return transformAssetFileToUrl(fieldValue);
1545
+ }
1546
+ const fieldValue = get(entity, path);
1547
+ // walk around to render asset files
1548
+ return fieldValue && typeof fieldValue == 'object' && fieldValue.url
1549
+ ? fieldValue.url
1550
+ : fieldValue;
1364
1551
  }
1365
1552
  }
1366
1553
 
@@ -1442,9 +1629,9 @@ function gatherDeepReferencesFromTree(startingNode, dataSource) {
1442
1629
  return deepReferences;
1443
1630
  }
1444
1631
 
1445
- var css_248z$6 = ".styles-module_DraggableComponent__m5-dA {\n pointer-events: all;\n position: relative;\n transition: outline 0.2s;\n cursor: grab;\n box-sizing: border-box;\n display: flex;\n}\n\n.styles-module_DraggableComponent__m5-dA:before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -2px;\n outline: 2px solid transparent;\n z-index: 1;\n pointer-events: none;\n}\n\n.styles-module_DraggableClone__X8zTA:before {\n outline: 2px solid var(--exp-builder-blue500);\n}\n\n.styles-module_DraggableClone__X8zTA,\n.styles-module_DraggableClone__X8zTA * {\n pointer-events: none !important;\n}\n\n.styles-module_DraggableComponent__m5-dA:not(.styles-module_userIsDragging__lqbjG) :not(.styles-module_DraggableComponent__m5-dA) {\n pointer-events: none;\n}\n\n.styles-module_isDragging__WHjPU {\n overflow: hidden;\n}\n\n.styles-module_isSelected__BzICQ:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_overlay__r4th9 {\n position: absolute;\n display: flex;\n align-items: center;\n min-width: max-content;\n height: 24px;\n z-index: 1;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 14px;\n font-weight: 500;\n background-color: var(--exp-builder-gray500);\n color: var(--exp-builder-color-white);\n border-radius: 0 0 2px 0;\n padding: 4px 12px 4px 12px;\n transition: opacity 0.2s;\n opacity: 0;\n text-wrap: nowrap;\n}\n\n.styles-module_overlayContainer__eiX-5 {\n opacity: 0;\n}\n\n.styles-module_overlayAssembly__tOzZU {\n background-color: var(--exp-builder-purple600);\n}\n\n.styles-module_userIsDragging__lqbjG > .styles-module_overlay__r4th9,\n.styles-module_userIsDragging__lqbjG > .styles-module_overlayContainer__eiX-5 {\n opacity: 0 !important;\n}\n\n.styles-module_userIsDragging__lqbjG:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:not(:has(div[data-rfd-draggable-id]:hover)) > .styles-module_overlay__r4th9 {\n opacity: 1;\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:before,\n.styles-module_DraggableComponent__m5-dA:hover div[data-rfd-draggable-id]:before {\n outline: 2px dashed var(--exp-builder-gray500);\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:not(:has(div[data-rfd-draggable-id]:hover)):before {\n outline: 2px solid var(--exp-builder-gray500);\n}\n\n.styles-module_isAssemblyBlock__Y3Avk:hover:before,\n.styles-module_isAssemblyBlock__Y3Avk:hover div[data-rfd-draggable-id]:before,\n.styles-module_DraggableComponent__m5-dA:hover div[data-rfd-draggable-id][data-cf-node-block-type^='assembly']:before {\n outline: 2px dashed var(--exp-builder-purple600);\n}\n\n.styles-module_isAssemblyBlock__Y3Avk:hover:not(:has(div[data-rfd-draggable-id]:hover)):before {\n outline: 2px solid var(--exp-builder-purple600);\n}\n";
1632
+ var css_248z$7 = ".styles-module_DraggableComponent__m5-dA {\n pointer-events: all;\n position: relative;\n transition: outline 0.2s;\n cursor: grab;\n box-sizing: border-box;\n display: flex;\n}\n\n.styles-module_DraggableComponent__m5-dA:before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -2px;\n outline: 2px solid transparent;\n z-index: 1;\n pointer-events: none;\n}\n\n.styles-module_DraggableClone__X8zTA:before {\n outline: 2px solid var(--exp-builder-blue500);\n}\n\n.styles-module_DraggableClone__X8zTA,\n.styles-module_DraggableClone__X8zTA * {\n pointer-events: none !important;\n}\n\n.styles-module_DraggableComponent__m5-dA:not(.styles-module_userIsDragging__lqbjG) :not(.styles-module_DraggableComponent__m5-dA) {\n pointer-events: none;\n}\n\n.styles-module_isDragging__WHjPU {\n overflow: hidden;\n}\n\n.styles-module_isSelected__BzICQ:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_overlay__r4th9 {\n position: absolute;\n display: flex;\n align-items: center;\n min-width: max-content;\n height: 24px;\n z-index: 1;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 14px;\n font-weight: 500;\n background-color: var(--exp-builder-gray500);\n color: var(--exp-builder-color-white);\n border-radius: 0 0 2px 0;\n padding: 4px 12px 4px 12px;\n transition: opacity 0.2s;\n opacity: 0;\n text-wrap: nowrap;\n}\n\n.styles-module_overlayContainer__eiX-5 {\n opacity: 0;\n}\n\n.styles-module_overlayAssembly__tOzZU {\n background-color: var(--exp-builder-purple600);\n}\n\n.styles-module_userIsDragging__lqbjG > .styles-module_overlay__r4th9,\n.styles-module_userIsDragging__lqbjG > .styles-module_overlayContainer__eiX-5 {\n opacity: 0 !important;\n}\n\n.styles-module_userIsDragging__lqbjG:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:not(:has(div[data-rfd-draggable-id]:hover)) > .styles-module_overlay__r4th9 {\n opacity: 1;\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:before,\n.styles-module_DraggableComponent__m5-dA:hover div[data-rfd-draggable-id]:before {\n outline: 2px dashed var(--exp-builder-gray500);\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:not(:has(div[data-rfd-draggable-id]:hover)):before {\n outline: 2px solid var(--exp-builder-gray500);\n}\n\n.styles-module_isAssemblyBlock__Y3Avk:hover:before,\n.styles-module_isAssemblyBlock__Y3Avk:hover div[data-rfd-draggable-id]:before,\n.styles-module_DraggableComponent__m5-dA:hover div[data-rfd-draggable-id][data-cf-node-block-type^='assembly']:before {\n outline: 2px dashed var(--exp-builder-purple600);\n}\n\n.styles-module_isAssemblyBlock__Y3Avk:hover:not(:has(div[data-rfd-draggable-id]:hover)):before {\n outline: 2px solid var(--exp-builder-purple600);\n}\n";
1446
1633
  var styles$3 = {"DraggableComponent":"styles-module_DraggableComponent__m5-dA","DraggableClone":"styles-module_DraggableClone__X8zTA","userIsDragging":"styles-module_userIsDragging__lqbjG","isDragging":"styles-module_isDragging__WHjPU","isSelected":"styles-module_isSelected__BzICQ","overlay":"styles-module_overlay__r4th9","overlayContainer":"styles-module_overlayContainer__eiX-5","overlayAssembly":"styles-module_overlayAssembly__tOzZU","isAssemblyBlock":"styles-module_isAssemblyBlock__Y3Avk"};
1447
- styleInject(css_248z$6);
1634
+ styleInject(css_248z$7);
1448
1635
 
1449
1636
  const SCROLL_STATES = {
1450
1637
  Start: 'scrollStart',
@@ -1547,13 +1734,14 @@ const CF_STYLE_ATTRIBUTES = [
1547
1734
  'cfWidth',
1548
1735
  'cfMaxWidth',
1549
1736
  'cfHeight',
1737
+ 'cfImageAsset',
1738
+ 'cfImageOptions',
1739
+ 'cfBackgroundImageUrl',
1740
+ 'cfBackgroundImageOptions',
1550
1741
  'cfFlexDirection',
1551
1742
  'cfFlexWrap',
1552
1743
  'cfBorder',
1553
1744
  'cfGap',
1554
- 'cfBackgroundImageUrl',
1555
- 'cfBackgroundImageScaling',
1556
- 'cfBackgroundImageAlignment',
1557
1745
  'cfFontSize',
1558
1746
  'cfFontWeight',
1559
1747
  'cfLineHeight',
@@ -1568,6 +1756,8 @@ const CF_STYLE_ATTRIBUTES = [
1568
1756
  // we need to keep those in this constant array
1569
1757
  // so that omit() in <VisualEditorBlock> and <CompositionBlock>
1570
1758
  // can filter them out and not pass as props
1759
+ 'cfBackgroundImageScaling',
1760
+ 'cfBackgroundImageAlignment',
1571
1761
  'cfBackgroundImageAlignmentVertical',
1572
1762
  'cfBackgroundImageAlignmentHorizontal',
1573
1763
  ];
@@ -1703,7 +1893,7 @@ const NEW_COMPONENT_ID = 'ctfl-new-draggable';
1703
1893
  const CTFL_ZONE_ID = 'data-ctfl-zone-id';
1704
1894
  const CTFL_DRAGGING_ELEMENT = 'data-ctfl-dragging-element';
1705
1895
  const HITBOX = {
1706
- WIDTH: 80,
1896
+ WIDTH: 70,
1707
1897
  HEIGHT: 20,
1708
1898
  INITIAL_OFFSET: 10,
1709
1899
  OFFSET_INCREMENT: 8,
@@ -2120,10 +2310,12 @@ const useEntityStore = create((set) => ({
2120
2310
  },
2121
2311
  resetEntityStore(locale, entities = []) {
2122
2312
  console.debug(`[experiences-sdk-react] Resetting entity store because the locale changed to '${locale}'.`);
2313
+ const newEntityStore = new EditorModeEntityStore({ locale, entities });
2123
2314
  set({
2124
- entityStore: new EditorModeEntityStore({ locale, entities }),
2315
+ entityStore: newEntityStore,
2125
2316
  areEntitiesFetched: false,
2126
2317
  });
2318
+ return newEntityStore;
2127
2319
  },
2128
2320
  }));
2129
2321
 
@@ -2159,48 +2351,25 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
2159
2351
  };
2160
2352
  }
2161
2353
  else if (variableMapping.type === 'BoundValue') {
2162
- if (!areEntitiesFetched) {
2163
- console.debug(`[experiences-sdk-react::useComponentProps] Idle-cycle: as entities are not fetched(areEntitiesFetched=${areEntitiesFetched}), we cannot resolve bound values for ${variableName} so we just resolve them to default values.`);
2164
- // Just forcing default value (if we're in idle-cycle, entities are missing)
2165
- return {
2166
- ...acc,
2167
- [variableName]: transformContentValue(variableDefinition.defaultValue, variableDefinition),
2168
- };
2169
- }
2170
- if (isDeepPath(variableMapping.path)) {
2171
- const [, uuid] = variableMapping.path.split('/');
2172
- const link = dataSource[uuid];
2173
- const boundValue = entityStore?.getValueDeep(link, variableMapping.path);
2174
- const value = boundValue || variableDefinition.defaultValue;
2175
- return {
2176
- ...acc,
2177
- [variableName]: transformContentValue(value, variableDefinition),
2178
- };
2179
- }
2180
- // // take value from the datasource for both bound and unbound value types
2181
- const [, uuid, ...path] = variableMapping.path.split('/');
2354
+ const [, uuid, path] = variableMapping.path.split('/');
2182
2355
  const binding = dataSource[uuid];
2183
- let boundValue = areEntitiesFetched
2184
- ? entityStore.getValue(binding, path.slice(0, -1))
2185
- : undefined;
2356
+ const variableDefinition = definition.variables[variableName];
2357
+ let boundValue = transformBoundContentValue(node.data.props, entityStore, binding, resolveDesignValue, variableName, variableDefinition, variableMapping.path);
2186
2358
  // In some cases, there may be an asset linked in the path, so we need to consider this scenario:
2187
2359
  // If no 'boundValue' is found, we also attempt to extract the value associated with the second-to-last item in the path.
2188
2360
  // If successful, it means we have identified the linked asset.
2189
2361
  if (!boundValue) {
2190
2362
  const maybeBoundAsset = areEntitiesFetched
2191
- ? entityStore.getValue(binding, path.slice(0, -2))
2363
+ ? entityStore.getValue(binding, path.split('/').slice(0, -2))
2192
2364
  : undefined;
2193
2365
  if (isLinkToAsset(maybeBoundAsset)) {
2194
2366
  boundValue = maybeBoundAsset;
2195
2367
  }
2196
2368
  }
2197
- if (typeof boundValue === 'object' && boundValue.sys?.linkType === 'Asset') {
2198
- boundValue = entityStore?.getValue(boundValue, ['fields', 'file']);
2199
- }
2200
2369
  const value = boundValue || variableDefinition.defaultValue;
2201
2370
  return {
2202
2371
  ...acc,
2203
- [variableName]: transformContentValue(value, variableDefinition),
2372
+ [variableName]: value,
2204
2373
  };
2205
2374
  }
2206
2375
  else {
@@ -2273,12 +2442,15 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
2273
2442
  'data-cf-node-block-id': node.data.blockId,
2274
2443
  'data-cf-node-block-type': node.type,
2275
2444
  };
2445
+ //List explicit style props that will end up being passed to the component
2446
+ const stylesToKeep = ['cfImageAsset'];
2447
+ const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.includes(style));
2276
2448
  const componentProps = {
2277
2449
  className: componentClass,
2278
2450
  editorMode: true,
2279
2451
  node,
2280
2452
  renderDropzone,
2281
- ...omit(props, CF_STYLE_ATTRIBUTES, ['cfHyperlink', 'cfOpenInNewTab']),
2453
+ ...omit(props, stylesToRemove, ['cfHyperlink', 'cfOpenInNewTab']),
2282
2454
  ...(definition.children ? { children: renderDropzone(node) } : {}),
2283
2455
  };
2284
2456
  return { componentProps, wrapperProps };
@@ -2300,13 +2472,16 @@ var PostMessageMethods;
2300
2472
  PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
2301
2473
  })(PostMessageMethods || (PostMessageMethods = {}));
2302
2474
 
2303
- var css_248z$4 = ".cf-heading {\n white-space: pre-line;\n}\n";
2475
+ var css_248z$5 = ".cf-heading {\n white-space: pre-line;\n}\n";
2476
+ styleInject(css_248z$5);
2477
+
2478
+ var css_248z$4 = ".cf-richtext {\n white-space: pre-line;\n}\n";
2304
2479
  styleInject(css_248z$4);
2305
2480
 
2306
- var css_248z$3 = ".cf-richtext {\n white-space: pre-line;\n}\n";
2481
+ var css_248z$3 = ".cf-text {\n white-space: pre-line;\n}\n";
2307
2482
  styleInject(css_248z$3);
2308
2483
 
2309
- var css_248z$2$1 = ".cf-text {\n white-space: pre-line;\n}\n";
2484
+ var css_248z$2$1 = ".cf-no-image {\n position: relative;\n}\n\n.cf-no-image img {\n background-color: var(--cf-color-gray100);\n outline-offset: -2px;\n outline: 2px solid rgba(var(--cf-color-gray400-rgb), 0.5);\n}\n\n[data-ctfl-draggable-id] .cf-no-image {\n width: 100%;\n height: 100%;\n}\n\n[data-ctfl-draggable-id] .cf-no-image img {\n width: 100%;\n}\n\n.cf-no-image svg {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n height: var(--cf-text-3xl);\n width: var(--cf-text-3xl);\n max-height: 100%;\n max-width: 100%;\n}\n\n.cf-no-image svg path {\n fill: var(--cf-color-gray400);\n}\n";
2310
2485
  styleInject(css_248z$2$1);
2311
2486
 
2312
2487
  var css_248z$1$1 = ".contentful-container {\n position: relative;\n display: flex;\n box-sizing: border-box;\n pointer-events: all;\n}\n\n.contentful-container::-webkit-scrollbar {\n display: none; /* Safari and Chrome */\n}\n\n.cf-single-column-wrapper {\n position: relative;\n}\n\n.cf-container-wrapper {\n position: relative;\n width: 100%;\n}\n\n.cf-container-label {\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow-x: clip;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 10;\n}\n\n/* used by ContentfulSectionAsHyperlink.tsx */\n\n.contentful-container-link,\n.contentful-container-link:active,\n.contentful-container-link:visited,\n.contentful-container-link:hover,\n.contentful-container-link:read-write,\n.contentful-container-link:focus-visible {\n color: inherit;\n text-decoration: unset;\n outline: unset;\n}\n";
@@ -2334,8 +2509,8 @@ const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave,
2334
2509
  });
2335
2510
  Flex.displayName = 'Flex';
2336
2511
 
2337
- var css_248z$5 = ".Columns {\n display: flex;\n gap: 24px;\n grid-template-columns: repeat(12, 1fr);\n flex-direction: column;\n min-height: 0; /* NEW */\n min-width: 0; /* NEW; needed for Firefox */\n}\n\n@media (min-width: 768px) {\n .Columns {\n display: grid;\n }\n}\n\n.cf-single-column-wrapper {\n position: relative;\n}\n\n.cf-single-column {\n pointer-events: all;\n}\n\n.cf-single-column-label {\n pointer-events: none;\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 100;\n}\n";
2338
- styleInject(css_248z$5);
2512
+ var css_248z$6 = ".Columns {\n display: flex;\n gap: 24px;\n grid-template-columns: repeat(12, 1fr);\n flex-direction: column;\n min-height: 0; /* NEW */\n min-width: 0; /* NEW; needed for Firefox */\n}\n\n@media (min-width: 768px) {\n .Columns {\n display: grid;\n }\n}\n\n.cf-single-column-wrapper {\n position: relative;\n}\n\n.cf-single-column {\n pointer-events: all;\n}\n\n.cf-single-column-label {\n pointer-events: none;\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 100;\n}\n";
2513
+ styleInject(css_248z$6);
2339
2514
 
2340
2515
  const ColumnWrapper = forwardRef((props, ref) => {
2341
2516
  return (React.createElement("div", { ref: ref, ...props, style: {
@@ -2558,7 +2733,7 @@ const DraggableChildComponent = (props) => {
2558
2733
  })));
2559
2734
  };
2560
2735
 
2561
- var css_248z$2 = ".styles-module_container__te-1H {\n margin-left: auto;\n margin-right: auto;\n position: relative;\n height: 100%;\n width: 100%;\n background-color: transparent;\n transition: background-color 0.2s;\n pointer-events: all !important;\n}\n\n.styles-module_container__te-1H:not(.styles-module_isRoot__5cn-i):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -1px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_isRoot__5cn-i,\n.styles-module_isEmptyCanvas__0XHZR {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__zVpnZ {\n min-height: 80px;\n}\n\n.styles-module_isDragging__Gm8v5:not(.styles-module_isRoot__5cn-i):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_isDestination__5sCQx:not(.styles-module_isRoot__5cn-i):before {\n transition:\n outline 0.2s,\n background-color 0.2s;\n outline: 2px dashed var(--exp-builder-blue400);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n z-index: 2;\n}\n\n.styles-module_hitbox__YQ-1Z {\n position: fixed;\n pointer-events: all !important;\n}\n";
2736
+ var css_248z$2 = ".styles-module_container__te-1H {\n margin-left: auto;\n margin-right: auto;\n position: relative;\n height: 100%;\n width: 100%;\n background-color: transparent;\n transition: background-color 0.2s;\n pointer-events: all !important;\n}\n\n.styles-module_container__te-1H:not(.styles-module_isRoot__5cn-i):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -1px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_isRoot__5cn-i,\n.styles-module_isEmptyCanvas__0XHZR {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__zVpnZ {\n min-height: 80px;\n}\n\n.styles-module_isDragging__Gm8v5:not(.styles-module_isRoot__5cn-i):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_isDestination__5sCQx:not(.styles-module_isRoot__5cn-i):before {\n transition:\n outline 0.2s,\n background-color 0.2s;\n outline: 2px dashed var(--exp-builder-blue400);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n z-index: 2;\n}\n\n.styles-module_hitbox__YQ-1Z {\n position: fixed;\n pointer-events: all !important;\n}\n\n.styles-module_hitbox__YQ-1Z {\n position: fixed;\n pointer-events: all !important;\n}\n";
2562
2737
  var styles$2 = {"container":"styles-module_container__te-1H","isRoot":"styles-module_isRoot__5cn-i","isEmptyCanvas":"styles-module_isEmptyCanvas__0XHZR","isEmptyZone":"styles-module_isEmptyZone__zVpnZ","isDragging":"styles-module_isDragging__Gm8v5","isDestination":"styles-module_isDestination__5sCQx","hitbox":"styles-module_hitbox__YQ-1Z"};
2563
2738
  styleInject(css_248z$2);
2564
2739
 
@@ -3102,11 +3277,11 @@ const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, draggi
3102
3277
  if (node.data.blockId === CONTENTFUL_COMPONENTS.singleColumn.id) {
3103
3278
  return (React.createElement(React.Fragment, null,
3104
3279
  React.createElement(DraggableChildComponent, { elementToRender: elementToRender, id: componentId, index: index, isAssemblyBlock: isAssemblyBlock, isDragDisabled: isSingleColumn, isSelected: selectedNodeId === componentId, userIsDragging: userIsDragging, isContainer: isContainer, blockId: node.data.blockId, coordinates: coordinates, wrapperProps: wrapperProps, onClick: onClick, definition: definition }),
3105
- isStructureComponent && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, enableRootHitboxes: enableRootHitboxes }))));
3280
+ isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, enableRootHitboxes: enableRootHitboxes }))));
3106
3281
  }
3107
3282
  return (React.createElement(DraggableComponent, { placeholder: placeholder, definition: definition, id: componentId, index: index, isAssemblyBlock: isAssemblyBlock, isDragDisabled: isAssemblyBlock, isSelected: selectedNodeId === componentId, userIsDragging: userIsDragging, isContainer: isContainer, blockId: node.data.blockId, coordinates: coordinates, wrapperProps: wrapperProps, onClick: onClick },
3108
3283
  elementToRender(),
3109
- isStructureComponent && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, enableRootHitboxes: enableRootHitboxes }))));
3284
+ isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, enableRootHitboxes: enableRootHitboxes }))));
3110
3285
  };
3111
3286
 
3112
3287
  var css_248z$1 = ".EmptyContainer-module_container__XPH5b {\n height: 200px;\n display: flex;\n width: 100%;\n position: absolute;\n align-items: center;\n justify-content: center;\n flex-direction: row;\n transition: all 0.2s;\n color: var(--exp-builder-gray400);\n font-size: var(--exp-builder-font-size-l);\n font-family: var(--exp-builder-font-stack-primary);\n outline: 2px dashed var(--exp-builder-gray400);\n outline-offset: -2px;\n}\n\n.EmptyContainer-module_highlight__lcICy:hover {\n outline: 2px dashed var(--exp-builder-blue500);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n cursor: grabbing;\n}\n\n.EmptyContainer-module_icon__82-2O rect {\n fill: var(--exp-builder-gray400);\n}\n\n.EmptyContainer-module_label__4TxRa {\n margin-left: var(--exp-builder-spacing-s);\n}\n";
@@ -3633,6 +3808,7 @@ function useEditorSubscriber() {
3633
3808
  const setDataSource = useEditorStore((state) => state.setDataSource);
3634
3809
  const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
3635
3810
  const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
3811
+ const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
3636
3812
  const setComponentId = useDraggedItemStore((state) => state.setComponentId);
3637
3813
  const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
3638
3814
  const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
@@ -3654,7 +3830,7 @@ function useEditorSubscriber() {
3654
3830
  * Fills up entityStore with entities from newDataSource and from the tree.
3655
3831
  * Also manages "entity status" variables (areEntitiesFetched, isFetchingEntities)
3656
3832
  */
3657
- const fetchMissingEntities = useCallback(async (newDataSource, tree) => {
3833
+ const fetchMissingEntities = useCallback(async (entityStore, newDataSource, tree) => {
3658
3834
  // if we realize that there's nothing missing and nothing to fill-fetch before we do any async call,
3659
3835
  // then we can simply return and not lock the EntityStore at all.
3660
3836
  const startFetching = () => {
@@ -3725,10 +3901,7 @@ function useEditorSubscriber() {
3725
3901
  finally {
3726
3902
  endFetching();
3727
3903
  }
3728
- }, [
3729
- /* dataSource, */ entityStore,
3730
- setEntitiesFetched /* setFetchingEntities, assembliesRegistry */,
3731
- ]);
3904
+ }, [setEntitiesFetched /* setFetchingEntities, assembliesRegistry */]);
3732
3905
  useEffect(() => {
3733
3906
  const onMessage = async (event) => {
3734
3907
  let reason;
@@ -3758,6 +3931,11 @@ function useEditorSubscriber() {
3758
3931
  // If the assemblyEntry is not yet fetched, this will be done below by
3759
3932
  // the imperative calls to fetchMissingEntities.
3760
3933
  }
3934
+ let newEntityStore = entityStore;
3935
+ if (entityStore.locale !== locale) {
3936
+ newEntityStore = resetEntityStore(locale);
3937
+ setLocale(locale);
3938
+ }
3761
3939
  // Below are mutually exclusive cases
3762
3940
  if (changedNode) {
3763
3941
  /**
@@ -3770,7 +3948,7 @@ function useEditorSubscriber() {
3770
3948
  if (changedValueType === 'BoundValue') {
3771
3949
  const newDataSource = { ...dataSource, ...changedNode.data.dataSource };
3772
3950
  setDataSource(newDataSource);
3773
- await fetchMissingEntities(newDataSource, tree);
3951
+ await fetchMissingEntities(newEntityStore, newDataSource, tree);
3774
3952
  }
3775
3953
  else if (changedValueType === 'UnboundValue') {
3776
3954
  setUnboundValues({
@@ -3778,19 +3956,15 @@ function useEditorSubscriber() {
3778
3956
  ...changedNode.data.unboundValues,
3779
3957
  });
3780
3958
  }
3781
- // Update the tree when all necessary data is fetched and ready for rendering.
3782
- updateTree(tree);
3783
- setLocale(locale);
3784
3959
  }
3785
3960
  else {
3786
3961
  const { dataSource, unboundValues } = getDataFromTree(tree);
3787
3962
  setDataSource(dataSource);
3788
3963
  setUnboundValues(unboundValues);
3789
- await fetchMissingEntities(dataSource, tree);
3790
- // Update the tree when all necessary data is fetched and ready for rendering.
3791
- updateTree(tree);
3792
- setLocale(locale);
3964
+ await fetchMissingEntities(newEntityStore, dataSource, tree);
3793
3965
  }
3966
+ // Update the tree when all necessary data is fetched and ready for rendering.
3967
+ updateTree(tree);
3794
3968
  break;
3795
3969
  }
3796
3970
  case INCOMING_EVENTS.AssembliesRegistered: {
@@ -3924,6 +4098,7 @@ function useEditorSubscriber() {
3924
4098
  updateTree,
3925
4099
  updateNodesByUpdatedEntity,
3926
4100
  setMousePosition,
4101
+ resetEntityStore,
3927
4102
  ]);
3928
4103
  /*
3929
4104
  * Handles on scroll business
@@ -4304,20 +4479,8 @@ const useInitializeEditor = () => {
4304
4479
 
4305
4480
  const VisualEditorRoot = () => {
4306
4481
  const initialized = useInitializeEditor();
4307
- const locale = useEditorStore((state) => state.locale);
4308
4482
  const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
4309
- const entityStore = useEntityStore((state) => state.entityStore);
4310
4483
  const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
4311
- const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
4312
- useEffect(() => {
4313
- if (!locale) {
4314
- return;
4315
- }
4316
- if (entityStore.locale === locale) {
4317
- return;
4318
- }
4319
- resetEntityStore(locale);
4320
- }, [locale, resetEntityStore, entityStore.locale]);
4321
4484
  useEffect(() => {
4322
4485
  const onMouseMove = (e) => {
4323
4486
  setMousePosition(e.clientX, e.clientY);