@contentful/experiences-visual-editor-react 3.1.1 → 3.2.0-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/renderApp.js CHANGED
@@ -44069,35 +44069,69 @@ const breakpointsRefinement$1 = (value, ctx) => {
44069
44069
  code: z.ZodIssueCode.custom,
44070
44070
  message: `The first breakpoint should include the following attributes: { "query": "*" }`,
44071
44071
  });
44072
+ return;
44072
44073
  }
44073
- const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
44074
- // check if the current breakpoint id is found in the rest of the array
44075
- const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
44076
- return breakpointIndex !== currentBreakpointIndex;
44077
- });
44074
+ // Return early if there's only one generic breakpoint
44075
+ const hasNoBreakpointsStrategy = value.length === 1;
44076
+ if (hasNoBreakpointsStrategy) {
44077
+ return;
44078
+ }
44079
+ // Check if any breakpoint id occurs twice
44080
+ const ids = value.map((breakpoint) => breakpoint.id);
44081
+ const hasDuplicateIds = new Set(ids).size !== ids.length;
44078
44082
  if (hasDuplicateIds) {
44079
44083
  ctx.addIssue({
44080
44084
  code: z.ZodIssueCode.custom,
44081
44085
  message: `Breakpoint IDs must be unique`,
44082
44086
  });
44087
+ return;
44083
44088
  }
