@contentful/experiences-core 1.34.0-dev-20250228T1701-87c41f6.0 → 1.34.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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:
@@ -1176,11 +1233,13 @@ const VariableMappingsSchema = z.record(propertyKeySchema, VariableMappingSchema
1176
1233
  // https://contentful.atlassian.net/browse/LUMOS-523
1177
1234
  const PatternPropertyDefinitionSchema = z.object({
1178
1235
  defaultValue: z
1179
- .object({
1180
- path: z.string(),
1181
- type: z.literal('BoundValue'),
1182
- contentType: z.string(),
1183
- })
1236
+ .record(z.string(), z.object({
1237
+ sys: z.object({
1238
+ type: z.literal('Link'),
1239
+ id: z.string(),
1240
+ linkType: z.enum(['Entry']),
1241
+ }),
1242
+ }))
1184
1243
  .optional(),
1185
1244
  contentTypes: z.record(z.string(), z.any()),
1186
1245
  });
@@ -1594,41 +1653,17 @@ const detachExperienceStyles = (experience) => {
1594
1653
  }
1595
1654
  const mapOfDesignVariableKeys = flattenDesignTokenRegistry(designTokensRegistry);
1596
1655
  // 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
1656
  const { breakpoints } = experienceTreeRoot;
1620
1657
  // 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
- }, {});
1658
+ const mediaQueryDataByBreakpoint = breakpoints.reduce((mediaQueryTemplate, breakpoint) => ({
1659
+ ...mediaQueryTemplate,
1660
+ [breakpoint.id]: {
1661
+ condition: breakpoint.query,
1662
+ cssByClassName: {},
1663
+ },
1664
+ }), {});
1630
1665
  // getting the breakpoint ids
