@contentful/experiences-core 1.40.2-dev-20250606T1202-905bb9e.0 → 1.40.2

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
@@ -9,7 +9,8 @@ export { Fieldset, UnresolvedFieldset, isDeepPath, lastPathNamedSegmentEq, parse
9
9
  export { addLocale, buildTemplate, getTemplateValue, resolveHyperlinkPattern } from './utils/resolveHyperlinkPattern.js';
10
10
  export { sanitizeNodeProps } from './utils/sanitizeNodeProps.js';
11
11
  export { addMinHeightForEmptyStructures, buildCfStyles, buildStyleTag, calculateNodeDefaultHeight, stringifyCssProperties, toCSSAttribute } from './utils/styleUtils/stylesUtils.js';
12
- export { detachExperienceStyles, flattenDesignTokenRegistry, indexByBreakpoint, isCfStyleAttribute, maybePopulateDesignTokenValue, resolveBackgroundImageBinding, toMediaQuery } from './utils/styleUtils/ssrStyles.js';
12
+ export { detachExperienceStyles, flattenDesignTokenRegistry, indexByBreakpoint, isCfStyleAttribute, maybePopulateDesignTokenValue, resolveBackgroundImageBinding } from './utils/styleUtils/ssrStyles.js';
13
+ export { toMediaQuery } from './utils/styleUtils/toMediaQuery.js';
13
14
  export { transformVisibility } from './utils/styleUtils/styleTransformers.js';
14
15
  export { transformBoundContentValue } from './utils/transformers/transformBoundContentValue.js';
15
16
  export { treeMap, treeVisit } from './utils/treeTraversal.js';
package/dist/index.js CHANGED
@@ -2103,6 +2103,55 @@ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', fo
2103
2103
  }
2104
2104
  };
2105
2105
 
2106
+ /**
2107
+ * Turns a condition like `<768px` or `>1024px` into a media query rule.
2108
+ * For example, `<768px` becomes `max-width:768px` and `>1024px` becomes `min-width:1024px`.
2109
+ */
2110
+ const toMediaQueryRule = (condition) => {
2111
+ const [evaluation, pixelValue] = [condition[0], condition.substring(1)];
2112
+ const mediaQueryRule = evaluation === '<' ? 'max-width' : 'min-width';
2113
+ return `(${mediaQueryRule}:${pixelValue})`;
2114
+ };
2115
+ /**
2116
+ * Converts a map of class names to CSS strings into a single CSS string.
2117
+ *
2118
+ * @param cssByClassName map of class names to CSS strings containing all rules for each class
2119
+ * @returns joined string of all CSS class definitions
2120
+ */
2121
+ const toCompoundCss = (cssByClassName) => {
2122
+ return Object.entries(cssByClassName).reduce((acc, [className, css]) => {
2123
+ if (css === '')
2124
+ return acc;
2125
+ return `${acc}.${className}{${css}}`;
2126
+ }, ``);
2127
+ };
2128
+ /**
2129
+ * Create a single CSS string containing all class definitions for a given media query.
2130
+ *
2131
+ * @param cssByClassName map of class names to CSS strings containing all rules for each class
2132
+ * @param condition e.g. "*", "<520px", ">520px"
2133
+ * @param nextCondition optional next condition to create a disjunct media query that doesn't affect the next breakpoint
2134
+ * @returns joined string of all CSS class definitions wrapped into media queries
2135
+ */
2136
+ const toMediaQuery = ({ cssByClassName, condition, nextCondition, }) => {
2137
+ const compoundCss = toCompoundCss(cssByClassName);
2138
+ if (compoundCss === '') {
2139
+ return '';
2140
+ }
2141
+ const queryRule = toMediaQueryRule(condition);
2142
+ if (!nextCondition) {
2143
+ if (condition === '*') {
2144
+ return compoundCss;
2145
+ }
2146
+ return `@media${queryRule}{${compoundCss}}`;
2147
+ }
2148
+ const nextRule = toMediaQueryRule(nextCondition);
2149
+ if (condition === '*') {
2150
+ return `@media not ${nextRule}{${compoundCss}}`;
2151
+ }
2152
+ return `@media${queryRule} and (not ${nextRule}){${compoundCss}}`;
2153
+ };
2154
+
2106
2155
  const detachExperienceStyles = (experience) => {
2107
2156
  const experienceTreeRoot = experience.entityStore?.experienceEntryFields
2108
2157
  ?.componentTree;
@@ -2118,6 +2167,7 @@ const detachExperienceStyles = (experience) => {
2118
2167
  [breakpoint.id]: {
2119
2168
  condition: breakpoint.query,
2120
2169
  cssByClassName: {},
2170
+ visibilityCssByClassName: {},
2121
2171
  },
2122
2172
  }), {});
2123
2173
  // getting the breakpoint ids
@@ -2194,6 +2244,11 @@ const detachExperienceStyles = (experience) => {
2194
2244
  return experience.entityStore?.entities.find((entity) => entity.sys.id === id);
2195
2245
  },