44084
- // Extract the queries boundary by removing the special characters around it
44085
- const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
44086
- // sort updates queries array in place so we need to create a copy
44087
- const originalQueries = [...queries];
44088
- queries.sort((q1, q2) => {
44089
- if (q1 === '*') {
44090
- return -1;
44089
+ // Skip the first one which is guaranteed to be a wildcard query
44090
+ const nonBaseBreakpoints = value.slice(1);
44091
+ const isMobileFirstStrategy = nonBaseBreakpoints[0].query.startsWith('>');
44092
+ const isDesktopFirstStrategy = nonBaseBreakpoints[0].query.startsWith('<');
44093
+ if (isMobileFirstStrategy) {
44094
+ const areOperatorsEqual = nonBaseBreakpoints.every(({ query }) => query.startsWith('>'));
44095
+ if (!areOperatorsEqual) {
44096
+ ctx.addIssue({
44097
+ code: z.ZodIssueCode.custom,
44098
+ message: `Breakpoint queries must be in the format ">[size]px" for mobile-first strategy`,
44099
+ });
44091
44100
  }
44092
- if (q2 === '*') {
44093
- return 1;
44101
+ // Extract the queries boundary by removing the special characters around it
44102
+ const queries = nonBaseBreakpoints.map((bp) => parseInt(bp.query.replace(/px|<|>/, '')));
44103
+ // Starting with the third breakpoint, check that every query is higher than the one above
44104
+ const isIncreasing = queries.every((value, index, array) => index === 0 || value > array[index - 1]);
44105
+ if (!isIncreasing) {
44106
+ ctx.addIssue({
44107
+ code: z.ZodIssueCode.custom,
44108
+ message: `When using a mobile-first strategy, all breakpoints must have strictly increasing pixel values`,
44109
+ });
44094
44110
  }
44095
- return q1 > q2 ? -1 : 1;
44096
- });
44097
- if (originalQueries.join('') !== queries.join('')) {
44111
+ }
44112
+ else if (isDesktopFirstStrategy) {
44113
+ const areOperatorsEqual = nonBaseBreakpoints.every(({ query }) => query.startsWith('<'));
44114
+ if (!areOperatorsEqual) {
44115
+ ctx.addIssue({
44116
+ code: z.ZodIssueCode.custom,
44117
+ message: `Breakpoint queries must be in the format "<[size]px" for desktop-first strategy`,
44118
+ });
44119
+ }
44120
+ // Extract the queries boundary by removing the special characters around it
44121
+ const queries = nonBaseBreakpoints.map((bp) => parseInt(bp.query.replace(/px|<|>/, '')));
44122
+ // Starting with the third breakpoint, check that every query is lower than the one above
44123
+ const isDecreasing = queries.every((value, index, array) => index === 0 || value < array[index - 1]);
44124
+ if (!isDecreasing) {
44125
+ ctx.addIssue({
44126
+ code: z.ZodIssueCode.custom,
44127
+ message: `When using a desktop-first strategy, all breakpoints must have strictly decreasing pixel values`,
44128
+ });
44129
+ }
44130
+ }
44131
+ else if (!isMobileFirstStrategy && !isDesktopFirstStrategy) {
44098
44132
  ctx.addIssue({
44099
44133
  code: z.ZodIssueCode.custom,
44100
- message: `Breakpoints should be ordered from largest to smallest pixel value`,
44134
+ message: `You may only use a mobile-first or desktop-first strategy for breakpoints using '<' or '>' queries`,
44101
44135
  });
44102
44136
  }
44103
44137
  };
@@ -44156,7 +44190,7 @@ const ParametersSchema$1 = z.record(propertyKeySchema$1, ParameterSchema$1);
44156
44190
  const BreakpointSchema$1 = z
44157
44191
  .object({
44158
44192
  id: propertyKeySchema$1,
44159
- query: z.string().regex(/^\*$|^<[0-9*]+px$/),
44193
+ query: z.string().regex(/^\*$|^[<>][0-9*]+px$/),
44160
44194
  previewSize: z.string(),
44161
44195
  displayName: z.string(),
44162
44196
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
@@ -44434,7 +44468,7 @@ const toCSSMediaQuery = ({ query }) => {
44434
44468
  };
44435
44469
  // Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
44436
44470
  const findLast = (array, predicate) => {
44437
- return array.reverse().find(predicate);
44471
+ return [...array].reverse().find(predicate);
44438
44472
  };
44439
44473
  // Initialise media query matchers. This won't include the always matching fallback breakpoint.
44440
44474
  const mediaQueryMatcher = (breakpoints) => {
@@ -44454,19 +44488,19 @@ const mediaQueryMatcher = (breakpoints) => {
44454
44488
  return [mediaQueryMatchers, mediaQueryMatches];
44455
44489
  };
44456
44490
  const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
44457
- // The breakpoints are ordered (desktop-first: descending by screen width)
44491
+ // The breakpoints are ordered (desktop-first: descending by screen width, mobile-first: ascending by screen width).
44458
44492
  const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
44459
44493
  id,
44460
44494
  index,
44461
44495
  // The fallback breakpoint with wildcard query will always match
44462
44496
  isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
44463
44497
  }));
44464
- // Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
44498
+ // Find the last breakpoint in the list that matches (desktop-first: the narrowest one, mobile-first: the widest one)
44465
44499
  const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
44466
44500
  return mostSpecificIndex ?? fallbackBreakpointIndex;
44467
44501
  };
44468
44502
  const getFallbackBreakpointIndex = (breakpoints) => {
44469
- // We assume that there will be a single breakpoint which uses the wildcard query.
44503
+ // The validation ensures that there will be exactly one breakpoint using the wildcard query.
44470
44504
  // If there is none, we just take the first one in the list.
44471
44505
  return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
44472
44506
  };
@@ -48684,35 +48718,69 @@ const breakpointsRefinement = (value, ctx) => {
48684
48718
  code: z.ZodIssueCode.custom,
48685
48719
  message: `The first breakpoint should include the following attributes: { "query": "*" }`,
48686
48720
  });
48721
+ return;
48687
48722
  }
48688
- const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
48689
- // check if the current breakpoint id is found in the rest of the array
48690
- const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
48691
- return breakpointIndex !== currentBreakpointIndex;
48692
- });
48723
+ // Return early if there's only one generic breakpoint
48724
+ const hasNoBreakpointsStrategy = value.length === 1;
48725
+ if (hasNoBreakpointsStrategy) {
48726
+ return;
48727
+ }
48728
+ // Check if any breakpoint id occurs twice
48729
+ const ids = value.map((breakpoint) => breakpoint.id);
48730
+ const hasDuplicateIds = new Set(ids).size !== ids.length;
48693
48731
  if (hasDuplicateIds) {
48694
48732
  ctx.addIssue({
48695
48733
  code: z.ZodIssueCode.custom,
48696
48734
  message: `Breakpoint IDs must be unique`,
48697
48735
  });
48736
+ return;
48698
48737
  }