1631
- const breakpointIds = Object.keys(mediaQueriesTemplate);
1666
+ const breakpointIds = Object.keys(mediaQueryDataByBreakpoint);
1632
1667
  const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, patternWrapper, wrappingPatternIds, patternNodeIdsChain = '', }) => {
1633
1668
  // traversing the tree
1634
1669
  const queue = [];
@@ -1654,29 +1689,6 @@ const detachExperienceStyles = (experience) => {
1654
1689
  if (!patternEntry || !('fields' in patternEntry)) {
1655
1690
  continue;
1656
1691
  }
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
1692
  const nextComponentVariablesOverwrites = resolveComponentVariablesOverwrites({
1681
1693
  patternNode: currentNode,
1682
1694
  wrapperComponentVariablesOverwrites: componentVariablesOverwrites,
@@ -1704,36 +1716,15 @@ const detachExperienceStyles = (experience) => {
1704
1716
  });
1705
1717
  continue;
1706
1718
  }
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
- */
1719
+ /* [Data format] `currentNode.variables` uses the following serialized shape:
1720
+ * {
1721
+ * cfMargin: { type: 'DesignValue', valuesByBreakpoint: { desktop: '1px', tablet: '2px' } },
1722
+ * cfPadding: { type: 'DesignValue', valuesByBreakpoint: { desktop: '3px' } }
1723
+ * cfBackgroundImageUrl: { type: 'BoundValue', path: '/lUERH7tX7nJTaPX6f0udB/fields/assetReference/~locale/fields/file/~locale' }
1724
+ * asdf1234: { type: 'ComponentValue', key: 'qwer567' }
1725
+ * // ...
1726
+ * }
1727
+ */
1737
1728
  // so first, I convert it into a map to help me make it easier to access the values
1738
1729
  const propsByBreakpoint = indexByBreakpoint({
1739
1730
  variables: currentNode.variables,
@@ -1746,30 +1737,18 @@ const detachExperienceStyles = (experience) => {
1746
1737
  return experience.entityStore?.entities.find((entity) => entity.sys.id === id);
1747
1738
  },
1748
1739
  });
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
- */
1740
+ /* [Data format] `propsByBreakpoint` is a map of "breakpointId > propertyName > plainValue":
1741
+ * {
1742
+ * desktop: {
1743
+ * cfMargin: '1px',
1744
+ * cfWidth: 'fill',
1745
+ * cfBackgroundImageUrl: 'https://example.com/image.jpg'
1746
+ * //...
1747
+ * }
1748
+ * }
1749
+ */
1771
1750
  const currentNodeClassNames = [];
1772
- // then for each breakpoint
1751
+ // For each breakpoint, resolve design tokens, create the CSS and generate a unique className.
1773
1752
  for (const breakpointId of breakpointIds) {
1774
1753
  const propsByBreakpointWithResolvedDesignTokens = Object.entries(propsByBreakpoint[breakpointId]).reduce((acc, [variableName, variableValue]) => {
1775
1754
  return {
@@ -1777,36 +1756,24 @@ const detachExperienceStyles = (experience) => {
1777
1756
  [variableName]: maybePopulateDesignTokenValue(variableName, variableValue, mapOfDesignVariableKeys),
1778
1757
  };
1779
1758
  }, {});
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
- */
1759
+ // Convert CF-specific property names to CSS variables, e.g. `cfMargin` -> `margin`
1760
+ const cfStyles = buildCfStyles(propsByBreakpointWithResolvedDesignTokens);
1761
+ /* [Data format] `cfStyles` is a list of CSSProperties (React format):
1762
+ * {
1763
+ * margin: '1px',
1764
+ * width: '100%',
1765
+ * backgroundImage: 'url(https://example.com/image.jpg)'
1766
+ * //...
1767
+ * }
1768
+ */
1769
+ const generatedCss = stringifyCssProperties(cfStyles);
1770
+ /* [Data format] `generatedCss` is the minimized CSS string that will be added to the DOM:
1771
+ * generatedCss = "margin: 1px;width: 100%;..."
1772
+ */
1806
1773
  // I create a hash of the object above because that would ensure hash stability
1807
1774
  // Adding breakpointId to ensure not using the same IDs between breakpoints as this leads to
1808
1775
  // conflicts between different breakpoint values from multiple nodes where the hash would be equal
1809
- const styleHash = md5(breakpointId + JSON.stringify(stylesForBreakpointWithoutUndefined));
1776
+ const styleHash = md5(breakpointId + generatedCss);
1810
1777
  // and prefix the className to make sure the value can be processed
1811
1778
  const className = `cf-${styleHash}`;
1812
1779
  // I save the generated hashes into an array to later save it in the tree node
@@ -1816,11 +1783,11 @@ const detachExperienceStyles = (experience) => {
1816
1783
  currentNodeClassNames.push(className);
1817
1784
  }
1818
1785
  // if there is already the similar hash - no need to over-write it
1819
- if (mediaQueriesTemplate[breakpointId].cssByClassName[className]) {
1786
+ if (mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className]) {
1820
1787
  continue;
1821
1788
  }
1822
1789
  // otherwise, save it to the stylesheet
1823
- mediaQueriesTemplate[breakpointId].cssByClassName[className] = toCSSString(stylesForBreakpointWithoutUndefined);
1790
+ mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className] = generatedCss;
1824
1791
  }
1825
1792
  // all generated classNames are saved in the tree node
1826
1793
  // to be handled by the sdk later
@@ -1864,10 +1831,8 @@ const detachExperienceStyles = (experience) => {
1864
1831
  });
1865
1832
  // once the whole tree was traversed, for each breakpoint, I aggregate the styles
1866
1833
  // 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;
1834
+ const stylesheet = Object.entries(mediaQueryDataByBreakpoint).reduce((acc, [, mediaQueryData]) => `${acc}${toMediaQuery(mediaQueryData)}`, '');
1835
+ return stylesheet;
1871
1836
  };
