@contentful/experiences-core 3.1.1 → 3.2.0-dev-20250812T1437-66fa8cb.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,5 +1,5 @@
1
1
  export { isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isPatternComponent, isStructureWithRelativeHeight } from './utils/components.js';
2
- export { MEDIA_QUERY_REGEXP, getActiveBreakpointIndex, getFallbackBreakpointIndex, getValueForBreakpoint, isValidBreakpointValue, mediaQueryMatcher, mergeDesignValuesByBreakpoint } from './utils/breakpoints.js';
2
+ export { BREAKPOINTS_STRATEGY_DESKTOP_FIRST, BREAKPOINTS_STRATEGY_MOBILE_FIRST, BreakpointsStrategy, MEDIA_QUERY_REGEXP, detectBreakpointsStrategy, getActiveBreakpointIndex, getFallbackBreakpointIndex, getValueForBreakpoint, isValidBreakpointValue, mediaQueryMatcher, mergeDesignValuesByBreakpoint } from './utils/breakpoints.js';
3
3
  export { DebugLogger, debug, disableDebug, enableDebug } from './utils/debugLogger.js';
4
4
  export { findOutermostCoordinates, getElementCoordinates, isElementHidden } from './utils/domValues.js';
5
5
  export { isLinkToAsset } from './utils/isLinkToAsset.js';
package/dist/index.js CHANGED
@@ -875,35 +875,69 @@ const breakpointsRefinement = (value, ctx) => {
875
875
  code: z.ZodIssueCode.custom,
876
876
  message: `The first breakpoint should include the following attributes: { "query": "*" }`,
877
877
  });
878
+ return;
878
879
  }
879
- const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
880
- // check if the current breakpoint id is found in the rest of the array
881
- const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
882
- return breakpointIndex !== currentBreakpointIndex;
883
- });
880
+ // Return early if there's only one generic breakpoint
881
+ const hasNoBreakpointsStrategy = value.length === 1;
882
+ if (hasNoBreakpointsStrategy) {
883
+ return;
884
+ }
885
+ // Check if any breakpoint id occurs twice
886
+ const ids = value.map((breakpoint) => breakpoint.id);
887
+ const hasDuplicateIds = new Set(ids).size !== ids.length;
884
888
  if (hasDuplicateIds) {
885
889
  ctx.addIssue({
886
890
  code: z.ZodIssueCode.custom,
887
891
  message: `Breakpoint IDs must be unique`,
888
892
  });
893
+ return;
889
894
  }