48699
- // Extract the queries boundary by removing the special characters around it
48700
- const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
48701
- // sort updates queries array in place so we need to create a copy
48702
- const originalQueries = [...queries];
48703
- queries.sort((q1, q2) => {
48704
- if (q1 === '*') {
48705
- return -1;
48738
+ // Skip the first one which is guaranteed to be a wildcard query
48739
+ const nonBaseBreakpoints = value.slice(1);
48740
+ const isMobileFirstStrategy = nonBaseBreakpoints[0].query.startsWith('>');
48741
+ const isDesktopFirstStrategy = nonBaseBreakpoints[0].query.startsWith('<');
48742
+ if (isMobileFirstStrategy) {
48743
+ const areOperatorsEqual = nonBaseBreakpoints.every(({ query }) => query.startsWith('>'));
48744
+ if (!areOperatorsEqual) {
48745
+ ctx.addIssue({
48746
+ code: z.ZodIssueCode.custom,
48747
+ message: `Breakpoint queries must be in the format ">[size]px" for mobile-first strategy`,
48748
+ });
48706
48749
  }
48707
- if (q2 === '*') {
48708
- return 1;
48750
+ // Extract the queries boundary by removing the special characters around it
48751
+ const queries = nonBaseBreakpoints.map((bp) => parseInt(bp.query.replace(/px|<|>/, '')));
48752
+ // Starting with the third breakpoint, check that every query is higher than the one above
48753
+ const isIncreasing = queries.every((value, index, array) => index === 0 || value > array[index - 1]);
48754
+ if (!isIncreasing) {
48755
+ ctx.addIssue({
48756
+ code: z.ZodIssueCode.custom,
48757
+ message: `When using a mobile-first strategy, all breakpoints must have strictly increasing pixel values`,
48758
+ });
48709
48759
  }
48710
- return q1 > q2 ? -1 : 1;
48711
- });
48712
- if (originalQueries.join('') !== queries.join('')) {
48760
+ }
48761
+ else if (isDesktopFirstStrategy) {
48762
+ const areOperatorsEqual = nonBaseBreakpoints.every(({ query }) => query.startsWith('<'));
48763
+ if (!areOperatorsEqual) {
48764
+ ctx.addIssue({
48765
+ code: z.ZodIssueCode.custom,
48766
+ message: `Breakpoint queries must be in the format "<[size]px" for desktop-first strategy`,
48767
+ });
48768
+ }
48769
+ // Extract the queries boundary by removing the special characters around it
48770
+ const queries = nonBaseBreakpoints.map((bp) => parseInt(bp.query.replace(/px|<|>/, '')));
48771
+ // Starting with the third breakpoint, check that every query is lower than the one above
48772
+ const isDecreasing = queries.every((value, index, array) => index === 0 || value < array[index - 1]);
48773
+ if (!isDecreasing) {
48774
+ ctx.addIssue({
48775
+ code: z.ZodIssueCode.custom,
48776
+ message: `When using a desktop-first strategy, all breakpoints must have strictly decreasing pixel values`,
48777
+ });
48778
+ }
48779
+ }
48780
+ else if (!isMobileFirstStrategy && !isDesktopFirstStrategy) {
48713
48781
  ctx.addIssue({
48714
48782
  code: z.ZodIssueCode.custom,
48715
- message: `Breakpoints should be ordered from largest to smallest pixel value`,
48783
+ message: `You may only use a mobile-first or desktop-first strategy for breakpoints using '<' or '>' queries`,
48716
48784
  });
48717
48785
  }
48718
48786
  };
@@ -48771,7 +48839,7 @@ const ParametersSchema = z.record(propertyKeySchema, ParameterSchema);
48771
48839
  const BreakpointSchema = z
48772
48840
  .object({
48773
48841
  id: propertyKeySchema,
48774
- query: z.string().regex(/^\*$|^<[0-9*]+px$/),
48842
+ query: z.string().regex(/^\*$|^[<>][0-9*]+px$/),
48775
48843
  previewSize: z.string(),
48776
48844
  displayName: z.string(),
48777
48845
  displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
@@ -50222,7 +50290,7 @@ const collectNodeCoordinates = (node, nodeToCoordinatesMap) => {
50222
50290
  node.children.forEach((child) => collectNodeCoordinates(child, nodeToCoordinatesMap));
50223
50291
  };
50224
50292
  function waitForImageToBeLoaded(imageNode) {
50225
- if (imageNode.complete) {
50293
+ if (imageNode.complete && (imageNode.naturalWidth > 0 || imageNode.naturalHeight > 0)) {
50226
50294
  return Promise.resolve();
50227
50295
  }
50228
50296
  return new Promise((resolve, reject) => {
@@ -50256,8 +50324,8 @@ const useCanvasGeometryUpdates = ({ tree, canvasMode }) => {
50256
50324
  trailing: true,
50257
50325
  }), []);
50258
50326
  const debouncedCollectImages = reactExports.useMemo(() => debounce(() => {
50259
- return Array.from(document.querySelectorAll('img'));
50260
- }, 300, { leading: true, trailing: true }), []);
50327
+ setImages((prev) => ({ ...prev, allImages: findAllImages() }));
50328
+ }, 300, { trailing: true }), []);
50261
50329
  // Store tree in a ref to avoid the need to deactivate & reactivate the mutation observer
50262
50330
  // when the tree changes. This is important to avoid missing out on some mutation events.
50263
50331
  const treeRef = reactExports.useRef(tree);
@@ -50266,13 +50334,17 @@ const useCanvasGeometryUpdates = ({ tree, canvasMode }) => {
50266
50334
  }, [tree]);
50267
50335
  // Handling window resize events
50268
50336
  reactExports.useEffect(() => {
50269
- const resizeEventListener = () => debouncedUpdateGeometry(treeRef.current, 'resize');
50337
+ const resizeEventListener = () => {
50338
+ debouncedUpdateGeometry(treeRef.current, 'resize');
50339
+ // find all images on resize
50340
+ debouncedCollectImages();
50341
+ };
50270
50342
  window.addEventListener('resize', resizeEventListener);
50271
50343
  return () => window.removeEventListener('resize', resizeEventListener);
50272
- }, [debouncedUpdateGeometry]);
50344
+ }, [debouncedCollectImages, debouncedUpdateGeometry]);
50273
50345
  const [{ allImages, loadedImages }, setImages] = reactExports.useState(() => {
50274
- const allImages = debouncedCollectImages();
50275
- const loadedImages = new WeakSet();
50346
+ const allImages = findAllImages();
50347
+ const loadedImages = new WeakMap();
50276
50348
  return { allImages, loadedImages };
50277
50349
  });
