@contentful/experiences-core 1.7.1 → 1.7.2-dev-20240613T0816-4d3b2da.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/entity/EntityStore.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1052 -568
- package/dist/index.js.map +1 -1
- package/dist/utils/styleUtils/ssrStyles.d.ts +163 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import md5 from 'md5';
|
|
2
|
-
import { BLOCKS } from '@contentful/rich-text-types';
|
|
3
2
|
import { z, ZodIssueCode } from 'zod';
|
|
3
|
+
import { BLOCKS } from '@contentful/rich-text-types';
|
|
4
4
|
import { uniqBy } from 'lodash-es';
|
|
5
5
|
|
|
6
6
|
const INCOMING_EVENTS = {
|
|
@@ -65,6 +65,43 @@ const CONTENTFUL_COMPONENTS = {
|
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
|
|
68
|
+
const CF_STYLE_ATTRIBUTES = [
|
|
69
|
+
'cfHorizontalAlignment',
|
|
70
|
+
'cfVerticalAlignment',
|
|
71
|
+
'cfMargin',
|
|
72
|
+
'cfPadding',
|
|
73
|
+
'cfBackgroundColor',
|
|
74
|
+
'cfWidth',
|
|
75
|
+
'cfMaxWidth',
|
|
76
|
+
'cfHeight',
|
|
77
|
+
'cfImageAsset',
|
|
78
|
+
'cfImageOptions',
|
|
79
|
+
'cfBackgroundImageUrl',
|
|
80
|
+
'cfBackgroundImageOptions',
|
|
81
|
+
'cfFlexDirection',
|
|
82
|
+
'cfFlexWrap',
|
|
83
|
+
'cfBorder',
|
|
84
|
+
'cfBorderRadius',
|
|
85
|
+
'cfGap',
|
|
86
|
+
'cfFontSize',
|
|
87
|
+
'cfFontWeight',
|
|
88
|
+
'cfLineHeight',
|
|
89
|
+
'cfLetterSpacing',
|
|
90
|
+
'cfTextColor',
|
|
91
|
+
'cfTextAlign',
|
|
92
|
+
'cfTextTransform',
|
|
93
|
+
'cfTextBold',
|
|
94
|
+
'cfTextItalic',
|
|
95
|
+
'cfTextUnderline',
|
|
96
|
+
// For backwards compatibility
|
|
97
|
+
// we need to keep those in this constant array
|
|
98
|
+
// so that omit() in <VisualEditorBlock> and <CompositionBlock>
|
|
99
|
+
// can filter them out and not pass as props
|
|
100
|
+
'cfBackgroundImageScaling',
|
|
101
|
+
'cfBackgroundImageAlignment',
|
|
102
|
+
'cfBackgroundImageAlignmentVertical',
|
|
103
|
+
'cfBackgroundImageAlignmentHorizontal',
|
|
104
|
+
];
|
|
68
105
|
const EMPTY_CONTAINER_HEIGHT = '80px';
|
|
69
106
|
const DEFAULT_IMAGE_WIDTH = '500px';
|
|
70
107
|
var PostMessageMethods;
|
|
@@ -360,485 +397,178 @@ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
|
|
|
360
397
|
return EMPTY_CONTAINER_HEIGHT;
|
|
361
398
|
};
|
|
362
399
|
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
const [currentPath, ...nextPath] = path;
|
|
370
|
-
return get(obj[currentPath], nextPath);
|
|
371
|
-
}
|
|
372
|
-
catch (err) {
|
|
373
|
-
return undefined;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const getBoundValue = (entryOrAsset, path) => {
|
|
378
|
-
const value = get(entryOrAsset, path.split('/').slice(2, -1));
|
|
379
|
-
return value && typeof value == 'object' && value.url
|
|
380
|
-
? value.url
|
|
381
|
-
: value;
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
const transformRichText = (entryOrAsset, path) => {
|
|
385
|
-
const value = getBoundValue(entryOrAsset, path);
|
|
386
|
-
if (typeof value === 'string') {
|
|
387
|
-
return {
|
|
388
|
-
data: {},
|
|
389
|
-
content: [
|
|
400
|
+
// These styles get added to every component, user custom or contentful provided
|
|
401
|
+
const builtInStyles = {
|
|
402
|
+
cfVerticalAlignment: {
|
|
403
|
+
validations: {
|
|
404
|
+
in: [
|
|
390
405
|
{
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
406
|
+
value: 'start',
|
|
407
|
+
displayName: 'Align left',
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
value: 'center',
|
|
411
|
+
displayName: 'Align center',
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
value: 'end',
|
|
415
|
+
displayName: 'Align right',
|
|
401
416
|
},
|
|
402
417
|
],
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
418
|
+
},
|
|
419
|
+
type: 'Text',
|
|
420
|
+
group: 'style',
|
|
421
|
+
description: 'The horizontal alignment of the section',
|
|
422
|
+
defaultValue: 'center',
|
|
423
|
+
displayName: 'Vertical alignment',
|
|
424
|
+
},
|
|
425
|
+
cfHorizontalAlignment: {
|
|
426
|
+
validations: {
|
|
427
|
+
in: [
|
|
428
|
+
{
|
|
429
|
+
value: 'start',
|
|
430
|
+
displayName: 'Align top',
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
value: 'center',
|
|
434
|
+
displayName: 'Align center',
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
value: 'end',
|
|
438
|
+
displayName: 'Align bottom',
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
type: 'Text',
|
|
443
|
+
group: 'style',
|
|
444
|
+
description: 'The horizontal alignment of the section',
|
|
445
|
+
defaultValue: 'center',
|
|
446
|
+
displayName: 'Horizontal alignment',
|
|
447
|
+
},
|
|
448
|
+
cfMargin: {
|
|
449
|
+
displayName: 'Margin',
|
|
450
|
+
type: 'Text',
|
|
451
|
+
group: 'style',
|
|
452
|
+
description: 'The margin of the section',
|
|
453
|
+
defaultValue: '0 0 0 0',
|
|
454
|
+
},
|
|
455
|
+
cfPadding: {
|
|
456
|
+
displayName: 'Padding',
|
|
457
|
+
type: 'Text',
|
|
458
|
+
group: 'style',
|
|
459
|
+
description: 'The padding of the section',
|
|
460
|
+
defaultValue: '0 0 0 0',
|
|
461
|
+
},
|
|
462
|
+
cfBackgroundColor: {
|
|
463
|
+
displayName: 'Background color',
|
|
464
|
+
type: 'Text',
|
|
465
|
+
group: 'style',
|
|
466
|
+
description: 'The background color of the section',
|
|
467
|
+
},
|
|
468
|
+
cfWidth: {
|
|
469
|
+
displayName: 'Width',
|
|
470
|
+
type: 'Text',
|
|
471
|
+
group: 'style',
|
|
472
|
+
description: 'The width of the section',
|
|
473
|
+
defaultValue: '100%',
|
|
474
|
+
},
|
|
475
|
+
cfHeight: {
|
|
476
|
+
displayName: 'Height',
|
|
477
|
+
type: 'Text',
|
|
478
|
+
group: 'style',
|
|
479
|
+
description: 'The height of the section',
|
|
480
|
+
defaultValue: 'fit-content',
|
|
481
|
+
},
|
|
482
|
+
cfMaxWidth: {
|
|
483
|
+
displayName: 'Max width',
|
|
484
|
+
type: 'Text',
|
|
485
|
+
group: 'style',
|
|
486
|
+
description: 'The max-width of the section',
|
|
487
|
+
defaultValue: 'none',
|
|
488
|
+
},
|
|
489
|
+
cfFlexDirection: {
|
|
490
|
+
displayName: 'Direction',
|
|
491
|
+
type: 'Text',
|
|
492
|
+
group: 'style',
|
|
493
|
+
description: 'The orientation of the section',
|
|
494
|
+
defaultValue: 'column',
|
|
495
|
+
},
|
|
496
|
+
cfFlexWrap: {
|
|
497
|
+
displayName: 'Wrap objects',
|
|
498
|
+
type: 'Text',
|
|
499
|
+
group: 'style',
|
|
500
|
+
description: 'Wrap objects',
|
|
501
|
+
defaultValue: 'nowrap',
|
|
502
|
+
},
|
|
503
|
+
cfBorder: {
|
|
504
|
+
displayName: 'Border',
|
|
505
|
+
type: 'Text',
|
|
506
|
+
group: 'style',
|
|
507
|
+
description: 'The border of the section',
|
|
508
|
+
defaultValue: '0px solid rgba(0, 0, 0, 0)',
|
|
509
|
+
},
|
|
510
|
+
cfGap: {
|
|
511
|
+
displayName: 'Gap',
|
|
512
|
+
type: 'Text',
|
|
513
|
+
group: 'style',
|
|
514
|
+
description: 'The spacing between the elements of the section',
|
|
515
|
+
defaultValue: '0px',
|
|
516
|
+
},
|
|
517
|
+
cfHyperlink: {
|
|
518
|
+
displayName: 'Hyperlink',
|
|
519
|
+
type: 'Hyperlink',
|
|
520
|
+
defaultValue: '',
|
|
521
|
+
validations: {
|
|
522
|
+
format: 'URL',
|
|
523
|
+
},
|
|
524
|
+
description: 'hyperlink for section or container',
|
|
525
|
+
},
|
|
526
|
+
cfOpenInNewTab: {
|
|
527
|
+
displayName: 'Hyperlink behaviour',
|
|
528
|
+
type: 'Boolean',
|
|
529
|
+
defaultValue: false,
|
|
530
|
+
description: 'To open hyperlink in new Tab or not',
|
|
531
|
+
},
|
|
473
532
|
};
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const qualityNumber = Number(quality.replace('%', ''));
|
|
478
|
-
if (!validateParams(file, qualityNumber, format)) ;
|
|
479
|
-
const url = file.url;
|
|
480
|
-
const maxWidth = Math.min(file.details.image.width, MAX_WIDTH_ALLOWED);
|
|
481
|
-
const numOfParts = Math.max(2, Math.ceil(maxWidth / 500));
|
|
482
|
-
const widthParts = Array.from({ length: numOfParts }, (_, index) => Math.ceil((index + 1) * (maxWidth / numOfParts)));
|
|
483
|
-
const srcSet = sizes
|
|
484
|
-
? widthParts.map((width) => `${getOptimizedImageUrl(url, width, qualityNumber, format)} ${width}w`)
|
|
485
|
-
: [];
|
|
486
|
-
const intrinsicImageWidth = file.details.image.width;
|
|
487
|
-
if (intrinsicImageWidth > MAX_WIDTH_ALLOWED) {
|
|
488
|
-
srcSet.push(`${getOptimizedImageUrl(url, undefined, qualityNumber, format)} ${intrinsicImageWidth}w`);
|
|
489
|
-
}
|
|
490
|
-
const returnedUrl = getOptimizedImageUrl(url, file.details.image.width > 2000 ? 2000 : undefined, qualityNumber, format);
|
|
491
|
-
const optimizedImageAsset = {
|
|
492
|
-
url: returnedUrl,
|
|
493
|
-
srcSet,
|
|
494
|
-
sizes,
|
|
495
|
-
file,
|
|
496
|
-
loading,
|
|
497
|
-
};
|
|
498
|
-
return optimizedImageAsset;
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
const transformMedia = (asset, variables, resolveDesignValue, variableName, path) => {
|
|
502
|
-
let value;
|
|
503
|
-
// If it is not a deep path and not pointing to the file of the asset,
|
|
504
|
-
// it is just pointing to a normal field and therefore we just resolve the value as normal field
|
|
505
|
-
if (!isDeepPath(path) && !lastPathNamedSegmentEq(path, 'file')) {
|
|
506
|
-
return getBoundValue(asset, path);
|
|
507
|
-
}
|
|
508
|
-
//TODO: this will be better served by injectable type transformers instead of if statement
|
|
509
|
-
if (variableName === 'cfImageAsset') {
|
|
510
|
-
const optionsVariableName = 'cfImageOptions';
|
|
511
|
-
const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
|
|
512
|
-
? variables[optionsVariableName].valuesByBreakpoint
|
|
513
|
-
: {}, optionsVariableName);
|
|
514
|
-
if (!options) {
|
|
515
|
-
console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
try {
|
|
519
|
-
value = getOptimizedImageAsset({
|
|
520
|
-
file: asset.fields.file,
|
|
521
|
-
loading: options.loading,
|
|
522
|
-
sizes: options.targetSize,
|
|
523
|
-
quality: options.quality,
|
|
524
|
-
format: options.format,
|
|
525
|
-
});
|
|
526
|
-
return value;
|
|
527
|
-
}
|
|
528
|
-
catch (error) {
|
|
529
|
-
console.error('Error transforming image asset', error);
|
|
530
|
-
}
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
if (variableName === 'cfBackgroundImageUrl') {
|
|
534
|
-
const width = resolveDesignValue(variables['cfWidth']?.type === 'DesignValue' ? variables['cfWidth'].valuesByBreakpoint : {}, 'cfWidth');
|
|
535
|
-
const optionsVariableName = 'cfBackgroundImageOptions';
|
|
536
|
-
const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
|
|
537
|
-
? variables[optionsVariableName].valuesByBreakpoint
|
|
538
|
-
: {}, optionsVariableName);
|
|
539
|
-
if (!options) {
|
|
540
|
-
console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
try {
|
|
544
|
-
value = getOptimizedBackgroundImageAsset(asset.fields.file, width, options.quality, options.format);
|
|
545
|
-
return value;
|
|
546
|
-
}
|
|
547
|
-
catch (error) {
|
|
548
|
-
console.error('Error transforming image asset', error);
|
|
549
|
-
}
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
return asset.fields.file?.url;
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableDefinition, path) => {
|
|
556
|
-
const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
|
|
557
|
-
if (!entityOrAsset)
|
|
558
|
-
return;
|
|
559
|
-
switch (variableDefinition.type) {
|
|
560
|
-
case 'Media':
|
|
561
|
-
// If we bound a normal entry field to the media veriable we just return the bound value
|
|
562
|
-
if (entityOrAsset.sys.type === 'Entry') {
|
|
563
|
-
return getBoundValue(entityOrAsset, path);
|
|
564
|
-
}
|
|
565
|
-
return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
|
|
566
|
-
case 'RichText':
|
|
567
|
-
return transformRichText(entityOrAsset, path);
|
|
568
|
-
default:
|
|
569
|
-
return getBoundValue(entityOrAsset, path);
|
|
570
|
-
}
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
const getDataFromTree = (tree) => {
|
|
574
|
-
let dataSource = {};
|
|
575
|
-
let unboundValues = {};
|
|
576
|
-
const queue = [...tree.root.children];
|
|
577
|
-
while (queue.length) {
|
|
578
|
-
const node = queue.shift();
|
|
579
|
-
if (!node) {
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
dataSource = { ...dataSource, ...node.data.dataSource };
|
|
583
|
-
unboundValues = { ...unboundValues, ...node.data.unboundValues };
|
|
584
|
-
if (node.children.length) {
|
|
585
|
-
queue.push(...node.children);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return {
|
|
589
|
-
dataSource,
|
|
590
|
-
unboundValues,
|
|
591
|
-
};
|
|
592
|
-
};
|
|
593
|
-
/**
|
|
594
|
-
* Gets calculates the index to drop the dragged component based on the mouse position
|
|
595
|
-
* @returns {InsertionData} a object containing a node that will become a parent for dragged component and index at which it must be inserted
|
|
596
|
-
*/
|
|
597
|
-
const getInsertionData = ({ dropReceiverParentNode, dropReceiverNode, flexDirection, isMouseAtTopBorder, isMouseAtBottomBorder, isMouseInLeftHalf, isMouseInUpperHalf, isOverTopIndicator, isOverBottomIndicator, }) => {
|
|
598
|
-
const APPEND_INSIDE = dropReceiverNode.children.length;
|
|
599
|
-
const PREPEND_INSIDE = 0;
|
|
600
|
-
if (isMouseAtTopBorder || isMouseAtBottomBorder) {
|
|
601
|
-
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
602
|
-
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
603
|
-
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
604
|
-
return {
|
|
605
|
-
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
606
|
-
node: dropReceiverParentNode,
|
|
607
|
-
index: isMouseAtBottomBorder ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
// if over one of the section indicators
|
|
611
|
-
if (isOverTopIndicator || isOverBottomIndicator) {
|
|
612
|
-
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
613
|
-
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
614
|
-
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
615
|
-
return {
|
|
616
|
-
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
617
|
-
node: dropReceiverParentNode,
|
|
618
|
-
index: isOverBottomIndicator ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
if (flexDirection === undefined || flexDirection === 'row') {
|
|
622
|
-
return {
|
|
623
|
-
node: dropReceiverNode,
|
|
624
|
-
index: isMouseInLeftHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
return {
|
|
629
|
-
node: dropReceiverNode,
|
|
630
|
-
index: isMouseInUpperHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
const generateRandomId = (letterCount) => {
|
|
635
|
-
const LETTERS = 'abcdefghijklmnopqvwxyzABCDEFGHIJKLMNOPQVWXYZ';
|
|
636
|
-
const NUMS = '0123456789';
|
|
637
|
-
const ALNUM = NUMS + LETTERS;
|
|
638
|
-
const times = (n, callback) => Array.from({ length: n }, callback);
|
|
639
|
-
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
640
|
-
return times(letterCount, () => ALNUM[random(0, ALNUM.length - 1)]).join('');
|
|
641
|
-
};
|
|
642
|
-
const checkIsAssemblyNode = ({ componentId, usedComponents, }) => {
|
|
643
|
-
if (!usedComponents?.length)
|
|
644
|
-
return false;
|
|
645
|
-
return usedComponents.some((usedComponent) => usedComponent.sys.id === componentId);
|
|
646
|
-
};
|
|
647
|
-
/** @deprecated use `checkIsAssemblyNode` instead. Will be removed with SDK v5. */
|
|
648
|
-
const checkIsAssembly = checkIsAssemblyNode;
|
|
649
|
-
/**
|
|
650
|
-
* This check assumes that the entry is already ensured to be an experience, i.e. the
|
|
651
|
-
* content type of the entry is an experience type with the necessary annotations.
|
|
652
|
-
**/
|
|
653
|
-
const checkIsAssemblyEntry = (entry) => {
|
|
654
|
-
return Boolean(entry.fields?.componentSettings);
|
|
655
|
-
};
|
|
656
|
-
const checkIsAssemblyDefinition = (component) => component?.category === ASSEMBLY_DEFAULT_CATEGORY;
|
|
657
|
-
|
|
658
|
-
const isExperienceEntry = (entry) => {
|
|
659
|
-
return (entry?.sys?.type === 'Entry' &&
|
|
660
|
-
!!entry.fields?.title &&
|
|
661
|
-
!!entry.fields?.slug &&
|
|
662
|
-
!!entry.fields?.componentTree &&
|
|
663
|
-
Array.isArray(entry.fields.componentTree.breakpoints) &&
|
|
664
|
-
Array.isArray(entry.fields.componentTree.children) &&
|
|
665
|
-
typeof entry.fields.componentTree.schemaVersion === 'string');
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
const supportedModes = ['delivery', 'preview', 'editor'];
|
|
669
|
-
|
|
670
|
-
// These styles get added to every component, user custom or contentful provided
|
|
671
|
-
const builtInStyles = {
|
|
672
|
-
cfVerticalAlignment: {
|
|
673
|
-
validations: {
|
|
674
|
-
in: [
|
|
675
|
-
{
|
|
676
|
-
value: 'start',
|
|
677
|
-
displayName: 'Align left',
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
value: 'center',
|
|
681
|
-
displayName: 'Align center',
|
|
682
|
-
},
|
|
683
|
-
{
|
|
684
|
-
value: 'end',
|
|
685
|
-
displayName: 'Align right',
|
|
686
|
-
},
|
|
687
|
-
],
|
|
688
|
-
},
|
|
533
|
+
const optionalBuiltInStyles = {
|
|
534
|
+
cfFontSize: {
|
|
535
|
+
displayName: 'Font Size',
|
|
689
536
|
type: 'Text',
|
|
690
537
|
group: 'style',
|
|
691
|
-
description: 'The
|
|
692
|
-
defaultValue: '
|
|
693
|
-
displayName: 'Vertical alignment',
|
|
538
|
+
description: 'The font size of the element',
|
|
539
|
+
defaultValue: '16px',
|
|
694
540
|
},
|
|
695
|
-
|
|
541
|
+
cfFontWeight: {
|
|
696
542
|
validations: {
|
|
697
543
|
in: [
|
|
698
544
|
{
|
|
699
|
-
value: '
|
|
700
|
-
displayName: '
|
|
545
|
+
value: '400',
|
|
546
|
+
displayName: 'Normal',
|
|
701
547
|
},
|
|
702
548
|
{
|
|
703
|
-
value: '
|
|
704
|
-
displayName: '
|
|
549
|
+
value: '500',
|
|
550
|
+
displayName: 'Medium',
|
|
705
551
|
},
|
|
706
552
|
{
|
|
707
|
-
value: '
|
|
708
|
-
displayName: '
|
|
553
|
+
value: '600',
|
|
554
|
+
displayName: 'Semi Bold',
|
|
709
555
|
},
|
|
710
556
|
],
|
|
711
557
|
},
|
|
558
|
+
displayName: 'Font Weight',
|
|
712
559
|
type: 'Text',
|
|
713
560
|
group: 'style',
|
|
714
|
-
description: 'The
|
|
715
|
-
defaultValue: '
|
|
716
|
-
displayName: 'Horizontal alignment',
|
|
561
|
+
description: 'The font weight of the element',
|
|
562
|
+
defaultValue: '400',
|
|
717
563
|
},
|
|
718
|
-
|
|
719
|
-
displayName: '
|
|
720
|
-
type: '
|
|
721
|
-
|
|
722
|
-
description: 'The margin of the section',
|
|
723
|
-
defaultValue: '0 0 0 0',
|
|
564
|
+
cfImageAsset: {
|
|
565
|
+
displayName: 'Image',
|
|
566
|
+
type: 'Media',
|
|
567
|
+
description: 'Image to display',
|
|
724
568
|
},
|
|
725
|
-
|
|
726
|
-
displayName: '
|
|
727
|
-
type: '
|
|
728
|
-
group: 'style',
|
|
729
|
-
description: 'The padding of the section',
|
|
730
|
-
defaultValue: '0 0 0 0',
|
|
731
|
-
},
|
|
732
|
-
cfBackgroundColor: {
|
|
733
|
-
displayName: 'Background color',
|
|
734
|
-
type: 'Text',
|
|
735
|
-
group: 'style',
|
|
736
|
-
description: 'The background color of the section',
|
|
737
|
-
},
|
|
738
|
-
cfWidth: {
|
|
739
|
-
displayName: 'Width',
|
|
740
|
-
type: 'Text',
|
|
741
|
-
group: 'style',
|
|
742
|
-
description: 'The width of the section',
|
|
743
|
-
defaultValue: '100%',
|
|
744
|
-
},
|
|
745
|
-
cfHeight: {
|
|
746
|
-
displayName: 'Height',
|
|
747
|
-
type: 'Text',
|
|
748
|
-
group: 'style',
|
|
749
|
-
description: 'The height of the section',
|
|
750
|
-
defaultValue: 'fit-content',
|
|
751
|
-
},
|
|
752
|
-
cfMaxWidth: {
|
|
753
|
-
displayName: 'Max width',
|
|
754
|
-
type: 'Text',
|
|
755
|
-
group: 'style',
|
|
756
|
-
description: 'The max-width of the section',
|
|
757
|
-
defaultValue: 'none',
|
|
758
|
-
},
|
|
759
|
-
cfFlexDirection: {
|
|
760
|
-
displayName: 'Direction',
|
|
761
|
-
type: 'Text',
|
|
762
|
-
group: 'style',
|
|
763
|
-
description: 'The orientation of the section',
|
|
764
|
-
defaultValue: 'column',
|
|
765
|
-
},
|
|
766
|
-
cfFlexWrap: {
|
|
767
|
-
displayName: 'Wrap objects',
|
|
768
|
-
type: 'Text',
|
|
769
|
-
group: 'style',
|
|
770
|
-
description: 'Wrap objects',
|
|
771
|
-
defaultValue: 'nowrap',
|
|
772
|
-
},
|
|
773
|
-
cfBorder: {
|
|
774
|
-
displayName: 'Border',
|
|
775
|
-
type: 'Text',
|
|
776
|
-
group: 'style',
|
|
777
|
-
description: 'The border of the section',
|
|
778
|
-
defaultValue: '0px solid rgba(0, 0, 0, 0)',
|
|
779
|
-
},
|
|
780
|
-
cfGap: {
|
|
781
|
-
displayName: 'Gap',
|
|
782
|
-
type: 'Text',
|
|
783
|
-
group: 'style',
|
|
784
|
-
description: 'The spacing between the elements of the section',
|
|
785
|
-
defaultValue: '0px',
|
|
786
|
-
},
|
|
787
|
-
cfHyperlink: {
|
|
788
|
-
displayName: 'Hyperlink',
|
|
789
|
-
type: 'Hyperlink',
|
|
790
|
-
defaultValue: '',
|
|
791
|
-
validations: {
|
|
792
|
-
format: 'URL',
|
|
793
|
-
},
|
|
794
|
-
description: 'hyperlink for section or container',
|
|
795
|
-
},
|
|
796
|
-
cfOpenInNewTab: {
|
|
797
|
-
displayName: 'Hyperlink behaviour',
|
|
798
|
-
type: 'Boolean',
|
|
799
|
-
defaultValue: false,
|
|
800
|
-
description: 'To open hyperlink in new Tab or not',
|
|
801
|
-
},
|
|
802
|
-
};
|
|
803
|
-
const optionalBuiltInStyles = {
|
|
804
|
-
cfFontSize: {
|
|
805
|
-
displayName: 'Font Size',
|
|
806
|
-
type: 'Text',
|
|
807
|
-
group: 'style',
|
|
808
|
-
description: 'The font size of the element',
|
|
809
|
-
defaultValue: '16px',
|
|
810
|
-
},
|
|
811
|
-
cfFontWeight: {
|
|
812
|
-
validations: {
|
|
813
|
-
in: [
|
|
814
|
-
{
|
|
815
|
-
value: '400',
|
|
816
|
-
displayName: 'Normal',
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
value: '500',
|
|
820
|
-
displayName: 'Medium',
|
|
821
|
-
},
|
|
822
|
-
{
|
|
823
|
-
value: '600',
|
|
824
|
-
displayName: 'Semi Bold',
|
|
825
|
-
},
|
|
826
|
-
],
|
|
827
|
-
},
|
|
828
|
-
displayName: 'Font Weight',
|
|
829
|
-
type: 'Text',
|
|
830
|
-
group: 'style',
|
|
831
|
-
description: 'The font weight of the element',
|
|
832
|
-
defaultValue: '400',
|
|
833
|
-
},
|
|
834
|
-
cfImageAsset: {
|
|
835
|
-
displayName: 'Image',
|
|
836
|
-
type: 'Media',
|
|
837
|
-
description: 'Image to display',
|
|
838
|
-
},
|
|
839
|
-
cfImageOptions: {
|
|
840
|
-
displayName: 'Image options',
|
|
841
|
-
type: 'Object',
|
|
569
|
+
cfImageOptions: {
|
|
570
|
+
displayName: 'Image options',
|
|
571
|
+
type: 'Object',
|
|
842
572
|
group: 'style',
|
|
843
573
|
defaultValue: {
|
|
844
574
|
width: DEFAULT_IMAGE_WIDTH,
|
|
@@ -1189,6 +919,54 @@ const columnsBuiltInStyles = {
|
|
|
1189
919
|
},
|
|
1190
920
|
};
|
|
1191
921
|
|
|
922
|
+
const sectionDefinition = {
|
|
923
|
+
id: CONTENTFUL_COMPONENTS.section.id,
|
|
924
|
+
name: CONTENTFUL_COMPONENTS.section.name,
|
|
925
|
+
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
926
|
+
children: true,
|
|
927
|
+
variables: sectionBuiltInStyles,
|
|
928
|
+
tooltip: {
|
|
929
|
+
description: 'Create a new full width section of your experience by dragging this component onto the canvas. Other components and patterns can be added into a section.',
|
|
930
|
+
},
|
|
931
|
+
};
|
|
932
|
+
const containerDefinition = {
|
|
933
|
+
id: CONTENTFUL_COMPONENTS.container.id,
|
|
934
|
+
name: CONTENTFUL_COMPONENTS.container.name,
|
|
935
|
+
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
936
|
+
children: true,
|
|
937
|
+
variables: containerBuiltInStyles,
|
|
938
|
+
tooltip: {
|
|
939
|
+
description: 'Create a new area or pattern within your page layout by dragging a container onto the canvas. Other components and patterns can be added into a container.',
|
|
940
|
+
},
|
|
941
|
+
};
|
|
942
|
+
const columnsDefinition = {
|
|
943
|
+
id: CONTENTFUL_COMPONENTS.columns.id,
|
|
944
|
+
name: CONTENTFUL_COMPONENTS.columns.name,
|
|
945
|
+
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
946
|
+
children: true,
|
|
947
|
+
variables: columnsBuiltInStyles,
|
|
948
|
+
tooltip: {
|
|
949
|
+
description: 'Add columns to a container to create your desired layout and ensure that the experience is responsive across different screen sizes.',
|
|
950
|
+
},
|
|
951
|
+
};
|
|
952
|
+
const singleColumnDefinition = {
|
|
953
|
+
id: CONTENTFUL_COMPONENTS.singleColumn.id,
|
|
954
|
+
name: CONTENTFUL_COMPONENTS.singleColumn.name,
|
|
955
|
+
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
956
|
+
children: true,
|
|
957
|
+
variables: singleColumnBuiltInStyles,
|
|
958
|
+
};
|
|
959
|
+
const dividerDefinition = {
|
|
960
|
+
id: CONTENTFUL_COMPONENTS.divider.id,
|
|
961
|
+
name: CONTENTFUL_COMPONENTS.divider.name,
|
|
962
|
+
category: CONTENTFUL_DEFAULT_CATEGORY,
|
|
963
|
+
children: false,
|
|
964
|
+
variables: dividerBuiltInStyles,
|
|
965
|
+
tooltip: {
|
|
966
|
+
description: 'Drop onto the canvas to add a divider.',
|
|
967
|
+
},
|
|
968
|
+
};
|
|
969
|
+
|
|
1192
970
|
let designTokensRegistry = {};
|
|
1193
971
|
// This function is used to ensure that the composite values are valid since composite values are optional.
|
|
1194
972
|
// Therefore only border and in the future text related design tokens are/will be checked in this funciton.
|
|
@@ -1538,79 +1316,833 @@ const convertTooBig = (issue) => {
|
|
|
1538
1316
|
max: issue.maximum,
|
|
1539
1317
|
};
|
|
1540
1318
|
};
|
|
1541
|
-
const convertTooSmall = (issue) => {
|
|
1542
|
-
return {
|
|
1543
|
-
details: issue.message || `Size should be at least ${issue.minimum}`,
|
|
1544
|
-
name: CodeNames.Size,
|
|
1545
|
-
path: issue.path,
|
|
1546
|
-
min: issue.minimum,
|
|
1547
|
-
};
|
|
1319
|
+
const convertTooSmall = (issue) => {
|
|
1320
|
+
return {
|
|
1321
|
+
details: issue.message || `Size should be at least ${issue.minimum}`,
|
|
1322
|
+
name: CodeNames.Size,
|
|
1323
|
+
path: issue.path,
|
|
1324
|
+
min: issue.minimum,
|
|
1325
|
+
};
|
|
1326
|
+
};
|
|
1327
|
+
const defaultConversion = (issue) => {
|
|
1328
|
+
return {
|
|
1329
|
+
details: issue.message || 'An unexpected error occurred',
|
|
1330
|
+
name: CodeNames.Custom,
|
|
1331
|
+
path: issue.path.map(String),
|
|
1332
|
+
};
|
|
1333
|
+
};
|
|
1334
|
+
const zodToContentfulError = (issue) => {
|
|
1335
|
+
switch (issue.code) {
|
|
1336
|
+
case ZodIssueCode.invalid_type:
|
|
1337
|
+
return convertInvalidType(issue);
|
|
1338
|
+
case ZodIssueCode.unrecognized_keys:
|
|
1339
|
+
return convertUnrecognizedKeys(issue);
|
|
1340
|
+
case ZodIssueCode.invalid_enum_value:
|
|
1341
|
+
return convertInvalidEnumValue(issue);
|
|
1342
|
+
case ZodIssueCode.invalid_string:
|
|
1343
|
+
return convertInvalidString(issue);
|
|
1344
|
+
case ZodIssueCode.too_small:
|
|
1345
|
+
return convertTooSmall(issue);
|
|
1346
|
+
case ZodIssueCode.too_big:
|
|
1347
|
+
return convertTooBig(issue);
|
|
1348
|
+
case ZodIssueCode.invalid_literal:
|
|
1349
|
+
return convertInvalidLiteral(issue);
|
|
1350
|
+
default:
|
|
1351
|
+
return defaultConversion(issue);
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
const validateBreakpointsDefinition = (breakpoints) => {
|
|
1355
|
+
const result = z
|
|
1356
|
+
.array(BreakpointSchema)
|
|
1357
|
+
.superRefine(breakpointsRefinement)
|
|
1358
|
+
.safeParse(breakpoints);
|
|
1359
|
+
if (!result.success) {
|
|
1360
|
+
return {
|
|
1361
|
+
success: false,
|
|
1362
|
+
errors: result.error.issues.map(zodToContentfulError),
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
return { success: true };
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
let breakpointsRegistry = [];
|
|
1369
|
+
/**
|
|
1370
|
+
* Register custom breakpoints
|
|
1371
|
+
* @param breakpoints - [{[key:string]: string}]
|
|
1372
|
+
* @returns void
|
|
1373
|
+
*/
|
|
1374
|
+
const defineBreakpoints = (breakpoints) => {
|
|
1375
|
+
Object.assign(breakpointsRegistry, breakpoints);
|
|
1376
|
+
};
|
|
1377
|
+
const runBreakpointsValidation = () => {
|
|
1378
|
+
if (!breakpointsRegistry.length)
|
|
1379
|
+
return;
|
|
1380
|
+
const validation = validateBreakpointsDefinition(breakpointsRegistry);
|
|
1381
|
+
if (!validation.success) {
|
|
1382
|
+
throw new Error(`Invalid breakpoints definition. Failed with errors: \n${JSON.stringify(validation.errors, null, 2)}`);
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
// Used in the tests to get a breakpoint registration
|
|
1386
|
+
const getBreakpointRegistration = (id) => breakpointsRegistry.find((breakpoint) => breakpoint.id === id);
|
|
1387
|
+
// Used in the tests to reset the registry
|
|
1388
|
+
const resetBreakpointsRegistry = () => {
|
|
1389
|
+
breakpointsRegistry = [];
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
const detachExperienceStyles = (experience) => {
|
|
1393
|
+
const experienceTreeRoot = experience.entityStore?.experienceEntryFields
|
|
1394
|
+
?.componentTree;
|
|
1395
|
+
if (!experienceTreeRoot) {
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const mapOfDesignVariableKeys = flattenDesignTokenRegistry(designTokensRegistry);
|
|
1399
|
+
// getting breakpoints from the entry componentTree field
|
|
1400
|
+
/**
|
|
1401
|
+
* breakpoints [
|
|
1402
|
+
{
|
|
1403
|
+
id: 'desktop',
|
|
1404
|
+
query: '*',
|
|
1405
|
+
displayName: 'All Sizes',
|
|
1406
|
+
previewSize: '100%'
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
id: 'tablet',
|
|
1410
|
+
query: '<992px',
|
|
1411
|
+
displayName: 'Tablet',
|
|
1412
|
+
previewSize: '820px'
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
id: 'mobile',
|
|
1416
|
+
query: '<576px',
|
|
1417
|
+
displayName: 'Mobile',
|
|
1418
|
+
previewSize: '390px'
|
|
1419
|
+
}
|
|
1420
|
+
]
|
|
1421
|
+
*/
|
|
1422
|
+
const { breakpoints } = experienceTreeRoot;
|
|
1423
|
+
// creating the structure which I thought would work best for aggregation
|
|
1424
|
+
const mediaQueriesTemplate = breakpoints.reduce((mediaQueryTemplate, breakpoint) => {
|
|
1425
|
+
return {
|
|
1426
|
+
...mediaQueryTemplate,
|
|
1427
|
+
[breakpoint.id]: {
|
|
1428
|
+
condition: breakpoint.query,
|
|
1429
|
+
cssByClassName: {},
|
|
1430
|
+
},
|
|
1431
|
+
};
|
|
1432
|
+
}, {});
|
|
1433
|
+
// getting the breakpoint ids
|
|
1434
|
+
const breakpointIds = Object.keys(mediaQueriesTemplate);
|
|
1435
|
+
const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, }) => {
|
|
1436
|
+
// traversing the tree
|
|
1437
|
+
const queue = [];
|
|
1438
|
+
queue.push(...componentTree.children);
|
|
1439
|
+
let currentNode = undefined;
|
|
1440
|
+
// for each tree node
|
|
1441
|
+
while (queue.length) {
|
|
1442
|
+
currentNode = queue.shift();
|
|
1443
|
+
if (!currentNode) {
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
const usedComponents = experience.entityStore?.experienceEntryFields?.usedComponents ?? [];
|
|
1447
|
+
const isPatternNode = checkIsAssemblyNode({
|
|
1448
|
+
componentId: currentNode.definitionId,
|
|
1449
|
+
usedComponents,
|
|
1450
|
+
});
|
|
1451
|
+
if (isPatternNode) {
|
|
1452
|
+
const patternEntry = usedComponents.find((component) => component.sys.id === currentNode.definitionId);
|
|
1453
|
+
if (!patternEntry || !('fields' in patternEntry)) {
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
const defaultPatternDivStyles = Object.fromEntries(Object.entries(buildCfStyles({}))
|
|
1457
|
+
.filter(([, value]) => value !== undefined)
|
|
1458
|
+
.map(([key, value]) => [toCSSAttribute(key), value]));
|
|
1459
|
+
// I create a hash of the object above because that would ensure hash stability
|
|
1460
|
+
const styleHash = md5(JSON.stringify(defaultPatternDivStyles));
|
|
1461
|
+
// and prefix the className to make sure the value can be processed
|
|
1462
|
+
const className = `cf-${styleHash}`;
|
|
1463
|
+
for (const breakpointId of breakpointIds) {
|
|
1464
|
+
if (!mediaQueriesTemplate[breakpointId].cssByClassName[className]) {
|
|
1465
|
+
mediaQueriesTemplate[breakpointId].cssByClassName[className] =
|
|
1466
|
+
toCSSString(defaultPatternDivStyles);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
currentNode.variables.cfSsrClassName = {
|
|
1470
|
+
type: 'DesignValue',
|
|
1471
|
+
valuesByBreakpoint: {
|
|
1472
|
+
[breakpointIds[0]]: className,
|
|
1473
|
+
},
|
|
1474
|
+
};
|
|
1475
|
+
// the node of a used pattern contains only the definitionId (id of the patter entry)
|
|
1476
|
+
// as well as the variables overwrites
|
|
1477
|
+
// the layout of a pattern is stored in it's entry
|
|
1478
|
+
iterateOverTreeAndExtractStyles({
|
|
1479
|
+
// that is why we pass it here to iterate of the pattern tree
|
|
1480
|
+
componentTree: patternEntry.fields.componentTree,
|
|
1481
|
+
// but we pass the data source of the experience entry cause that's where the binding is stored
|
|
1482
|
+
dataSource,
|
|
1483
|
+
// unbound values of a pattern store the default values of pattern variables
|
|
1484
|
+
unboundValues: patternEntry.fields.unboundValues,
|
|
1485
|
+
// this is where we can map the pattern variable to it's default value
|
|
1486
|
+
componentSettings: patternEntry.fields.componentSettings,
|
|
1487
|
+
// and this is where the over-writes for the default values are stored
|
|
1488
|
+
// yes, I know, it's a bit confusing
|
|
1489
|
+
componentVariablesOverwrites: currentNode.variables,
|
|
1490
|
+
});
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
/** Variables value is stored in `valuesByBreakpoint` object
|
|
1494
|
+
* {
|
|
1495
|
+
cfVerticalAlignment: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'center' } },
|
|
1496
|
+
cfHorizontalAlignment: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'center' } },
|
|
1497
|
+
cfMargin: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0 0 0 0' } },
|
|
1498
|
+
cfPadding: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0 0 0 0' } },
|
|
1499
|
+
cfBackgroundColor: {
|
|
1500
|
+
type: 'DesignValue',
|
|
1501
|
+
valuesByBreakpoint: { desktop: 'rgba(246, 246, 246, 1)' }
|
|
1502
|
+
},
|
|
1503
|
+
cfWidth: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'fill' } },
|
|
1504
|
+
cfHeight: {
|
|
1505
|
+
type: 'DesignValue',
|
|
1506
|
+
valuesByBreakpoint: { desktop: 'fit-content' }
|
|
1507
|
+
},
|
|
1508
|
+
cfMaxWidth: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'none' } },
|
|
1509
|
+
cfFlexDirection: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'column' } },
|
|
1510
|
+
cfFlexWrap: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'nowrap' } },
|
|
1511
|
+
cfBorder: {
|
|
1512
|
+
type: 'DesignValue',
|
|
1513
|
+
valuesByBreakpoint: { desktop: '0px solid rgba(0, 0, 0, 0)' }
|
|
1514
|
+
},
|
|
1515
|
+
cfBorderRadius: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0px' } },
|
|
1516
|
+
cfGap: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0px 0px' } },
|
|
1517
|
+
cfHyperlink: { type: 'UnboundValue', key: 'VNc49Qyepd6IzN7rmKUyS' },
|
|
1518
|
+
cfOpenInNewTab: { type: 'UnboundValue', key: 'ZA5YqB2fmREQ4pTKqY5hX' },
|
|
1519
|
+
cfBackgroundImageUrl: { type: 'UnboundValue', key: 'FeskH0WbYD5_RQVXX-1T8' },
|
|
1520
|
+
cfBackgroundImageOptions: { type: 'DesignValue', valuesByBreakpoint: { desktop: [Object] } }
|
|
1521
|
+
}
|
|
1522
|
+
*/
|
|
1523
|
+
// so first, I convert it into a map to help me make it easier to access the values
|
|
1524
|
+
const propsByBreakpoint = indexByBreakpoint({
|
|
1525
|
+
variables: currentNode.variables,
|
|
1526
|
+
breakpointIds,
|
|
1527
|
+
unboundValues: unboundValues,
|
|
1528
|
+
dataSource: dataSource,
|
|
1529
|
+
componentSettings,
|
|
1530
|
+
componentVariablesOverwrites,
|
|
1531
|
+
getBoundEntityById: (id) => {
|
|
1532
|
+
return experience.entityStore?.entities.find((entity) => entity.sys.id === id);
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
/**
|
|
1536
|
+
* propsByBreakpoint {
|
|
1537
|
+
desktop: {
|
|
1538
|
+
cfVerticalAlignment: 'center',
|
|
1539
|
+
cfHorizontalAlignment: 'center',
|
|
1540
|
+
cfMargin: '0 0 0 0',
|
|
1541
|
+
cfPadding: '0 0 0 0',
|
|
1542
|
+
cfBackgroundColor: 'rgba(246, 246, 246, 1)',
|
|
1543
|
+
cfWidth: 'fill',
|
|
1544
|
+
cfHeight: 'fit-content',
|
|
1545
|
+
cfMaxWidth: 'none',
|
|
1546
|
+
cfFlexDirection: 'column',
|
|
1547
|
+
cfFlexWrap: 'nowrap',
|
|
1548
|
+
cfBorder: '0px solid rgba(0, 0, 0, 0)',
|
|
1549
|
+
cfBorderRadius: '0px',
|
|
1550
|
+
cfGap: '0px 0px',
|
|
1551
|
+
cfBackgroundImageOptions: { scaling: 'fill', alignment: 'left top', targetSize: '2000px' }
|
|
1552
|
+
},
|
|
1553
|
+
tablet: {},
|
|
1554
|
+
mobile: {}
|
|
1555
|
+
}
|
|
1556
|
+
*/
|
|
1557
|
+
const currentNodeClassNames = [];
|
|
1558
|
+
// then for each breakpoint
|
|
1559
|
+
for (const breakpointId of breakpointIds) {
|
|
1560
|
+
const propsByBreakpointWithResolvedDesignTokens = Object.entries(propsByBreakpoint[breakpointId]).reduce((acc, [variableName, variableValue]) => {
|
|
1561
|
+
return {
|
|
1562
|
+
...acc,
|
|
1563
|
+
[variableName]: maybePopulateDesignTokenValue(variableName, variableValue, mapOfDesignVariableKeys),
|
|
1564
|
+
};
|
|
1565
|
+
}, {});
|
|
1566
|
+
// We convert cryptic prop keys to css variables
|
|
1567
|
+
// Eg: cfMargin to margin
|
|
1568
|
+
const stylesForBreakpoint = buildCfStyles(propsByBreakpointWithResolvedDesignTokens);
|
|
1569
|
+
const stylesForBreakpointWithoutUndefined = Object.fromEntries(Object.entries(stylesForBreakpoint)
|
|
1570
|
+
.filter(([, value]) => value !== undefined)
|
|
1571
|
+
.map(([key, value]) => [toCSSAttribute(key), value]));
|
|
1572
|
+
/**
|
|
1573
|
+
* stylesForBreakpoint {
|
|
1574
|
+
margin: '0 0 0 0',
|
|
1575
|
+
padding: '0 0 0 0',
|
|
1576
|
+
'background-color': 'rgba(246, 246, 246, 1)',
|
|
1577
|
+
width: '100%',
|
|
1578
|
+
height: 'fit-content',
|
|
1579
|
+
'max-width': 'none',
|
|
1580
|
+
border: '0px solid rgba(0, 0, 0, 0)',
|
|
1581
|
+
'border-radius': '0px',
|
|
1582
|
+
gap: '0px 0px',
|
|
1583
|
+
'align-items': 'center',
|
|
1584
|
+
'justify-content': 'safe center',
|
|
1585
|
+
'flex-direction': 'column',
|
|
1586
|
+
'flex-wrap': 'nowrap',
|
|
1587
|
+
'font-style': 'normal',
|
|
1588
|
+
'text-decoration': 'none',
|
|
1589
|
+
'box-sizing': 'border-box'
|
|
1590
|
+
}
|
|
1591
|
+
*/
|
|
1592
|
+
// I create a hash of the object above because that would ensure hash stability
|
|
1593
|
+
const styleHash = md5(JSON.stringify(stylesForBreakpointWithoutUndefined));
|
|
1594
|
+
// and prefix the className to make sure the value can be processed
|
|
1595
|
+
const className = `cf-${styleHash}`;
|
|
1596
|
+
// I save the generated hashes into an array to later save it in the tree node
|
|
1597
|
+
// as cfSsrClassName prop
|
|
1598
|
+
// making sure to avoid the duplicates in case styles for > 1 breakpoints are the same
|
|
1599
|
+
if (!currentNodeClassNames.includes(className)) {
|
|
1600
|
+
currentNodeClassNames.push(className);
|
|
1601
|
+
}
|
|
1602
|
+
// if there is already the similar hash - no need to over-write it
|
|
1603
|
+
if (mediaQueriesTemplate[breakpointId].cssByClassName[className]) {
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
// otherwise, save it to the stylesheet
|
|
1607
|
+
mediaQueriesTemplate[breakpointId].cssByClassName[className] = toCSSString(stylesForBreakpointWithoutUndefined);
|
|
1608
|
+
}
|
|
1609
|
+
// all generated classNames are saved in the tree node
|
|
1610
|
+
// to be handled by the sdk later
|
|
1611
|
+
// each node will get N classNames, where N is the number of breakpoints
|
|
1612
|
+
// browsers process classNames in the order they are defined
|
|
1613
|
+
// meaning that in case of className1 className2 className3
|
|
1614
|
+
// className3 will win over className2 and className1
|
|
1615
|
+
// making sure that we respect the order of breakpoints from
|
|
1616
|
+
// we can achieve "desktop first" or "mobile first" approach to style over-writes
|
|
1617
|
+
currentNode.variables.cfSsrClassName = {
|
|
1618
|
+
type: 'DesignValue',
|
|
1619
|
+
valuesByBreakpoint: {
|
|
1620
|
+
[breakpointIds[0]]: currentNodeClassNames.join(' '),
|
|
1621
|
+
},
|
|
1622
|
+
};
|
|
1623
|
+
queue.push(...currentNode.children);
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
iterateOverTreeAndExtractStyles({
|
|
1627
|
+
componentTree: experienceTreeRoot,
|
|
1628
|
+
dataSource: experience.entityStore?.dataSource ?? {},
|
|
1629
|
+
unboundValues: experience.entityStore?.unboundValues ?? {},
|
|
1630
|
+
componentSettings: experience.entityStore?.experienceEntryFields?.componentSettings,
|
|
1631
|
+
});
|
|
1632
|
+
// once the whole tree was traversed, for each breakpoint, I aggregate the styles
|
|
1633
|
+
// for each generated className into one css string
|
|
1634
|
+
const styleSheet = Object.entries(mediaQueriesTemplate).reduce((acc, [, breakpointPayload]) => {
|
|
1635
|
+
return `${acc}${toMediaQuery(breakpointPayload)}`;
|
|
1636
|
+
}, '');
|
|
1637
|
+
return styleSheet;
|
|
1638
|
+
};
|
|
1639
|
+
const isCfStyleAttribute = (variableName) => {
|
|
1640
|
+
return CF_STYLE_ATTRIBUTES.includes(variableName);
|
|
1641
|
+
};
|
|
1642
|
+
const maybePopulateDesignTokenValue = (variableName, variableValue, mapOfDesignVariableKeys) => {
|
|
1643
|
+
// TODO: refactor to reuse fn from core package
|
|
1644
|
+
if (typeof variableValue !== 'string') {
|
|
1645
|
+
return variableValue;
|
|
1646
|
+
}
|
|
1647
|
+
if (!isCfStyleAttribute(variableName)) {
|
|
1648
|
+
return variableValue;
|
|
1649
|
+
}
|
|
1650
|
+
const resolveSimpleDesignToken = (variableName, variableValue) => {
|
|
1651
|
+
const nonTemplateDesignTokenValue = variableValue.replace(templateStringRegex, '$1');
|
|
1652
|
+
const tokenValue = mapOfDesignVariableKeys[nonTemplateDesignTokenValue];
|
|
1653
|
+
if (!tokenValue) {
|
|
1654
|
+
if (builtInStyles[variableName]) {
|
|
1655
|
+
return builtInStyles[variableName].defaultValue;
|
|
1656
|
+
}
|
|
1657
|
+
if (optionalBuiltInStyles[variableName]) {
|
|
1658
|
+
return optionalBuiltInStyles[variableName].defaultValue;
|
|
1659
|
+
}
|
|
1660
|
+
return '0px';
|
|
1661
|
+
}
|
|
1662
|
+
if (variableName === 'cfBorder' || variableName.startsWith('cfBorder_')) {
|
|
1663
|
+
if (typeof tokenValue === 'object') {
|
|
1664
|
+
const { width, style, color } = tokenValue;
|
|
1665
|
+
return `${width} ${style} ${color}`;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
return tokenValue;
|
|
1669
|
+
};
|
|
1670
|
+
const templateStringRegex = /\${(.+?)}/g;
|
|
1671
|
+
const parts = variableValue.split(' ');
|
|
1672
|
+
let resolvedValue = '';
|
|
1673
|
+
for (const part of parts) {
|
|
1674
|
+
const tokenValue = templateStringRegex.test(part)
|
|
1675
|
+
? resolveSimpleDesignToken(variableName, part)
|
|
1676
|
+
: part;
|
|
1677
|
+
resolvedValue += `${tokenValue} `;
|
|
1678
|
+
}
|
|
1679
|
+
// Not trimming would end up with a trailing space that breaks the check in `calculateNodeDefaultHeight`
|
|
1680
|
+
return resolvedValue.trim();
|
|
1681
|
+
};
|
|
1682
|
+
const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataSource = {}, unboundValues = {}, componentVariablesOverwrites, componentSettings = { variableDefinitions: {} }, }) => {
|
|
1683
|
+
if (variableData.type === 'UnboundValue') {
|
|
1684
|
+
const uuid = variableData.key;
|
|
1685
|
+
return unboundValues[uuid]?.value;
|
|
1686
|
+
}
|
|
1687
|
+
if (variableData.type === 'ComponentValue') {
|
|
1688
|
+
const variableDefinitionKey = variableData.key;
|
|
1689
|
+
const variableDefinition = componentSettings.variableDefinitions[variableDefinitionKey];
|
|
1690
|
+
// @ts-expect-error TODO: fix the types as it thinks taht `defaultValue` is of type string
|
|
1691
|
+
const defaultValueKey = variableDefinition.defaultValue?.key;
|
|
1692
|
+
const defaultValue = unboundValues[defaultValueKey].value;
|
|
1693
|
+
const userSetValue = componentVariablesOverwrites?.[variableDefinitionKey];
|
|
1694
|
+
if (!userSetValue) {
|
|
1695
|
+
return defaultValue;
|
|
1696
|
+
}
|
|
1697
|
+
// at this point userSetValue will either be type of 'DesignValue' or 'BoundValue'
|
|
1698
|
+
// so we recursively run resolution again to resolve it
|
|
1699
|
+
const resolvedValue = resolveBackgroundImageBinding({
|
|
1700
|
+
variableData: userSetValue,
|
|
1701
|
+
getBoundEntityById,
|
|
1702
|
+
dataSource,
|
|
1703
|
+
unboundValues,
|
|
1704
|
+
componentVariablesOverwrites,
|
|
1705
|
+
componentSettings,
|
|
1706
|
+
});
|
|
1707
|
+
return resolvedValue || defaultValue;
|
|
1708
|
+
}
|
|
1709
|
+
if (variableData.type === 'BoundValue') {
|
|
1710
|
+
// '/lUERH7tX7nJTaPX6f0udB/fields/assetReference/~locale/fields/file/~locale'
|
|
1711
|
+
const [, uuid] = variableData.path.split('/');
|
|
1712
|
+
const binding = dataSource[uuid];
|
|
1713
|
+
const boundEntity = getBoundEntityById(binding.sys.id);
|
|
1714
|
+
if (!boundEntity) {
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (boundEntity.sys.type === 'Asset') {
|
|
1718
|
+
return boundEntity.fields.file?.url;
|
|
1719
|
+
}
|
|
1720
|
+
else {
|
|
1721
|
+
// '/lUERH7tX7nJTaPX6f0udB/fields/assetReference/~locale/fields/file/~locale'
|
|
1722
|
+
// becomes
|
|
1723
|
+
// '/fields/assetReference/~locale/fields/file/~locale'
|
|
1724
|
+
const pathWithoutUUID = variableData.path.split(uuid)[1];
|
|
1725
|
+
// '/fields/assetReference/~locale/fields/file/~locale'
|
|
1726
|
+
// becomes
|
|
1727
|
+
// '/fields/assetReference/'
|
|
1728
|
+
const pathToReferencedAsset = pathWithoutUUID.split('~locale')[0];
|
|
1729
|
+
// '/fields/assetReference/'
|
|
1730
|
+
// becomes
|
|
1731
|
+
// '[fields, assetReference]'
|
|
1732
|
+
const [, fieldName] = pathToReferencedAsset.substring(1).split('/') ?? undefined;
|
|
1733
|
+
const referenceToAsset = boundEntity.fields[fieldName];
|
|
1734
|
+
if (!referenceToAsset) {
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
if (referenceToAsset.sys?.linkType === 'Asset') {
|
|
1738
|
+
const referencedAsset = getBoundEntityById(referenceToAsset.sys.id);
|
|
1739
|
+
if (!referencedAsset) {
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
return referencedAsset.fields.file?.url;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unboundValues = {}, dataSource = {}, componentVariablesOverwrites, componentSettings = { variableDefinitions: {} }, }) => {
|
|
1748
|
+
const variableValuesByBreakpoints = breakpointIds.reduce((acc, breakpointId) => {
|
|
1749
|
+
return {
|
|
1750
|
+
...acc,
|
|
1751
|
+
[breakpointId]: {},
|
|
1752
|
+
};
|
|
1753
|
+
}, {});
|
|
1754
|
+
const defaultBreakpoint = breakpointIds[0];
|
|
1755
|
+
for (const [variableName, variableData] of Object.entries(variables)) {
|
|
1756
|
+
// handling the special case - cfBackgroundImageUrl variable, which can be bound or unbound
|
|
1757
|
+
// so, we need to resolve it here and pass it down as a css property to be convereted into the CSS
|
|
1758
|
+
// I used .startsWith() cause it can be part of a pattern node
|
|
1759
|
+
if (variableName === 'cfBackgroundImageUrl' ||
|
|
1760
|
+
variableName.startsWith('cfBackgroundImageUrl_')) {
|
|
1761
|
+
const imageUrl = resolveBackgroundImageBinding({
|
|
1762
|
+
variableData,
|
|
1763
|
+
getBoundEntityById,
|
|
1764
|
+
unboundValues,
|
|
1765
|
+
dataSource,
|
|
1766
|
+
componentSettings,
|
|
1767
|
+
componentVariablesOverwrites,
|
|
1768
|
+
});
|
|
1769
|
+
if (imageUrl) {
|
|
1770
|
+
variableValuesByBreakpoints[defaultBreakpoint][variableName] = imageUrl;
|
|
1771
|
+
}
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (variableData.type !== 'DesignValue') {
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
for (const [breakpointId, variableValue] of Object.entries(variableData.valuesByBreakpoint)) {
|
|
1778
|
+
if (!variableValue) {
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
variableValuesByBreakpoints[breakpointId] = {
|
|
1782
|
+
...variableValuesByBreakpoints[breakpointId],
|
|
1783
|
+
[variableName]: variableValue,
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return variableValuesByBreakpoints;
|
|
1788
|
+
};
|
|
1789
|
+
/**
|
|
1790
|
+
* Flattens the object from
|
|
1791
|
+
* {
|
|
1792
|
+
* color: {
|
|
1793
|
+
* [key]: [value]
|
|
1794
|
+
* }
|
|
1795
|
+
* }
|
|
1796
|
+
*
|
|
1797
|
+
* to
|
|
1798
|
+
*
|
|
1799
|
+
* {
|
|
1800
|
+
* 'color.key': [value]
|
|
1801
|
+
* }
|
|
1802
|
+
*/
|
|
1803
|
+
const flattenDesignTokenRegistry = (designTokenRegistry) => {
|
|
1804
|
+
return Object.entries(designTokenRegistry).reduce((acc, [categoryName, tokenCategory]) => {
|
|
1805
|
+
const tokensWithCategory = Object.entries(tokenCategory).reduce((acc, [tokenName, tokenValue]) => {
|
|
1806
|
+
return {
|
|
1807
|
+
...acc,
|
|
1808
|
+
[`${categoryName}.${tokenName}`]: tokenValue,
|
|
1809
|
+
};
|
|
1810
|
+
}, {});
|
|
1811
|
+
return {
|
|
1812
|
+
...acc,
|
|
1813
|
+
...tokensWithCategory,
|
|
1814
|
+
};
|
|
1815
|
+
}, {});
|
|
1816
|
+
};
|
|
1817
|
+
// Replaces camelCase with kebab-case
|
|
1818
|
+
// converts the <key, value> object into a css string
|
|
1819
|
+
const toCSSString = (breakpointStyles) => {
|
|
1820
|
+
return Object.entries(breakpointStyles)
|
|
1821
|
+
.map(([key, value]) => `${key}:${value};`)
|
|
1822
|
+
.join('');
|
|
1823
|
+
};
|
|
1824
|
+
const toMediaQuery = (breakpointPayload) => {
|
|
1825
|
+
const mediaQueryStyles = Object.entries(breakpointPayload.cssByClassName).reduce((acc, [className, css]) => {
|
|
1826
|
+
return `${acc}.${className}{${css}}`;
|
|
1827
|
+
}, ``);
|
|
1828
|
+
if (breakpointPayload.condition === '*') {
|
|
1829
|
+
return mediaQueryStyles;
|
|
1830
|
+
}
|
|
1831
|
+
const [evaluation, pixelValue] = [
|
|
1832
|
+
breakpointPayload.condition[0],
|
|
1833
|
+
breakpointPayload.condition.substring(1),
|
|
1834
|
+
];
|
|
1835
|
+
const mediaQueryRule = evaluation === '<' ? 'max-width' : 'min-width';
|
|
1836
|
+
return `@media(${mediaQueryRule}:${pixelValue}){${mediaQueryStyles}}`;
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1840
|
+
function get(obj, path) {
|
|
1841
|
+
if (!path.length) {
|
|
1842
|
+
return obj;
|
|
1843
|
+
}
|
|
1844
|
+
try {
|
|
1845
|
+
const [currentPath, ...nextPath] = path;
|
|
1846
|
+
return get(obj[currentPath], nextPath);
|
|
1847
|
+
}
|
|
1848
|
+
catch (err) {
|
|
1849
|
+
return undefined;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
const getBoundValue = (entryOrAsset, path) => {
|
|
1854
|
+
const value = get(entryOrAsset, path.split('/').slice(2, -1));
|
|
1855
|
+
return value && typeof value == 'object' && value.url
|
|
1856
|
+
? value.url
|
|
1857
|
+
: value;
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
const transformRichText = (entryOrAsset, path) => {
|
|
1861
|
+
const value = getBoundValue(entryOrAsset, path);
|
|
1862
|
+
if (typeof value === 'string') {
|
|
1863
|
+
return {
|
|
1864
|
+
data: {},
|
|
1865
|
+
content: [
|
|
1866
|
+
{
|
|
1867
|
+
nodeType: BLOCKS.PARAGRAPH,
|
|
1868
|
+
data: {},
|
|
1869
|
+
content: [
|
|
1870
|
+
{
|
|
1871
|
+
data: {},
|
|
1872
|
+
nodeType: 'text',
|
|
1873
|
+
value: value,
|
|
1874
|
+
marks: [],
|
|
1875
|
+
},
|
|
1876
|
+
],
|
|
1877
|
+
},
|
|
1878
|
+
],
|
|
1879
|
+
nodeType: BLOCKS.DOCUMENT,
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
|
|
1883
|
+
return value;
|
|
1884
|
+
}
|
|
1885
|
+
return undefined;
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1888
|
+
function getOptimizedImageUrl(url, width, quality, format) {
|
|
1889
|
+
if (url.startsWith('//')) {
|
|
1890
|
+
url = 'https:' + url;
|
|
1891
|
+
}
|
|
1892
|
+
const params = new URLSearchParams();
|
|
1893
|
+
if (width) {
|
|
1894
|
+
params.append('w', width.toString());
|
|
1895
|
+
}
|
|
1896
|
+
if (quality && quality > 0 && quality < 100) {
|
|
1897
|
+
params.append('q', quality.toString());
|
|
1898
|
+
}
|
|
1899
|
+
if (format) {
|
|
1900
|
+
params.append('fm', format);
|
|
1901
|
+
}
|
|
1902
|
+
const queryString = params.toString();
|
|
1903
|
+
return `${url}${queryString ? '?' + queryString : ''}`;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function validateParams(file, quality, format) {
|
|
1907
|
+
if (!file.details.image) {
|
|
1908
|
+
throw Error('No image in file asset to transform');
|
|
1909
|
+
}
|
|
1910
|
+
if (quality < 0 || quality > 100) {
|
|
1911
|
+
throw Error('Quality must be between 0 and 100');
|
|
1912
|
+
}
|
|
1913
|
+
if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
|
|
1914
|
+
throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
|
|
1915
|
+
}
|
|
1916
|
+
return true;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
const MAX_WIDTH_ALLOWED$1 = 2000;
|
|
1920
|
+
const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', format) => {
|
|
1921
|
+
const qualityNumber = Number(quality.replace('%', ''));
|
|
1922
|
+
if (!validateParams(file, qualityNumber, format)) ;
|
|
1923
|
+
if (!validateParams(file, qualityNumber, format)) ;
|
|
1924
|
+
const url = file.url;
|
|
1925
|
+
const { width1x, width2x } = getWidths(widthStyle, file);
|
|
1926
|
+
const imageUrl1x = getOptimizedImageUrl(url, width1x, qualityNumber, format);
|
|
1927
|
+
const imageUrl2x = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
1928
|
+
const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
|
|
1929
|
+
const returnedUrl = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
1930
|
+
const optimizedBackgroundImageAsset = {
|
|
1931
|
+
url: returnedUrl,
|
|
1932
|
+
srcSet,
|
|
1933
|
+
file,
|
|
1934
|
+
};
|
|
1935
|
+
return optimizedBackgroundImageAsset;
|
|
1936
|
+
function getWidths(widthStyle, file) {
|
|
1937
|
+
let width1x = 0;
|
|
1938
|
+
let width2x = 0;
|
|
1939
|
+
const intrinsicImageWidth = file.details.image.width;
|
|
1940
|
+
if (widthStyle.endsWith('px')) {
|
|
1941
|
+
width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
|
|
1942
|
+
}
|
|
1943
|
+
else {
|
|
1944
|
+
width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
|
|
1945
|
+
}
|
|
1946
|
+
width2x = Math.min(width1x * 2, intrinsicImageWidth);
|
|
1947
|
+
return { width1x, width2x };
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
const MAX_WIDTH_ALLOWED = 4000;
|
|
1952
|
+
const getOptimizedImageAsset = ({ file, sizes, loading, quality = '100%', format, }) => {
|
|
1953
|
+
const qualityNumber = Number(quality.replace('%', ''));
|
|
1954
|
+
if (!validateParams(file, qualityNumber, format)) ;
|
|
1955
|
+
const url = file.url;
|
|
1956
|
+
const maxWidth = Math.min(file.details.image.width, MAX_WIDTH_ALLOWED);
|
|
1957
|
+
const numOfParts = Math.max(2, Math.ceil(maxWidth / 500));
|
|
1958
|
+
const widthParts = Array.from({ length: numOfParts }, (_, index) => Math.ceil((index + 1) * (maxWidth / numOfParts)));
|
|
1959
|
+
const srcSet = sizes
|
|
1960
|
+
? widthParts.map((width) => `${getOptimizedImageUrl(url, width, qualityNumber, format)} ${width}w`)
|
|
1961
|
+
: [];
|
|
1962
|
+
const intrinsicImageWidth = file.details.image.width;
|
|
1963
|
+
if (intrinsicImageWidth > MAX_WIDTH_ALLOWED) {
|
|
1964
|
+
srcSet.push(`${getOptimizedImageUrl(url, undefined, qualityNumber, format)} ${intrinsicImageWidth}w`);
|
|
1965
|
+
}
|
|
1966
|
+
const returnedUrl = getOptimizedImageUrl(url, file.details.image.width > 2000 ? 2000 : undefined, qualityNumber, format);
|
|
1967
|
+
const optimizedImageAsset = {
|
|
1968
|
+
url: returnedUrl,
|
|
1969
|
+
srcSet,
|
|
1970
|
+
sizes,
|
|
1971
|
+
file,
|
|
1972
|
+
loading,
|
|
1973
|
+
};
|
|
1974
|
+
return optimizedImageAsset;
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1977
|
+
const transformMedia = (asset, variables, resolveDesignValue, variableName, path) => {
|
|
1978
|
+
let value;
|
|
1979
|
+
// If it is not a deep path and not pointing to the file of the asset,
|
|
1980
|
+
// it is just pointing to a normal field and therefore we just resolve the value as normal field
|
|
1981
|
+
if (!isDeepPath(path) && !lastPathNamedSegmentEq(path, 'file')) {
|
|
1982
|
+
return getBoundValue(asset, path);
|
|
1983
|
+
}
|
|
1984
|
+
//TODO: this will be better served by injectable type transformers instead of if statement
|
|
1985
|
+
if (variableName === 'cfImageAsset') {
|
|
1986
|
+
const optionsVariableName = 'cfImageOptions';
|
|
1987
|
+
const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
|
|
1988
|
+
? variables[optionsVariableName].valuesByBreakpoint
|
|
1989
|
+
: {}, optionsVariableName);
|
|
1990
|
+
if (!options) {
|
|
1991
|
+
console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
try {
|
|
1995
|
+
value = getOptimizedImageAsset({
|
|
1996
|
+
file: asset.fields.file,
|
|
1997
|
+
loading: options.loading,
|
|
1998
|
+
sizes: options.targetSize,
|
|
1999
|
+
quality: options.quality,
|
|
2000
|
+
format: options.format,
|
|
2001
|
+
});
|
|
2002
|
+
return value;
|
|
2003
|
+
}
|
|
2004
|
+
catch (error) {
|
|
2005
|
+
console.error('Error transforming image asset', error);
|
|
2006
|
+
}
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
if (variableName === 'cfBackgroundImageUrl') {
|
|
2010
|
+
const width = resolveDesignValue(variables['cfWidth']?.type === 'DesignValue' ? variables['cfWidth'].valuesByBreakpoint : {}, 'cfWidth');
|
|
2011
|
+
const optionsVariableName = 'cfBackgroundImageOptions';
|
|
2012
|
+
const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
|
|
2013
|
+
? variables[optionsVariableName].valuesByBreakpoint
|
|
2014
|
+
: {}, optionsVariableName);
|
|
2015
|
+
if (!options) {
|
|
2016
|
+
console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
try {
|
|
2020
|
+
value = getOptimizedBackgroundImageAsset(asset.fields.file, width, options.quality, options.format);
|
|
2021
|
+
return value;
|
|
2022
|
+
}
|
|
2023
|
+
catch (error) {
|
|
2024
|
+
console.error('Error transforming image asset', error);
|
|
2025
|
+
}
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
return asset.fields.file?.url;
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2031
|
+
const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableDefinition, path) => {
|
|
2032
|
+
const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
|
|
2033
|
+
if (!entityOrAsset)
|
|
2034
|
+
return;
|
|
2035
|
+
switch (variableDefinition.type) {
|
|
2036
|
+
case 'Media':
|
|
2037
|
+
// If we bound a normal entry field to the media veriable we just return the bound value
|
|
2038
|
+
if (entityOrAsset.sys.type === 'Entry') {
|
|
2039
|
+
return getBoundValue(entityOrAsset, path);
|
|
2040
|
+
}
|
|
2041
|
+
return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
|
|
2042
|
+
case 'RichText':
|
|
2043
|
+
return transformRichText(entityOrAsset, path);
|
|
2044
|
+
default:
|
|
2045
|
+
return getBoundValue(entityOrAsset, path);
|
|
2046
|
+
}
|
|
1548
2047
|
};
|
|
1549
|
-
|
|
2048
|
+
|
|
2049
|
+
const getDataFromTree = (tree) => {
|
|
2050
|
+
let dataSource = {};
|
|
2051
|
+
let unboundValues = {};
|
|
2052
|
+
const queue = [...tree.root.children];
|
|
2053
|
+
while (queue.length) {
|
|
2054
|
+
const node = queue.shift();
|
|
2055
|
+
if (!node) {
|
|
2056
|
+
continue;
|
|
2057
|
+
}
|
|
2058
|
+
dataSource = { ...dataSource, ...node.data.dataSource };
|
|
2059
|
+
unboundValues = { ...unboundValues, ...node.data.unboundValues };
|
|
2060
|
+
if (node.children.length) {
|
|
2061
|
+
queue.push(...node.children);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
1550
2064
|
return {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
path: issue.path.map(String),
|
|
2065
|
+
dataSource,
|
|
2066
|
+
unboundValues,
|
|
1554
2067
|
};
|
|
1555
2068
|
};
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
default:
|
|
1573
|
-
return defaultConversion(issue);
|
|
2069
|
+
/**
|
|
2070
|
+
* Gets calculates the index to drop the dragged component based on the mouse position
|
|
2071
|
+
* @returns {InsertionData} a object containing a node that will become a parent for dragged component and index at which it must be inserted
|
|
2072
|
+
*/
|
|
2073
|
+
const getInsertionData = ({ dropReceiverParentNode, dropReceiverNode, flexDirection, isMouseAtTopBorder, isMouseAtBottomBorder, isMouseInLeftHalf, isMouseInUpperHalf, isOverTopIndicator, isOverBottomIndicator, }) => {
|
|
2074
|
+
const APPEND_INSIDE = dropReceiverNode.children.length;
|
|
2075
|
+
const PREPEND_INSIDE = 0;
|
|
2076
|
+
if (isMouseAtTopBorder || isMouseAtBottomBorder) {
|
|
2077
|
+
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2078
|
+
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2079
|
+
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
2080
|
+
return {
|
|
2081
|
+
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2082
|
+
node: dropReceiverParentNode,
|
|
2083
|
+
index: isMouseAtBottomBorder ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2084
|
+
};
|
|
1574
2085
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
.safeParse(breakpoints);
|
|
1581
|
-
if (!result.success) {
|
|
2086
|
+
// if over one of the section indicators
|
|
2087
|
+
if (isOverTopIndicator || isOverBottomIndicator) {
|
|
2088
|
+
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2089
|
+
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2090
|
+
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
1582
2091
|
return {
|
|
1583
|
-
|
|
1584
|
-
|
|
2092
|
+
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2093
|
+
node: dropReceiverParentNode,
|
|
2094
|
+
index: isOverBottomIndicator ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
if (flexDirection === undefined || flexDirection === 'row') {
|
|
2098
|
+
return {
|
|
2099
|
+
node: dropReceiverNode,
|
|
2100
|
+
index: isMouseInLeftHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
else {
|
|
2104
|
+
return {
|
|
2105
|
+
node: dropReceiverNode,
|
|
2106
|
+
index: isMouseInUpperHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
1585
2107
|
};
|
|
1586
2108
|
}
|
|
1587
|
-
return { success: true };
|
|
1588
2109
|
};
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
*
|
|
1595
|
-
|
|
1596
|
-
const defineBreakpoints = (breakpoints) => {
|
|
1597
|
-
Object.assign(breakpointsRegistry, breakpoints);
|
|
2110
|
+
const generateRandomId = (letterCount) => {
|
|
2111
|
+
const LETTERS = 'abcdefghijklmnopqvwxyzABCDEFGHIJKLMNOPQVWXYZ';
|
|
2112
|
+
const NUMS = '0123456789';
|
|
2113
|
+
const ALNUM = NUMS + LETTERS;
|
|
2114
|
+
const times = (n, callback) => Array.from({ length: n }, callback);
|
|
2115
|
+
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
2116
|
+
return times(letterCount, () => ALNUM[random(0, ALNUM.length - 1)]).join('');
|
|
1598
2117
|
};
|
|
1599
|
-
const
|
|
1600
|
-
if (!
|
|
1601
|
-
return;
|
|
1602
|
-
|
|
1603
|
-
if (!validation.success) {
|
|
1604
|
-
throw new Error(`Invalid breakpoints definition. Failed with errors: \n${JSON.stringify(validation.errors, null, 2)}`);
|
|
1605
|
-
}
|
|
2118
|
+
const checkIsAssemblyNode = ({ componentId, usedComponents, }) => {
|
|
2119
|
+
if (!usedComponents?.length)
|
|
2120
|
+
return false;
|
|
2121
|
+
return usedComponents.some((usedComponent) => usedComponent.sys.id === componentId);
|
|
1606
2122
|
};
|
|
1607
|
-
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
2123
|
+
/** @deprecated use `checkIsAssemblyNode` instead. Will be removed with SDK v5. */
|
|
2124
|
+
const checkIsAssembly = checkIsAssemblyNode;
|
|
2125
|
+
/**
|
|
2126
|
+
* This check assumes that the entry is already ensured to be an experience, i.e. the
|
|
2127
|
+
* content type of the entry is an experience type with the necessary annotations.
|
|
2128
|
+
**/
|
|
2129
|
+
const checkIsAssemblyEntry = (entry) => {
|
|
2130
|
+
return Boolean(entry.fields?.componentSettings);
|
|
2131
|
+
};
|
|
2132
|
+
const checkIsAssemblyDefinition = (component) => component?.category === ASSEMBLY_DEFAULT_CATEGORY;
|
|
2133
|
+
|
|
2134
|
+
const isExperienceEntry = (entry) => {
|
|
2135
|
+
return (entry?.sys?.type === 'Entry' &&
|
|
2136
|
+
!!entry.fields?.title &&
|
|
2137
|
+
!!entry.fields?.slug &&
|
|
2138
|
+
!!entry.fields?.componentTree &&
|
|
2139
|
+
Array.isArray(entry.fields.componentTree.breakpoints) &&
|
|
2140
|
+
Array.isArray(entry.fields.componentTree.children) &&
|
|
2141
|
+
typeof entry.fields.componentTree.schemaVersion === 'string');
|
|
1612
2142
|
};
|
|
1613
2143
|
|
|
2144
|
+
const supportedModes = ['delivery', 'preview', 'editor'];
|
|
2145
|
+
|
|
1614
2146
|
const MEDIA_QUERY_REGEXP = /(<|>)(\d{1,})(px|cm|mm|in|pt|pc)$/;
|
|
1615
2147
|
const toCSSMediaQuery = ({ query }) => {
|
|
1616
2148
|
if (query === '*')
|
|
@@ -1903,54 +2435,6 @@ function buildTemplate({ template, context, }) {
|
|
|
1903
2435
|
}));
|
|
1904
2436
|
}
|
|
1905
2437
|
|
|
1906
|
-
const sectionDefinition = {
|
|
1907
|
-
id: CONTENTFUL_COMPONENTS.section.id,
|
|
1908
|
-
name: CONTENTFUL_COMPONENTS.section.name,
|
|
1909
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
1910
|
-
children: true,
|
|
1911
|
-
variables: sectionBuiltInStyles,
|
|
1912
|
-
tooltip: {
|
|
1913
|
-
description: 'Create a new full width section of your experience by dragging this component onto the canvas. Other components and patterns can be added into a section.',
|
|
1914
|
-
},
|
|
1915
|
-
};
|
|
1916
|
-
const containerDefinition = {
|
|
1917
|
-
id: CONTENTFUL_COMPONENTS.container.id,
|
|
1918
|
-
name: CONTENTFUL_COMPONENTS.container.name,
|
|
1919
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
1920
|
-
children: true,
|
|
1921
|
-
variables: containerBuiltInStyles,
|
|
1922
|
-
tooltip: {
|
|
1923
|
-
description: 'Create a new area or pattern within your page layout by dragging a container onto the canvas. Other components and patterns can be added into a container.',
|
|
1924
|
-
},
|
|
1925
|
-
};
|
|
1926
|
-
const columnsDefinition = {
|
|
1927
|
-
id: CONTENTFUL_COMPONENTS.columns.id,
|
|
1928
|
-
name: CONTENTFUL_COMPONENTS.columns.name,
|
|
1929
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
1930
|
-
children: true,
|
|
1931
|
-
variables: columnsBuiltInStyles,
|
|
1932
|
-
tooltip: {
|
|
1933
|
-
description: 'Add columns to a container to create your desired layout and ensure that the experience is responsive across different screen sizes.',
|
|
1934
|
-
},
|
|
1935
|
-
};
|
|
1936
|
-
const singleColumnDefinition = {
|
|
1937
|
-
id: CONTENTFUL_COMPONENTS.singleColumn.id,
|
|
1938
|
-
name: CONTENTFUL_COMPONENTS.singleColumn.name,
|
|
1939
|
-
category: CONTENTFUL_COMPONENT_CATEGORY,
|
|
1940
|
-
children: true,
|
|
1941
|
-
variables: singleColumnBuiltInStyles,
|
|
1942
|
-
};
|
|
1943
|
-
const dividerDefinition = {
|
|
1944
|
-
id: CONTENTFUL_COMPONENTS.divider.id,
|
|
1945
|
-
name: CONTENTFUL_COMPONENTS.divider.name,
|
|
1946
|
-
category: CONTENTFUL_DEFAULT_CATEGORY,
|
|
1947
|
-
children: false,
|
|
1948
|
-
variables: dividerBuiltInStyles,
|
|
1949
|
-
tooltip: {
|
|
1950
|
-
description: 'Drop onto the canvas to add a divider.',
|
|
1951
|
-
},
|
|
1952
|
-
};
|
|
1953
|
-
|
|
1954
2438
|
const sendMessage = (eventType, data) => {
|
|
1955
2439
|
if (typeof window === 'undefined') {
|
|
1956
2440
|
return;
|
|
@@ -2880,5 +3364,5 @@ async function fetchById({ client, experienceTypeId, id, localeCode, }) {
|
|
|
2880
3364
|
}
|
|
2881
3365
|
}
|
|
2882
3366
|
|
|
2883
|
-
export { DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, columnsDefinition, containerBuiltInStyles, containerDefinition, createExperience, defineBreakpoints, defineDesignTokens, designTokensRegistry, dividerBuiltInStyles, dividerDefinition, doesMismatchMessageSchema, fetchById, fetchBySlug, findOutermostCoordinates, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getInsertionData, getTemplateValue, getValueForBreakpoint, isComponentAllowedOnRoot, isContentfulStructureComponent, isDeepPath, isExperienceEntry, isLink, isLinkToAsset, isStructureWithRelativeHeight, lastPathNamedSegmentEq, mediaQueryMatcher, optionalBuiltInStyles, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveHyperlinkPattern, runBreakpointsValidation, sectionBuiltInStyles, sectionDefinition, sendMessage, singleColumnBuiltInStyles, singleColumnDefinition, supportedModes, toCSSAttribute, transformBoundContentValue, tryParseMessage, validateExperienceBuilderConfig };
|
|
3367
|
+
export { DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, columnsDefinition, containerBuiltInStyles, containerDefinition, createExperience, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, dividerBuiltInStyles, dividerDefinition, doesMismatchMessageSchema, fetchById, fetchBySlug, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getInsertionData, getTemplateValue, getValueForBreakpoint, indexByBreakpoint, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulStructureComponent, isDeepPath, isExperienceEntry, isLink, isLinkToAsset, isStructureWithRelativeHeight, lastPathNamedSegmentEq, maybePopulateDesignTokenValue, mediaQueryMatcher, optionalBuiltInStyles, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sectionBuiltInStyles, sectionDefinition, sendMessage, singleColumnBuiltInStyles, singleColumnDefinition, supportedModes, toCSSAttribute, toCSSString, toMediaQuery, transformBoundContentValue, tryParseMessage, validateExperienceBuilderConfig };
|
|
2884
3368
|
//# sourceMappingURL=index.js.map
|