2196
2246
  });
2247
+ // When the node is hidden for any breakpoint, we need to handle this separately with a disjunct media query.
2248
+ const isAnyVisibilityValueHidden = Object.values(propsByBreakpoint).some((designProperties) => designProperties.cfVisibility === false);
2249
+ // We always need an explicit value when using disjunct media queries
2250
+ // Example: desktop uses "false" and tablet is undefined -> we need to set `display: none` for tablet as well.
2251
+ let previousVisibilityValue;
2197
2252
  /* [Data format] `propsByBreakpoint` is a map of "breakpointId > propertyName > plainValue":
2198
2253
  * {
2199
2254
  * desktop: {
@@ -2244,12 +2299,21 @@ const detachExperienceStyles = (experience) => {
2244
2299
  if (!currentNodeClassNames.includes(className)) {
2245
2300
  currentNodeClassNames.push(className);
2246
2301
  }
2247
- // if there is already the similar hash - no need to over-write it
2248
- if (mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className]) {
2249
- continue;
2302
+ // Only if the hash was not used yet, save the CSS to the stylesheet
2303
+ if (!mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className]) {
2304
+ mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className] = generatedCss;
2305
+ }
2306
+ // Special case for visibility to override any custom `display` values but only for a specific breakpoint.
2307
+ if (isAnyVisibilityValueHidden) {
2308
+ const visibilityValue = propsByBreakpointWithResolvedDesignTokens.cfVisibility ?? previousVisibilityValue;
2309
+ previousVisibilityValue = visibilityValue;
2310
+ const visibilityStyles = transformVisibility(visibilityValue);
2311
+ const visibilityCss = stringifyCssProperties(visibilityStyles);
2312
+ if (!mediaQueryDataByBreakpoint[breakpointId].visibilityCssByClassName[className]) {
2313
+ mediaQueryDataByBreakpoint[breakpointId].visibilityCssByClassName[className] =
2314
+ visibilityCss;
2315
+ }
2250
2316
  }
2251
- // otherwise, save it to the stylesheet
2252
- mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className] = generatedCss;
2253
2317
  }
2254
2318
  // all generated classNames are saved in the tree node
2255
2319
  // to be handled by the sdk later
@@ -2291,7 +2355,18 @@ const detachExperienceStyles = (experience) => {
2291
2355
  });
2292
2356
  // once the whole tree was traversed, for each breakpoint, I aggregate the styles
2293
2357
  // for each generated className into one css string
2294
- const stylesheet = Object.entries(mediaQueryDataByBreakpoint).reduce((acc, [, mediaQueryData]) => `${acc}${toMediaQuery(mediaQueryData)}`, '');
2358
+ const stylesheet = Object.values(mediaQueryDataByBreakpoint).reduce((acc, { condition, cssByClassName, visibilityCssByClassName }, index) => {
2359
+ const mediaQueryCss = toMediaQuery({ cssByClassName, condition });
2360
+ // Handle visibility separately to use disjunct media queries ("if desktop but not tablet ...")
2361
+ // Enables to hide on one breakpoint but render any unknown custom `display` value on another breakpoint.
2362
+ const visibilityMediaQueryCss = toMediaQuery({
2363
+ cssByClassName: visibilityCssByClassName,
2364
+ condition,
2365
+ // Breakpoint validation ensures that it starts with the '*' breakpoint
2366
+ nextCondition: Object.values(mediaQueryDataByBreakpoint)[index + 1]?.condition,
2367
+ });
2368
+ return `${acc}${mediaQueryCss}${visibilityMediaQueryCss}`;
2369
+ }, '');
2295
2370
  return stylesheet;
2296
2371
  };
2297
2372
  /**
@@ -2635,24 +2710,6 @@ const flattenDesignTokenRegistry = (designTokenRegistry) => {
2635
2710
  };
2636
2711
  }, {});
2637
2712
  };
2638
- /**
2639
- * Create a single CSS string containing all class definitions for a given media query.
2640
- *
2641
- * @param condition e.g. "*", "<520px", ">520px"
2642
- * @param cssByClassName map of class names to CSS strings containing all rules for each class
2643
- * @returns joined string of all CSS class definitions wrapped into media queries
2644
- */
2645
- const toMediaQuery = ({ condition, cssByClassName }) => {
2646
- const mediaQueryStyles = Object.entries(cssByClassName).reduce((acc, [className, css]) => {
2647
- return `${acc}.${className}{${css}}`;
2648
- }, ``);
2649
- if (condition === '*') {
2650
- return mediaQueryStyles;
2651
- }
2652
- const [evaluation, pixelValue] = [condition[0], condition.substring(1)];
2653
- const mediaQueryRule = evaluation === '<' ? 'max-width' : 'min-width';
2654
- return `@media(${mediaQueryRule}:${pixelValue}){${mediaQueryStyles}}`;
2655
- };
2656
2713
 
2657
2714
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2658
2715
  function get(obj, path) {