@contentful/experiences-core 1.34.0 → 1.34.1-dev-20250305T1630-a40e5df.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isPatternComponent, isStructureWithRelativeHeight } from './utils/components.js';
2
2
  export { findOutermostCoordinates, getElementCoordinates } from './utils/domValues.js';
3
3
  export { doesMismatchMessageSchema, tryParseMessage, validateExperienceBuilderConfig } from './utils/validations.js';
4
- export { buildCfStyles, buildStyleTag, calculateNodeDefaultHeight, toCSSAttribute } from './utils/styleUtils/stylesUtils.js';
5
- export { detachExperienceStyles, flattenDesignTokenRegistry, indexByBreakpoint, isCfStyleAttribute, maybePopulateDesignTokenValue, resolveBackgroundImageBinding, toCSSString, toMediaQuery } from './utils/styleUtils/ssrStyles.js';
4
+ export { addMinHeightForEmptyStructures, buildCfStyles, buildStyleTag, calculateNodeDefaultHeight, stringifyCssProperties, toCSSAttribute } from './utils/styleUtils/stylesUtils.js';
5
+ export { detachExperienceStyles, flattenDesignTokenRegistry, indexByBreakpoint, isCfStyleAttribute, maybePopulateDesignTokenValue, resolveBackgroundImageBinding, toMediaQuery } from './utils/styleUtils/ssrStyles.js';
6
6
  export { transformBoundContentValue } from './utils/transformers/transformBoundContentValue.js';
7
7
  export { checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, generateRandomId, getDataFromTree, getInsertionData, getTargetValueInPixels, parseCSSValue } from './utils/utils.js';
8
8
  export { isExperienceEntry } from './utils/typeguards.js';
package/dist/index.js CHANGED
@@ -380,45 +380,102 @@ const toCSSAttribute = (key) => {
380
380
  val = val.replace(/\d+$/, '');
381
381
  return val;
382
382
  };