890
- // Extract the queries boundary by removing the special characters around it
891
- const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
892
- // sort updates queries array in place so we need to create a copy
893
- const originalQueries = [...queries];
894
- queries.sort((q1, q2) => {
895
- if (q1 === '*') {
896
- return -1;
895
+ // Skip the first one which is guaranteed to be a wildcard query
896
+ const nonBaseBreakpoints = value.slice(1);
897
+ const isMobileFirstStrategy = nonBaseBreakpoints[0].query.startsWith('>');
898
+ const isDesktopFirstStrategy = nonBaseBreakpoints[0].query.startsWith('<');
899
+ if (isMobileFirstStrategy) {
900
+ const areOperatorsEqual = nonBaseBreakpoints.every(({ query }) => query.startsWith('>'));
901
+ if (!areOperatorsEqual) {
902
+ ctx.addIssue({
903
+ code: z.ZodIssueCode.custom,
904
+ message: `Breakpoint queries must be in the format ">[size]px" for mobile-first strategy`,
905
+ });
897
906
  }
898
- if (q2 === '*') {
899
- return 1;
907
+ // Extract the queries boundary by removing the special characters around it
908
+ const queries = nonBaseBreakpoints.map((bp) => parseInt(bp.query.replace(/px|<|>/, '')));
909
+ // Starting with the third breakpoint, check that every query is higher than the one above
910
+ const isIncreasing = queries.every((value, index, array) => index === 0 || value > array[index - 1]);
911
+ if (!isIncreasing) {
912
+ ctx.addIssue({
913
+ code: z.ZodIssueCode.custom,
914
+ message: `When using a mobile-first strategy, all breakpoints must have strictly increasing pixel values`,
915
+ });
900
916
  }
901
- return q1 > q2 ? -1 : 1;
902
- });
903
- if (originalQueries.join('') !== queries.join('')) {
917
+ }
918
+ else if (isDesktopFirstStrategy) {
919
+ const areOperatorsEqual = nonBaseBreakpoints.every(({ query }) => query.startsWith('<'));
920
+ if (!areOperatorsEqual) {
921
+ ctx.addIssue({
922
+ code: z.ZodIssueCode.custom,
923
+ message: `Breakpoint queries must be in the format "<[size]px" for desktop-first strategy`,
924
+ });
925
+ }
926
+ // Extract the queries boundary by removing the special characters around it
927
+ const queries = nonBaseBreakpoints.map((bp) => parseInt(bp.query.replace(/px|<|>/, '')));
928
+ // Starting with the third breakpoint, check that every query is lower than the one above
929
+ const isDecreasing = queries.every((value, index, array) => index === 0 || value < array[index - 1]);
930
+ if (!isDecreasing) {
931
+ ctx.addIssue({
932
+ code: z.ZodIssueCode.custom,
933
+ message: `When using a desktop-first strategy, all breakpoints must have strictly decreasing pixel values`,
934
+ });
935
+ }
936
+ }
937
+ else if (!isMobileFirstStrategy && !isDesktopFirstStrategy) {
904
938
  ctx.addIssue({
905
939
  code: z.ZodIssueCode.custom,
906
- message: `Breakpoints should be ordered from largest to smallest pixel value`,
940
+ message: `You may only use a mobile-first or desktop-first strategy for breakpoints using '<' or '>' queries`,
907
941
  });
908
942
  }
909
943
  };
@@ -962,7 +996,7 @@ const ParametersSchema = z.record(propertyKeySchema, ParameterSchema);
962
996
  const BreakpointSchema = z
963
997
  .object({
964
998
  id: propertyKeySchema,
965
- query: z.string().regex(/^\*$|^<[0-9*]+px$/),
999
+ query: z.string().regex(/^\*$|^[<>][0-9*]+px$/),
966
1000
  previewSize: z.string(),
967
1001
  displayName: z.string(),
968
1002
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
@@ -1368,7 +1402,7 @@ const toCSSMediaQuery = ({ query }) => {
1368
1402
  };
1369
1403
  // Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
1370
1404
  const findLast = (array, predicate) => {
1371
- return array.reverse().find(predicate);
1405
+ return [...array].reverse().find(predicate);
1372
1406
  };
1373
1407
  // Initialise media query matchers. This won't include the always matching fallback breakpoint.
1374
1408
  const mediaQueryMatcher = (breakpoints) => {
@@ -1388,19 +1422,19 @@ const mediaQueryMatcher = (breakpoints) => {
1388
1422
  return [mediaQueryMatchers, mediaQueryMatches];
1389
1423
  };
1390
1424
  const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
1391
- // The breakpoints are ordered (desktop-first: descending by screen width)
1425
+ // The breakpoints are ordered (desktop-first: descending by screen width, mobile-first: ascending by screen width).
1392
1426
  const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
1393
1427
  id,
1394
1428
  index,
1395
1429
  // The fallback breakpoint with wildcard query will always match
1396
1430
  isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
1397
1431
  }));
1398
- // Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
1432
+ // Find the last breakpoint in the list that matches (desktop-first: the narrowest one, mobile-first: the widest one)
1399
1433
  const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
1400
1434
  return mostSpecificIndex ?? fallbackBreakpointIndex;
1401
1435
  };
1402
1436
  const getFallbackBreakpointIndex = (breakpoints) => {
1403
- // We assume that there will be a single breakpoint which uses the wildcard query.
1437
+ // The validation ensures that there will be exactly one breakpoint using the wildcard query.
1404
1438
  // If there is none, we just take the first one in the list.
1405
1439
  return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
1406
1440
  };
@@ -1478,6 +1512,28 @@ function mergeDesignValuesByBreakpoint(defaultValue, overwriteValue) {
1478
1512
  valuesByBreakpoint: mergedValuesByBreakpoint,
1479
1513
  };
1480
1514
  }