1872
1837
  /**
1873
1838
  * Rendering pattern nodes inside pattern entry by injecting default values from the top:
@@ -2042,6 +2007,41 @@ const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataS
2042
2007
  }
2043
2008
  }
2044
2009
  };
2010
+ /**
2011
+ * Takes the initial set of properties, filters only design properties that will be mapped to CSS and
2012
+ * re-organizes them to be indexed by breakpoint ID ("breakpoint > variable > value"). It will
2013
+ * also resolve the design/ component values to plain values.
2014
+ *
2015
+ * **Example Input**
2016
+ * ```
2017
+ * variables = {
2018
+ * cfMargin: { type: 'DesignValue', valuesByBreakpoint: { desktop: '1px', tablet: '2px' } },
2019
+ * cfPadding: { type: 'DesignValue', valuesByBreakpoint: { desktop: '3px', mobile: '4px' } }
2020
+ * }
2021
+ * ```
2022
+ *
2023
+ * **Example Output**
2024
+ * ```
2025
+ * variableValuesByBreakpoints = {
2026
+ * desktop: {
2027
+ * cfMargin: '1px',
2028
+ * cfPadding: '3px'
2029
+ * },
2030
+ * tablet: {
2031
+ * cfMargin: '2px'
2032
+ * },
2033
+ * mobile: {
2034
+ * cfPadding: '4px'
2035
+ * }
2036
+ * }
2037
+ * ```
2038
+ *
2039
+ * **Note**
2040
+ * - The property cfBackgroundImageUrl is the only content property that gets mapped to CSS as well.
2041
+ * It will be solely stored on the default breakpoint.
2042
+ * - For ComponentValues, it will either take the override from the pattern instance or fallback to
2043
+ * the defaultValue defined in variableDefinitions.
2044
+ */
2045
2045
  const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unboundValues = {}, dataSource = {}, componentVariablesOverwrites, componentSettings = { variableDefinitions: {} }, }) => {
2046
2046
  const variableValuesByBreakpoints = breakpointIds.reduce((acc, breakpointId) => {
2047
2047
  return {
@@ -2052,9 +2052,10 @@ const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unbou
2052
2052
  const defaultBreakpoint = breakpointIds[0];
2053
2053
  for (const [variableName, variableData] of Object.entries(variables)) {
2054
2054
  // 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
2055
+ // so, we need to resolve it here and pass it down as a css property to be converted into the CSS
2056
2056
  // I used .startsWith() cause it can be part of a pattern node
2057
2057
  if (variableName === 'cfBackgroundImageUrl' ||
2058
+ // TODO: Test this for nested patterns as the name might be just a random hash without the actual name (needs to be validated).
2058
2059
  variableName.startsWith('cfBackgroundImageUrl_')) {
2059
2060
  const imageUrl = resolveBackgroundImageBinding({
2060
2061
  variableData,
@@ -2095,17 +2096,9 @@ const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unbou
2095
2096
  };
2096
2097
  /**
2097
2098
  * Flattens the object from
2098
- * {
2099
- * color: {
2100
- * [key]: [value]
2101
- * }
2102
- * }
2103
- *
2099
+ * `{ color: { [key]: [value] } }`
2104
2100
  * to
2105
- *
2106
- * {
2107
- * 'color.key': [value]
2108
- * }
2101
+ * `{ 'color.key': [value] }`
2109
2102
  */
2110
2103
  const flattenDesignTokenRegistry = (designTokenRegistry) => {
2111
2104
  return Object.entries(designTokenRegistry).reduce((acc, [categoryName, tokenCategory]) => {
@@ -2121,24 +2114,21 @@ const flattenDesignTokenRegistry = (designTokenRegistry) => {
2121
2114
  };
2122
2115
  }, {});
2123
2116
  };
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]) => {
2117
+ /**
2118
+ * Create a single CSS string containing all class definitions for a given media query.
2119
+ *
2120
+ * @param condition e.g. "*", "<520px", ">520px"
2121
+ * @param cssByClassName map of class names to CSS strings containing all rules for each class
2122
+ * @returns joined string of all CSS class definitions wrapped into media queries
2123
+ */
2124
+ const toMediaQuery = ({ condition, cssByClassName }) => {
2125
+ const mediaQueryStyles = Object.entries(cssByClassName).reduce((acc, [className, css]) => {
2133
2126
  return `${acc}.${className}{${css}}`;
2134
2127
  }, ``);
2135
- if (breakpointPayload.condition === '*') {
2128
+ if (condition === '*') {
2136
2129
  return mediaQueryStyles;
2137
2130
  }
2138
- const [evaluation, pixelValue] = [
2139
- breakpointPayload.condition[0],
2140
- breakpointPayload.condition.substring(1),
2141
- ];
2131
+ const [evaluation, pixelValue] = [condition[0], condition.substring(1)];
2142
2132
  const mediaQueryRule = evaluation === '<' ? 'max-width' : 'min-width';
2143
2133
  return `@media(${mediaQueryRule}:${pixelValue}){${mediaQueryStyles}}`;
2144
2134
  };
@@ -3917,5 +3907,5 @@ async function fetchById({ client, experienceTypeId, id, localeCode, isEditorMod
3917
3907
  }
3918
3908
  }
3919
3909
 
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 };
3910
+ 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
3911
  //# sourceMappingURL=index.js.map