383
- const buildStyleTag = ({ styles, nodeId }) => {
384
- const stylesStr = Object.entries(styles)
383
+ /**
384
+ * Turns a list of CSSProperties into a joined CSS string that can be
385
+ * used for <style> tags. Per default it creates a minimized version.
386
+ * For editor mode, use the `useWhitespaces` flag to create a more readable version.
387
+ *
388
+ * @param cssProperties list of CSS properties
389
+ * @param useWhitespaces adds whitespaces and newlines between each rule
390
+ * @returns a string of CSS rules
391
+ */
392
+ const stringifyCssProperties = (cssProperties, useWhitespaces = false) => {
393
+ const rules = Object.entries(cssProperties)
385
394
  .filter(([, value]) => value !== undefined)
386
- .reduce((acc, [key, value]) => `${acc}
387
- ${toCSSAttribute(key)}: ${value};`, '');
388
- const className = `cfstyles-${nodeId ? nodeId : md5(stylesStr)}`;
389
- const styleRule = `.${className}{ ${stylesStr} }`;
395
+ .map(([key, value]) => useWhitespaces ? `${toCSSAttribute(key)}: ${value};` : `${toCSSAttribute(key)}:${value};`);
396
+ return rules.join(useWhitespaces ? '\n' : '');
397
+ };
398
+ const buildStyleTag = ({ styles, nodeId }) => {
399
+ const generatedStyles = stringifyCssProperties(styles, true);
400
+ const className = `cfstyles-${nodeId ? nodeId : md5(generatedStyles)}`;
401
+ const styleRule = `.${className}{ ${generatedStyles} }`;
390
402
  return [className, styleRule];
391
403
  };
392
- const buildCfStyles = ({ cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection, cfFlexReverse, cfFlexWrap, cfMargin, cfPadding, cfBackgroundColor, cfWidth, cfHeight, cfMaxWidth, cfBorder, cfBorderRadius, cfGap, cfBackgroundImageUrl, cfBackgroundImageOptions, cfFontSize, cfFontWeight, cfImageOptions, cfLineHeight, cfLetterSpacing, cfTextColor, cfTextAlign, cfTextTransform, cfTextBold, cfTextItalic, cfTextUnderline, cfColumnSpan, cfVisibility, }) => {
393
- return {
404
+ /**
405
+ * Takes plain design values and transforms them into CSS properties. Undefined values will
406
+ * be filtered out.
407
+ *
408
+ * **Example Input**
409
+ * ```
410
+ * values = {
411
+ * cfVisibility: 'visible',
412
+ * cfMargin: '10px',
413
+ * cfFlexReverse: true,
414
+ * cfImageOptions: { objectFit: 'cover' },
415
+ * // ...
416
+ * }
417
+ * ```
418
+ * **Example Output**
419
+ * ```
420
+ * cssProperties = {
421
+ * margin: '10px',
422
+ * flexDirection: 'row-reverse',
423
+ * objectFit: 'cover',
424
+ * // ...
425
+ * }
426
+ * ```
427
+ */
428
+ const buildCfStyles = (values) => {
429
+ const cssProperties = {
394
430
  boxSizing: 'border-box',
395
- ...transformVisibility(cfVisibility),
396
- margin: cfMargin,
397
- padding: cfPadding,
398
- backgroundColor: cfBackgroundColor,
399
- width: transformFill(cfWidth || cfImageOptions?.width),
400
- height: transformFill(cfHeight || cfImageOptions?.height),
401
- maxWidth: cfMaxWidth,
402
- ...transformGridColumn(cfColumnSpan),
403
- ...transformBorderStyle(cfBorder),
404
- borderRadius: cfBorderRadius,
405
- gap: cfGap,
406
- ...transformAlignment(cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection),
407
- flexDirection: cfFlexReverse && cfFlexDirection ? `${cfFlexDirection}-reverse` : cfFlexDirection,
408
- flexWrap: cfFlexWrap,
409
- ...transformBackgroundImage(cfBackgroundImageUrl, cfBackgroundImageOptions),
410
- fontSize: cfFontSize,
411
- fontWeight: cfTextBold ? 'bold' : cfFontWeight,
412
- fontStyle: cfTextItalic ? 'italic' : undefined,
413
- textDecoration: cfTextUnderline ? 'underline' : undefined,
414
- lineHeight: cfLineHeight,
415
- letterSpacing: cfLetterSpacing,
416
- color: cfTextColor,
417
- textAlign: cfTextAlign,
418
- textTransform: cfTextTransform,
419
- objectFit: cfImageOptions?.objectFit,
420
- objectPosition: cfImageOptions?.objectPosition,
431
+ ...transformVisibility(values.cfVisibility),
432
+ margin: values.cfMargin,
433
+ padding: values.cfPadding,
434
+ backgroundColor: values.cfBackgroundColor,
435
+ width: transformFill(values.cfWidth || values.cfImageOptions?.width),
436
+ height: transformFill(values.cfHeight || values.cfImageOptions?.height),
437
+ maxWidth: values.cfMaxWidth,
438
+ ...transformGridColumn(values.cfColumnSpan),
439
+ ...transformBorderStyle(values.cfBorder),
440
+ borderRadius: values.cfBorderRadius,
441
+ gap: values.cfGap,
442
+ ...transformAlignment(values.cfHorizontalAlignment, values.cfVerticalAlignment, values.cfFlexDirection),
443
+ flexDirection: values.cfFlexReverse && values.cfFlexDirection
444
+ ? `${values.cfFlexDirection}-reverse`
445
+ : values.cfFlexDirection,
446
+ flexWrap: values.cfFlexWrap,
447
+ ...transformBackgroundImage(values.cfBackgroundImageUrl, values.cfBackgroundImageOptions),
448
+ fontSize: values.cfFontSize,
449
+ fontWeight: values.cfTextBold ? 'bold' : values.cfFontWeight,
450
+ fontStyle: values.cfTextItalic ? 'italic' : undefined,
451
+ textDecoration: values.cfTextUnderline ? 'underline' : undefined,
452
+ lineHeight: values.cfLineHeight,
453
+ letterSpacing: values.cfLetterSpacing,
454
+ color: values.cfTextColor,
455
+ textAlign: values.cfTextAlign,
456
+ textTransform: values.cfTextTransform,
457
+ objectFit: values.cfImageOptions?.objectFit,
458
+ objectPosition: values.cfImageOptions?.objectPosition,
421
459
  };
460
+ const cssPropertiesWithoutUndefined = Object.fromEntries(Object.entries(cssProperties).filter(([, value]) => value !== undefined));
461
+ return cssPropertiesWithoutUndefined;
462
+ };
463
+ /**
464
+ * **Only meant to be used in editor mode!**
465
+ *
466
+ * If the node is an empty structure component with a relative height (e.g. '100%'),
467
+ * it might render with a zero height. In this case, add a min height of 80px to ensure
468
+ * that child nodes can be added via drag & drop.
469
+ */
470
+ const addMinHeightForEmptyStructures = (cssProperties, node) => {
471
+ if (!node.children.length &&
472
+ isStructureWithRelativeHeight(node.definitionId, cssProperties.height)) {
473
+ return {
474
+ ...cssProperties,
475
+ minHeight: EMPTY_CONTAINER_HEIGHT,
476
+ };
477
+ }
478
+ return cssProperties;
422
479
  };
423
480
  /**
424
481
  * Container/section default behavior:
@@ -1594,41 +1651,17 @@ const detachExperienceStyles = (experience) => {
1594
1651
  }
1595
1652
  const mapOfDesignVariableKeys = flattenDesignTokenRegistry(designTokensRegistry);
1596
1653
  // getting breakpoints from the entry componentTree field
1597
- /**
1598
- * breakpoints [
1599
- {
1600
- id: 'desktop',
1601
- query: '*',
1602
- displayName: 'All Sizes',
1603
- previewSize: '100%'
1604
- },
1605
- {
1606
- id: 'tablet',
1607
- query: '<992px',
1608
- displayName: 'Tablet',
1609
- previewSize: '820px'
1610
- },
1611
- {
1612
- id: 'mobile',
1613
- query: '<576px',
1614
- displayName: 'Mobile',
1615
- previewSize: '390px'
1616
- }
1617
- ]
1618
- */
1619
1654
  const { breakpoints } = experienceTreeRoot;
1620
1655
  // creating the structure which I thought would work best for aggregation
1621
- const mediaQueriesTemplate = breakpoints.reduce((mediaQueryTemplate, breakpoint) => {
1622
- return {
1623
- ...mediaQueryTemplate,
1624
- [breakpoint.id]: {
1625
- condition: breakpoint.query,
1626
- cssByClassName: {},
1627
- },
1628
- };
1629
- }, {});
1656
+ const mediaQueryDataByBreakpoint = breakpoints.reduce((mediaQueryTemplate, breakpoint) => ({
1657
+ ...mediaQueryTemplate,
1658
+ [breakpoint.id]: {
1659
+ condition: breakpoint.query,
1660
+ cssByClassName: {},
1661
+ },
1662
+ }), {});
1630
1663
  // getting the breakpoint ids
1631
- const breakpointIds = Object.keys(mediaQueriesTemplate);
1664
+ const breakpointIds = Object.keys(mediaQueryDataByBreakpoint);
1632
1665
  const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, patternWrapper, wrappingPatternIds, patternNodeIdsChain = '', }) => {
1633
1666
  // traversing the tree
1634
1667
  const queue = [];
@@ -1654,29 +1687,6 @@ const detachExperienceStyles = (experience) => {
1654
1687
  if (!patternEntry || !('fields' in patternEntry)) {
1655
1688
  continue;
1656
1689
  }
1657
- const defaultPatternDivStyles = Object.fromEntries(Object.entries(buildCfStyles({}))
1658
- .filter(([, value]) => value !== undefined)
1659
- .map(([key, value]) => [toCSSAttribute(key), value]));
1660
- // I create a hash of the object above because that would ensure hash stability
1661
- const styleHash = md5(JSON.stringify(defaultPatternDivStyles));
1662
- // and prefix the className to make sure the value can be processed
1663
- const className = `cf-${styleHash}`;
1664
- for (const breakpointId of breakpointIds) {
1665
- if (!mediaQueriesTemplate[breakpointId].cssByClassName[className]) {
1666
- mediaQueriesTemplate[breakpointId].cssByClassName[className] =
1667
- toCSSString(defaultPatternDivStyles);
1668
- }
1669
- }
1670
- // When iterating over instances of the same pattern, we will iterate over the identical
1671
- // pattern nodes again for every instance. Make sure to not overwrite the values from previous instances.
1672
- if (!currentNode.variables.cfSsrClassName) {
1673
- currentNode.variables.cfSsrClassName = {
1674
- type: 'DesignValue',
1675
- valuesByBreakpoint: {
1676
- [breakpointIds[0]]: className,
1677
- },
1678
- };
1679
- }
1680
1690
  const nextComponentVariablesOverwrites = resolveComponentVariablesOverwrites({
1681
1691
  patternNode: currentNode,
1682
1692
  wrapperComponentVariablesOverwrites: componentVariablesOverwrites,
@@ -1704,36 +1714,15 @@ const detachExperienceStyles = (experience) => {
1704
1714
  });
1705
1715
  continue;
1706
1716
  }
1707
- /** Variables value is stored in `valuesByBreakpoint` object
1708
- * {
1709
- cfVerticalAlignment: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'center' } },
1710
- cfHorizontalAlignment: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'center' } },
1711
- cfMargin: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0 0 0 0' } },
1712
- cfPadding: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0 0 0 0' } },
1713
- cfBackgroundColor: {
1714
- type: 'DesignValue',
1715
- valuesByBreakpoint: { desktop: 'rgba(246, 246, 246, 1)' }
1716
- },
1717
- cfWidth: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'fill' } },
1718
- cfHeight: {
1719
- type: 'DesignValue',
1720
- valuesByBreakpoint: { desktop: 'fit-content' }
1721
- },
1722
- cfMaxWidth: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'none' } },
1723
- cfFlexDirection: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'column' } },
1724
- cfFlexWrap: { type: 'DesignValue', valuesByBreakpoint: { desktop: 'nowrap' } },
1725
- cfBorder: {
1726
- type: 'DesignValue',
1727
- valuesByBreakpoint: { desktop: '0px solid rgba(0, 0, 0, 0)' }
1728
- },
1729
- cfBorderRadius: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0px' } },
1730
- cfGap: { type: 'DesignValue', valuesByBreakpoint: { desktop: '0px 0px' } },
1731
- cfHyperlink: { type: 'UnboundValue', key: 'VNc49Qyepd6IzN7rmKUyS' },
1732
- cfOpenInNewTab: { type: 'UnboundValue', key: 'ZA5YqB2fmREQ4pTKqY5hX' },
1733
- cfBackgroundImageUrl: { type: 'UnboundValue', key: 'FeskH0WbYD5_RQVXX-1T8' },
1734
- cfBackgroundImageOptions: { type: 'DesignValue', valuesByBreakpoint: { desktop: [Object] } }
1735
- }
1736
- */
1717
+ /* [Data format] `currentNode.variables` uses the following serialized shape:
1718
+ * {
1719
+ * cfMargin: { type: 'DesignValue', valuesByBreakpoint: { desktop: '1px', tablet: '2px' } },
1720
+ * cfPadding: { type: 'DesignValue', valuesByBreakpoint: { desktop: '3px' } }
1721
+ * cfBackgroundImageUrl: { type: 'BoundValue', path: '/lUERH7tX7nJTaPX6f0udB/fields/assetReference/~locale/fields/file/~locale' }
1722
+ * asdf1234: { type: 'ComponentValue', key: 'qwer567' }
1723
+ * // ...
1724
+ * }
1725
+ */
1737
1726
  // so first, I convert it into a map to help me make it easier to access the values
1738
1727
  const propsByBreakpoint = indexByBreakpoint({
1739
1728
  variables: currentNode.variables,
@@ -1746,30 +1735,18 @@ const detachExperienceStyles = (experience) => {
1746
1735
  return experience.entityStore?.entities.find((entity) => entity.sys.id === id);
1747
1736
  },
1748
1737
  });
1749
- /**
1750
- * propsByBreakpoint {
1751
- desktop: {
1752
- cfVerticalAlignment: 'center',
1753
- cfHorizontalAlignment: 'center',
1754
- cfMargin: '0 0 0 0',
1755
- cfPadding: '0 0 0 0',
1756
- cfBackgroundColor: 'rgba(246, 246, 246, 1)',
1757
- cfWidth: 'fill',
1758
- cfHeight: 'fit-content',
1759
- cfMaxWidth: 'none',
1760
- cfFlexDirection: 'column',
1761
- cfFlexWrap: 'nowrap',
1762
- cfBorder: '0px solid rgba(0, 0, 0, 0)',
1763
- cfBorderRadius: '0px',
1764
- cfGap: '0px 0px',
1765
- cfBackgroundImageOptions: { scaling: 'fill', alignment: 'left top', targetSize: '2000px' }
1766
- },
1767
- tablet: {},
1768
- mobile: {}
1769
- }
1770
- */
1738
+ /* [Data format] `propsByBreakpoint` is a map of "breakpointId > propertyName > plainValue":
1739
+ * {
1740
+ * desktop: {
1741
+ * cfMargin: '1px',
1742
+ * cfWidth: 'fill',
1743
+ * cfBackgroundImageUrl: 'https://example.com/image.jpg'
1744
+ * //...
1745
+ * }
1746
+ * }
1747
+ */
1771
1748
  const currentNodeClassNames = [];
1772
- // then for each breakpoint
1749
+ // For each breakpoint, resolve design tokens, create the CSS and generate a unique className.
1773
1750
  for (const breakpointId of breakpointIds) {
1774
1751
  const propsByBreakpointWithResolvedDesignTokens = Object.entries(propsByBreakpoint[breakpointId]).reduce((acc, [variableName, variableValue]) => {
1775
1752
  return {
@@ -1777,36 +1754,24 @@ const detachExperienceStyles = (experience) => {
1777
1754
  [variableName]: maybePopulateDesignTokenValue(variableName, variableValue, mapOfDesignVariableKeys),
1778
1755
  };
1779
1756
  }, {});
1780
- // We convert cryptic prop keys to css variables
1781
- // Eg: cfMargin to margin
1782
- const stylesForBreakpoint = buildCfStyles(propsByBreakpointWithResolvedDesignTokens);
1783
- const stylesForBreakpointWithoutUndefined = Object.fromEntries(Object.entries(stylesForBreakpoint)
1784
- .filter(([, value]) => value !== undefined)
1785
- .map(([key, value]) => [toCSSAttribute(key), value]));
1786
- /**
1787
- * stylesForBreakpoint {
1788
- margin: '0 0 0 0',
1789
- padding: '0 0 0 0',
1790
- 'background-color': 'rgba(246, 246, 246, 1)',
1791
- width: '100%',
1792
- height: 'fit-content',
1793
- 'max-width': 'none',
1794
- border: '0px solid rgba(0, 0, 0, 0)',
1795
- 'border-radius': '0px',
1796
- gap: '0px 0px',
1797
- 'align-items': 'center',
1798
- 'justify-content': 'safe center',
1799
- 'flex-direction': 'column',
1800
- 'flex-wrap': 'nowrap',
1801
- 'font-style': 'normal',
1802
- 'text-decoration': 'none',
1803
- 'box-sizing': 'border-box'
1804
- }
1805
- */
1757
+ // Convert CF-specific property names to CSS variables, e.g. `cfMargin` -> `margin`
1758
+ const cfStyles = buildCfStyles(propsByBreakpointWithResolvedDesignTokens);
1759
+ /* [Data format] `cfStyles` is a list of CSSProperties (React format):
1760
+ * {
1761
+ * margin: '1px',
1762
+ * width: '100%',
1763
+ * backgroundImage: 'url(https://example.com/image.jpg)'
1764
+ * //...
1765
+ * }
1766
+ */
1767
+ const generatedCss = stringifyCssProperties(cfStyles);
1768
+ /* [Data format] `generatedCss` is the minimized CSS string that will be added to the DOM:
1769
+ * generatedCss = "margin: 1px;width: 100%;..."
1770
+ */
1806
1771
  // I create a hash of the object above because that would ensure hash stability
1807
1772
  // Adding breakpointId to ensure not using the same IDs between breakpoints as this leads to
1808
1773
  // conflicts between different breakpoint values from multiple nodes where the hash would be equal
1809
- const styleHash = md5(breakpointId + JSON.stringify(stylesForBreakpointWithoutUndefined));
1774
+ const styleHash = md5(breakpointId + generatedCss);
1810
1775
  // and prefix the className to make sure the value can be processed
1811
1776
  const className = `cf-${styleHash}`;
1812
1777
  // I save the generated hashes into an array to later save it in the tree node
@@ -1816,11 +1781,11 @@ const detachExperienceStyles = (experience) => {
1816
1781
  currentNodeClassNames.push(className);
1817
1782
  }
1818
1783
  // if there is already the similar hash - no need to over-write it
1819
- if (mediaQueriesTemplate[breakpointId].cssByClassName[className]) {
1784
+ if (mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className]) {
1820
1785
  continue;
1821
1786
  }
1822
1787
  // otherwise, save it to the stylesheet
1823
- mediaQueriesTemplate[breakpointId].cssByClassName[className] = toCSSString(stylesForBreakpointWithoutUndefined);
1788
+ mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className] = generatedCss;
1824
1789
  }
1825
1790
  // all generated classNames are saved in the tree node
1826
1791
  // to be handled by the sdk later
@@ -1864,10 +1829,8 @@ const detachExperienceStyles = (experience) => {
1864
1829
  });
1865
1830
  // once the whole tree was traversed, for each breakpoint, I aggregate the styles
1866
1831
  // for each generated className into one css string
1867
- const styleSheet = Object.entries(mediaQueriesTemplate).reduce((acc, [, breakpointPayload]) => {
1868
- return `${acc}${toMediaQuery(breakpointPayload)}`;
1869
- }, '');
1870
- return styleSheet;
1832
+ const stylesheet = Object.entries(mediaQueryDataByBreakpoint).reduce((acc, [, mediaQueryData]) => `${acc}${toMediaQuery(mediaQueryData)}`, '');
1833
+ return stylesheet;
1871
1834
  };
1872
1835
  /**
1873
1836
  * Rendering pattern nodes inside pattern entry by injecting default values from the top:
@@ -2042,6 +2005,41 @@ const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataS
2042
2005
  }
2043
2006
  }
2044
2007
  };
2008
+ /**
2009
+ * Takes the initial set of properties, filters only design properties that will be mapped to CSS and
2010
+ * re-organizes them to be indexed by breakpoint ID ("breakpoint > variable > value"). It will
2011
+ * also resolve the design/ component values to plain values.
2012
+ *
2013
+ * **Example Input**
2014
+ * ```
2015
+ * variables = {
2016
+ * cfMargin: { type: 'DesignValue', valuesByBreakpoint: { desktop: '1px', tablet: '2px' } },
2017
+ * cfPadding: { type: 'DesignValue', valuesByBreakpoint: { desktop: '3px', mobile: '4px' } }
2018
+ * }
2019
+ * ```
2020
+ *
2021
+ * **Example Output**
2022
+ * ```
2023
+ * variableValuesByBreakpoints = {
2024
+ * desktop: {
2025
+ * cfMargin: '1px',
2026
+ * cfPadding: '3px'
2027
+ * },
2028
+ * tablet: {
2029
+ * cfMargin: '2px'
2030
+ * },
2031
+ * mobile: {
2032
+ * cfPadding: '4px'
2033
+ * }
2034
+ * }
2035
+ * ```
2036
+ *
2037
+ * **Note**
2038
+ * - The property cfBackgroundImageUrl is the only content property that gets mapped to CSS as well.
2039
+ * It will be solely stored on the default breakpoint.
2040
+ * - For ComponentValues, it will either take the override from the pattern instance or fallback to
2041
+ * the defaultValue defined in variableDefinitions.
2042
+ */
2045
2043
  const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unboundValues = {}, dataSource = {}, componentVariablesOverwrites, componentSettings = { variableDefinitions: {} }, }) => {
2046
2044
  const variableValuesByBreakpoints = breakpointIds.reduce((acc, breakpointId) => {
2047
2045
  return {
@@ -2052,9 +2050,10 @@ const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unbou
2052
2050
  const defaultBreakpoint = breakpointIds[0];
2053
2051
  for (const [variableName, variableData] of Object.entries(variables)) {
2054
2052
  // handling the special case - cfBackgroundImageUrl variable, which can be bound or unbound
2055
- // so, we need to resolve it here and pass it down as a css property to be convereted into the CSS
2053
+ // so, we need to resolve it here and pass it down as a css property to be converted into the CSS
2056
2054
  // I used .startsWith() cause it can be part of a pattern node
2057
2055
  if (variableName === 'cfBackgroundImageUrl' ||
2056
+ // TODO: Test this for nested patterns as the name might be just a random hash without the actual name (needs to be validated).
2058
2057
  variableName.startsWith('cfBackgroundImageUrl_')) {
2059
2058
  const imageUrl = resolveBackgroundImageBinding({
2060
2059
  variableData,
@@ -2095,17 +2094,9 @@ const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unbou
2095
2094
  };
2096
2095
  /**
2097
2096
  * Flattens the object from
2098
- * {
2099
- * color: {
2100
- * [key]: [value]
2101
- * }
2102
- * }
2103
- *
2097
+ * `{ color: { [key]: [value] } }`
2104
2098
  * to
2105
- *
2106
- * {
2107
- * 'color.key': [value]
2108
- * }
2099
+ * `{ 'color.key': [value] }`
2109
2100
  */
2110
2101
  const flattenDesignTokenRegistry = (designTokenRegistry) => {
2111
2102
  return Object.entries(designTokenRegistry).reduce((acc, [categoryName, tokenCategory]) => {
@@ -2121,24 +2112,21 @@ const flattenDesignTokenRegistry = (designTokenRegistry) => {
2121
2112
  };
2122
2113
  }, {});
2123
2114
  };
2124
- // Replaces camelCase with kebab-case
2125
- // converts the <key, value> object into a css string
2126
- const toCSSString = (breakpointStyles) => {
2127
- return Object.entries(breakpointStyles)
2128
- .map(([key, value]) => `${key}:${value};`)
2129
- .join('');
2130
- };
2131
- const toMediaQuery = (breakpointPayload) => {
2132
- const mediaQueryStyles = Object.entries(breakpointPayload.cssByClassName).reduce((acc, [className, css]) => {
2115
+ /**
2116
+ * Create a single CSS string containing all class definitions for a given media query.
2117
+ *
2118
+ * @param condition e.g. "*", "<520px", ">520px"
2119
+ * @param cssByClassName map of class names to CSS strings containing all rules for each class
2120
+ * @returns joined string of all CSS class definitions wrapped into media queries
2121
+ */
2122
+ const toMediaQuery = ({ condition, cssByClassName }) => {
2123
+ const mediaQueryStyles = Object.entries(cssByClassName).reduce((acc, [className, css]) => {
2133
2124
  return `${acc}.${className}{${css}}`;
2134
2125
  }, ``);
2135
- if (breakpointPayload.condition === '*') {
2126
+ if (condition === '*') {
2136
2127
  return mediaQueryStyles;
2137
2128
  }
2138
- const [evaluation, pixelValue] = [
2139
- breakpointPayload.condition[0],
2140
- breakpointPayload.condition.substring(1),
2141
- ];
2129
+ const [evaluation, pixelValue] = [condition[0], condition.substring(1)];
2142
2130
  const mediaQueryRule = evaluation === '<' ? 'max-width' : 'min-width';
2143
2131
  return `@media(${mediaQueryRule}:${pixelValue}){${mediaQueryStyles}}`;
2144
2132
  };
@@ -3917,5 +3905,5 @@ async function fetchById({ client, experienceTypeId, id, localeCode, isEditorMod
3917
3905
  }
3918
3906
  }
3919
3907
 
3920
- export { DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, containerBuiltInStyles, createExperience, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, dividerBuiltInStyles, doesMismatchMessageSchema, fetchAllAssets, fetchAllEntries, fetchById, fetchBySlug, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getInsertionData, getTargetValueInPixels, getTemplateValue, getValueForBreakpoint, indexByBreakpoint, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isDeepPath, isExperienceEntry, isLink, isLinkToAsset, isPatternComponent, isStructureWithRelativeHeight, isValidBreakpointValue, lastPathNamedSegmentEq, maybePopulateDesignTokenValue, mediaQueryMatcher, optionalBuiltInStyles, parseCSSValue, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sanitizeNodeProps, sectionBuiltInStyles, sendMessage, singleColumnBuiltInStyles, toCSSAttribute, toCSSString, toMediaQuery, transformBoundContentValue, tryParseMessage, validateExperienceBuilderConfig };
3908
+ export { DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, addMinHeightForEmptyStructures, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, containerBuiltInStyles, createExperience, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, dividerBuiltInStyles, doesMismatchMessageSchema, fetchAllAssets, fetchAllEntries, fetchById, fetchBySlug, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getInsertionData, getTargetValueInPixels, getTemplateValue, getValueForBreakpoint, indexByBreakpoint, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isDeepPath, isExperienceEntry, isLink, isLinkToAsset, isPatternComponent, isStructureWithRelativeHeight, isValidBreakpointValue, lastPathNamedSegmentEq, maybePopulateDesignTokenValue, mediaQueryMatcher, optionalBuiltInStyles, parseCSSValue, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sanitizeNodeProps, sectionBuiltInStyles, sendMessage, singleColumnBuiltInStyles, stringifyCssProperties, toCSSAttribute, toMediaQuery, transformBoundContentValue, tryParseMessage, validateExperienceBuilderConfig };
3921
3909
  //# sourceMappingURL=index.js.map