1515
+ const BREAKPOINTS_STRATEGY_DESKTOP_FIRST = 'desktop-first';
1516
+ const BREAKPOINTS_STRATEGY_MOBILE_FIRST = 'mobile-first';
1517
+ /**
1518
+ * Detects the breakpoint strategy based on the provided breakpoints.
1519
+ *
1520
+ * @param breakpoints The array of breakpoints to analyze.
1521
+ * @returns The detected breakpoint strategy or undefined if not determinable.
1522
+ */
1523
+ const detectBreakpointsStrategy = (breakpoints) => {
1524
+ if (breakpoints.length < 2) {
1525
+ return undefined;
1526
+ }
1527
+ const hasMobileFirst = breakpoints.slice(1).every((bp) => bp.query.startsWith('>'));
1528
+ if (hasMobileFirst) {
1529
+ return BREAKPOINTS_STRATEGY_MOBILE_FIRST;
1530
+ }
1531
+ const hasDesktopFirst = breakpoints.slice(1).every((bp) => bp.query.startsWith('<'));
1532
+ if (hasDesktopFirst) {
1533
+ return BREAKPOINTS_STRATEGY_DESKTOP_FIRST;
1534
+ }
1535
+ return undefined;
1536
+ };
1481
1537
 
1482
1538
  const CF_DEBUG_KEY = 'cf_debug';
1483
1539
  /**
@@ -4668,5 +4724,5 @@ async function fetchById({ client, experienceTypeId, id, localeCode, isEditorMod
4668
4724
  }
4669
4725
  }
4670
4726
 
4671
- export { DebugLogger, DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, addMinHeightForEmptyStructures, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, containerBuiltInStyles, createExperience, debug, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, disableDebug, dividerBuiltInStyles, doesMismatchMessageSchema, enableDebug, extractLeafLinksReferencedFromExperience, extractReferencesFromEntries, extractReferencesFromEntriesAsIds, fetchAllAssets, fetchAllEntries, fetchById, fetchBySlug, fetchExperienceEntry, fetchReferencedEntities, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getTargetValueInPixels, getTemplateValue, getValueForBreakpoint, inMemoryEntities, inMemoryEntitiesStore, indexByBreakpoint, isArrayOfLinks, isAsset, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isDeepPath, isElementHidden, isEntry, isExperienceEntry, isLink, isLinkToAsset, isLinkToEntry, isPatternComponent, isPatternEntry, isStructureWithRelativeHeight, isValidBreakpointValue, lastPathNamedSegmentEq, localizeEntity, maybePopulateDesignTokenValue, mediaQueryMatcher, mergeDesignValuesByBreakpoint, optionalBuiltInStyles, parseCSSValue, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, referencesOf, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sanitizeNodeProps, sectionBuiltInStyles, sendMessage, singleColumnBuiltInStyles, stringifyCssProperties, toCSSAttribute, toMediaQuery, transformBoundContentValue, transformVisibility, treeMap, treeVisit, tryParseMessage, uniqueById, useInMemoryEntities, validateExperienceBuilderConfig };
4727
+ export { BREAKPOINTS_STRATEGY_DESKTOP_FIRST, BREAKPOINTS_STRATEGY_MOBILE_FIRST, DebugLogger, DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, addMinHeightForEmptyStructures, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, containerBuiltInStyles, createExperience, debug, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, detectBreakpointsStrategy, disableDebug, dividerBuiltInStyles, doesMismatchMessageSchema, enableDebug, extractLeafLinksReferencedFromExperience, extractReferencesFromEntries, extractReferencesFromEntriesAsIds, fetchAllAssets, fetchAllEntries, fetchById, fetchBySlug, fetchExperienceEntry, fetchReferencedEntities, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getTargetValueInPixels, getTemplateValue, getValueForBreakpoint, inMemoryEntities, inMemoryEntitiesStore, indexByBreakpoint, isArrayOfLinks, isAsset, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isDeepPath, isElementHidden, isEntry, isExperienceEntry, isLink, isLinkToAsset, isLinkToEntry, isPatternComponent, isPatternEntry, isStructureWithRelativeHeight, isValidBreakpointValue, lastPathNamedSegmentEq, localizeEntity, maybePopulateDesignTokenValue, mediaQueryMatcher, mergeDesignValuesByBreakpoint, optionalBuiltInStyles, parseCSSValue, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, referencesOf, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sanitizeNodeProps, sectionBuiltInStyles, sendMessage, singleColumnBuiltInStyles, stringifyCssProperties, toCSSAttribute, toMediaQuery, transformBoundContentValue, transformVisibility, treeMap, treeVisit, tryParseMessage, uniqueById, useInMemoryEntities, validateExperienceBuilderConfig };
4672
4728
  //# sourceMappingURL=index.js.map