@contentful/experiences-core 1.40.1 → 1.40.2-beta.1

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
@@ -1453,10 +1453,11 @@ const CF_DEBUG_KEY = 'cf_debug';
1453
1453
  * SecurityError (e.g. Brave or Chromium with specific settings).
1454
1454
  */
1455
1455
  const checkLocalStorageAvailability = () => {
1456
- if (typeof localStorage === 'undefined' || localStorage === null) {
1457
- return false;
1458
- }
1459
1456
  try {
1457
+ // Even the typeof check can throw an error in an agressive browser like Brave (requires using the deprecated flag #block-all-cookies-toggle)
1458
+ if (typeof localStorage === 'undefined' || localStorage === null) {
1459
+ return false;
1460
+ }
1460
1461
  // Attempt to set and remove an item to check if localStorage is enabled
1461
1462
  const TEST_KEY = 'cf_test_local_storage';
1462
1463
  localStorage.setItem(TEST_KEY, 'yes');
@@ -2102,6 +2103,55 @@ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', fo
2102
2103
  }
2103
2104
  };
2104
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
+
2105
2155
  const detachExperienceStyles = (experience) => {
2106
2156
  const experienceTreeRoot = experience.entityStore?.experienceEntryFields
2107
2157
  ?.componentTree;
@@ -2117,6 +2167,7 @@ const detachExperienceStyles = (experience) => {
2117
2167
  [breakpoint.id]: {
2118
2168
  condition: breakpoint.query,
2119
2169
  cssByClassName: {},
2170
+ visibilityCssByClassName: {},
2120
2171
  },
2121
2172
  }), {});
2122
2173
  // getting the breakpoint ids
@@ -2193,6 +2244,11 @@ const detachExperienceStyles = (experience) => {
2193
2244
  return experience.entityStore?.entities.find((entity) => entity.sys.id === id);
2194
2245
  },
2195
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;
2196
2252
  /* [Data format] `propsByBreakpoint` is a map of "breakpointId > propertyName > plainValue":
2197
2253
  * {
2198
2254
  * desktop: {
@@ -2243,12 +2299,21 @@ const detachExperienceStyles = (experience) => {
2243
2299
  if (!currentNodeClassNames.includes(className)) {
2244
2300
  currentNodeClassNames.push(className);
2245
2301
  }
2246
- // if there is already the similar hash - no need to over-write it
2247
- if (mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className]) {
2248
- 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
+ }
2249
2316
  }
2250
- // otherwise, save it to the stylesheet
2251
- mediaQueryDataByBreakpoint[breakpointId].cssByClassName[className] = generatedCss;
2252
2317
  }
2253
2318
  // all generated classNames are saved in the tree node
2254
2319
  // to be handled by the sdk later
@@ -2290,7 +2355,18 @@ const detachExperienceStyles = (experience) => {
2290
2355
  });
2291
2356
  // once the whole tree was traversed, for each breakpoint, I aggregate the styles
2292
2357
  // for each generated className into one css string
2293
- 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
+ }, '');
2294
2370
  return stylesheet;
2295
2371
  };
2296
2372
  /**
@@ -2634,24 +2710,6 @@ const flattenDesignTokenRegistry = (designTokenRegistry) => {
2634
2710
  };
2635
2711
  }, {});
2636
2712
  };
2637
- /**
2638
- * Create a single CSS string containing all class definitions for a given media query.
2639
- *
2640
- * @param condition e.g. "*", "<520px", ">520px"
2641
- * @param cssByClassName map of class names to CSS strings containing all rules for each class
2642
- * @returns joined string of all CSS class definitions wrapped into media queries
2643
- */
2644
- const toMediaQuery = ({ condition, cssByClassName }) => {
2645
- const mediaQueryStyles = Object.entries(cssByClassName).reduce((acc, [className, css]) => {
2646
- return `${acc}.${className}{${css}}`;
2647
- }, ``);
2648
- if (condition === '*') {
2649
- return mediaQueryStyles;
2650
- }
2651
- const [evaluation, pixelValue] = [condition[0], condition.substring(1)];
2652
- const mediaQueryRule = evaluation === '<' ? 'max-width' : 'min-width';
2653
- return `@media(${mediaQueryRule}:${pixelValue}){${mediaQueryStyles}}`;
2654
- };
2655
2713
 
2656
2714
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2657
2715
  function get(obj, path) {