50278
50350
  // Handling DOM mutations
@@ -50280,8 +50352,7 @@ const useCanvasGeometryUpdates = ({ tree, canvasMode }) => {
50280
50352
  const observer = new MutationObserver(() => {
50281
50353
  debouncedUpdateGeometry(treeRef.current, 'mutation');
50282
50354
  // find all images on any DOM change
50283
- const allImages = debouncedCollectImages();
50284
- setImages((prevState) => ({ ...prevState, allImages }));
50355
+ debouncedCollectImages();
50285
50356
  });
50286
50357
  // send initial geometry in case the tree is empty
50287
50358
  debouncedUpdateGeometry(treeRef.current, 'mutation');
@@ -50297,13 +50368,14 @@ const useCanvasGeometryUpdates = ({ tree, canvasMode }) => {
50297
50368
  reactExports.useEffect(() => {
50298
50369
  let isCurrent = true;
50299
50370
  allImages.forEach(async (imageNode) => {
50300
- if (loadedImages.has(imageNode)) {
50371
+ const lastSrc = loadedImages.get(imageNode);
50372
+ if (lastSrc === imageNode.currentSrc) {
50301
50373
  return;
50302
50374
  }
50303
50375
  // update the geometry after each image is loaded, as it can shift the layout
50304
50376
  await waitForImageToBeLoaded(imageNode);
50305
50377
  if (isCurrent) {
50306
- loadedImages.add(imageNode);
50378
+ loadedImages.set(imageNode, imageNode.currentSrc);
50307
50379
  debouncedUpdateGeometry(treeRef.current, 'imageLoad');
50308
50380
  }
50309
50381
  });
@@ -50330,6 +50402,9 @@ const useCanvasGeometryUpdates = ({ tree, canvasMode }) => {
50330
50402
  return () => document.removeEventListener('wheel', onWheel);
50331
50403
  }, [canvasMode]);
50332
50404
  };
50405
+ function findAllImages() {
50406
+ return Array.from(document.querySelectorAll('img'));
50407
+ }
50333
50408
 
50334
50409
  const RootRenderer = ({ inMemoryEntitiesStore, canvasMode }) => {
50335
50410
  useEditorSubscriber(inMemoryEntitiesStore);