@contentful/experiences-visual-editor-react 1.36.0-beta.0 → 1.36.0-beta.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.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import styleInject from 'style-inject';
2
2
  import React, { useEffect, useRef, useState, useCallback, forwardRef, useLayoutEffect, useMemo } from 'react';
3
- import md5 from 'md5';
4
3
  import { z } from 'zod';
5
- import { BLOCKS } from '@contentful/rich-text-types';
6
4
  import { omit, isArray, isEqual, get as get$1 } from 'lodash-es';
5
+ import md5 from 'md5';
6
+ import { BLOCKS } from '@contentful/rich-text-types';
7
7
  import { create } from 'zustand';
8
8
  import { Droppable, Draggable, DragDropContext } from '@hello-pangea/dnd';
9
9
  import { produce } from 'immer';
@@ -163,497 +163,181 @@ const isStructureWithRelativeHeight = (componentId, height) => {
163
163
  return isContentfulStructureComponent(componentId) && !height?.toString().endsWith('px');
164
164
  };
165
165
 
166
- const findOutermostCoordinates = (first, second) => {
167
- return {
168
- top: Math.min(first.top, second.top),
169
- right: Math.max(first.right, second.right),
170
- bottom: Math.max(first.bottom, second.bottom),
171
- left: Math.min(first.left, second.left),
172
- };
173
- };
174
- const getElementCoordinates = (element) => {
175
- const rect = element.getBoundingClientRect();
176
- /**
177
- * If element does not have children, or element has it's own width or height,
178
- * return the element's coordinates.
179
- */
180
- if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
181
- return rect;
182
- }
183
- const rects = [];
184
- /**
185
- * If element has children, or element does not have it's own width and height,
186
- * we find the cordinates of the children, and assume the outermost coordinates of the children
187
- * as the coordinate of the element.
188
- *
189
- * E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
190
- * The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
191
- */
192
- for (const child of element.children) {
193
- const childRect = getElementCoordinates(child);
194
- if (childRect.width !== 0 || childRect.height !== 0) {
195
- const { top, right, bottom, left } = childRect;
196
- rects.push({ top, right, bottom, left });
197
- }
198
- }
199
- if (rects.length === 0) {
200
- return rect;
201
- }
202
- const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
203
- return DOMRect.fromRect({
204
- x: left,
205
- y: top,
206
- height: bottom - top,
207
- width: right - left,
208
- });
209
- };
210
-
211
- class ParseError extends Error {
212
- constructor(message) {
213
- super(message);
214
- }
215
- }
216
- const isValidJsonObject = (s) => {
217
- try {
218
- const result = JSON.parse(s);
219
- if ('object' !== typeof result) {
220
- return false;
221
- }
222
- return true;
223
- }
224
- catch (e) {
225
- return false;
226
- }
227
- };
228
- const doesMismatchMessageSchema = (event) => {
229
- try {
230
- tryParseMessage(event);
231
- return false;
232
- }
233
- catch (e) {
234
- if (e instanceof ParseError) {
235
- return e.message;
236
- }
237
- throw e;
238
- }
239
- };
240
- const tryParseMessage = (event) => {
241
- if (!event.data) {
242
- throw new ParseError('Field event.data is missing');
243
- }
244
- if ('string' !== typeof event.data) {
245
- throw new ParseError(`Field event.data must be a string, instead of '${typeof event.data}'`);
246
- }
247
- if (!isValidJsonObject(event.data)) {
248
- throw new ParseError('Field event.data must be a valid JSON object serialized as string');
249
- }
250
- const eventData = JSON.parse(event.data);
251
- if (!eventData.source) {
252
- throw new ParseError(`Field eventData.source must be equal to 'composability-app'`);
253
- }
254
- if ('composability-app' !== eventData.source) {
255
- throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
256
- }
257
- // check eventData.eventType
258
- const supportedEventTypes = Object.values(INCOMING_EVENTS$1);
259
- if (!supportedEventTypes.includes(eventData.eventType)) {
260
- // Expected message: This message is handled in the EntityStore to store fetched entities
261
- if (eventData.eventType !== PostMessageMethods$3.REQUESTED_ENTITIES) {
262
- throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
263
- }
264
- }
265
- return eventData;
166
+ // These styles get added to every component, user custom or contentful provided
167
+ const builtInStyles = {
168
+ cfVerticalAlignment: {
169
+ validations: {
170
+ in: [
171
+ {
172
+ value: 'start',
173
+ displayName: 'Align left',
174
+ },
175
+ {
176
+ value: 'center',
177
+ displayName: 'Align center',
178
+ },
179
+ {
180
+ value: 'end',
181
+ displayName: 'Align right',
182
+ },
183
+ ],
184
+ },
185
+ type: 'Text',
186
+ group: 'style',
187
+ description: 'The vertical alignment of the section',
188
+ defaultValue: 'center',
189
+ displayName: 'Vertical alignment',
190
+ },
191
+ cfHorizontalAlignment: {
192
+ validations: {
193
+ in: [
194
+ {
195
+ value: 'start',
196
+ displayName: 'Align top',
197
+ },
198
+ {
199
+ value: 'center',
200
+ displayName: 'Align center',
201
+ },
202
+ {
203
+ value: 'end',
204
+ displayName: 'Align bottom',
205
+ },
206
+ ],
207
+ },
208
+ type: 'Text',
209
+ group: 'style',
210
+ description: 'The horizontal alignment of the section',
211
+ defaultValue: 'center',
212
+ displayName: 'Horizontal alignment',
213
+ },
214
+ cfVisibility: {
215
+ displayName: 'Visibility toggle',
216
+ type: 'Boolean',
217
+ group: 'style',
218
+ defaultValue: true,
219
+ description: 'The visibility of the component',
220
+ },
221
+ cfMargin: {
222
+ displayName: 'Margin',
223
+ type: 'Text',
224
+ group: 'style',
225
+ description: 'The margin of the section',
226
+ defaultValue: '0 0 0 0',
227
+ },
228
+ cfPadding: {
229
+ displayName: 'Padding',
230
+ type: 'Text',
231
+ group: 'style',
232
+ description: 'The padding of the section',
233
+ defaultValue: '0 0 0 0',
234
+ },
235
+ cfBackgroundColor: {
236
+ displayName: 'Background color',
237
+ type: 'Text',
238
+ group: 'style',
239
+ description: 'The background color of the section',
240
+ defaultValue: 'rgba(0, 0, 0, 0)',
241
+ },
242
+ cfWidth: {
243
+ displayName: 'Width',
244
+ type: 'Text',
245
+ group: 'style',
246
+ description: 'The width of the section',
247
+ defaultValue: '100%',
248
+ },
249
+ cfHeight: {
250
+ displayName: 'Height',
251
+ type: 'Text',
252
+ group: 'style',
253
+ description: 'The height of the section',
254
+ defaultValue: 'fit-content',
255
+ },
256
+ cfMaxWidth: {
257
+ displayName: 'Max width',
258
+ type: 'Text',
259
+ group: 'style',
260
+ description: 'The max-width of the section',
261
+ defaultValue: 'none',
262
+ },
263
+ cfFlexDirection: {
264
+ displayName: 'Direction',
265
+ type: 'Text',
266
+ group: 'style',
267
+ description: 'The orientation of the section',
268
+ defaultValue: 'column',
269
+ },
270
+ cfFlexReverse: {
271
+ displayName: 'Reverse Direction',
272
+ type: 'Boolean',
273
+ group: 'style',
274
+ description: 'Toggle the flex direction to be reversed',
275
+ defaultValue: false,
276
+ },
277
+ cfFlexWrap: {
278
+ displayName: 'Wrap objects',
279
+ type: 'Text',
280
+ group: 'style',
281
+ description: 'Wrap objects',
282
+ defaultValue: 'nowrap',
283
+ },
284
+ cfBorder: {
285
+ displayName: 'Border',
286
+ type: 'Text',
287
+ group: 'style',
288
+ description: 'The border of the section',
289
+ defaultValue: '0px solid rgba(0, 0, 0, 0)',
290
+ },
291
+ cfGap: {
292
+ displayName: 'Gap',
293
+ type: 'Text',
294
+ group: 'style',
295
+ description: 'The spacing between the elements of the section',
296
+ defaultValue: '0px',
297
+ },
298
+ cfHyperlink: {
299
+ displayName: 'URL',
300
+ type: 'Hyperlink',
301
+ defaultValue: '',
302
+ validations: {
303
+ format: 'URL',
304
+ bindingSourceType: ['entry', 'experience', 'manual'],
305
+ },
306
+ description: 'hyperlink for section or container',
307
+ },
308
+ cfOpenInNewTab: {
309
+ displayName: 'URL behaviour',
310
+ type: 'Boolean',
311
+ defaultValue: false,
312
+ description: 'Open in new tab',
313
+ },
266
314
  };
267
-
268
- const transformVisibility = (value) => {
269
- if (value === false) {
270
- return {
271
- display: 'none !important',
272
- };
273
- }
274
- // Don't explicitly set anything when visible to not overwrite values like `grid` or `flex`.
275
- return {};
276
- };
277
- // Keep this for backwards compatibility - deleting this would be a breaking change
278
- // because existing components on a users experience will have the width value as fill
279
- // rather than 100%
280
- const transformFill = (value) => (value === 'fill' ? '100%' : value);
281
- const transformGridColumn = (span) => {
282
- if (!span) {
283
- return {};
284
- }
285
- return {
286
- gridColumn: `span ${span}`,
287
- };
288
- };
289
- const transformBorderStyle = (value) => {
290
- if (!value)
291
- return {};
292
- const parts = value.split(' ');
293
- // Just accept the passed value
294
- if (parts.length < 3)
295
- return { border: value };
296
- const [borderSize, borderStyle, ...borderColorParts] = parts;
297
- const borderColor = borderColorParts.join(' ');
298
- return {
299
- border: `${borderSize} ${borderStyle} ${borderColor}`,
300
- };
301
- };
302
- const transformAlignment = (cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection = 'column') => cfFlexDirection === 'row'
303
- ? {
304
- alignItems: cfHorizontalAlignment,
305
- justifyContent: cfVerticalAlignment === 'center' ? `safe ${cfVerticalAlignment}` : cfVerticalAlignment,
306
- }
307
- : {
308
- alignItems: cfVerticalAlignment,
309
- justifyContent: cfHorizontalAlignment === 'center'
310
- ? `safe ${cfHorizontalAlignment}`
311
- : cfHorizontalAlignment,
312
- };
313
- const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions) => {
314
- const matchBackgroundSize = (scaling) => {
315
- if ('fill' === scaling)
316
- return 'cover';
317
- if ('fit' === scaling)
318
- return 'contain';
319
- };
320
- const matchBackgroundPosition = (alignment) => {
321
- if (!alignment || 'string' !== typeof alignment) {
322
- return;
323
- }
324
- let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
325
- // Special case for handling single values
326
- // for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
327
- if (horizontalAlignment && !verticalAlignment) {
328
- const singleValue = horizontalAlignment;
329
- switch (singleValue) {
330
- case 'left':
331
- horizontalAlignment = 'left';
332
- verticalAlignment = 'center';
333
- break;
334
- case 'right':
335
- horizontalAlignment = 'right';
336
- verticalAlignment = 'center';
337
- break;
338
- case 'center':
339
- horizontalAlignment = 'center';
340
- verticalAlignment = 'center';
341
- break;
342
- case 'top':
343
- horizontalAlignment = 'center';
344
- verticalAlignment = 'top';
345
- break;
346
- case 'bottom':
347
- horizontalAlignment = 'center';
348
- verticalAlignment = 'bottom';
349
- break;
350
- // just fall down to the normal validation logic for horiz and vert
351
- }
352
- }
353
- const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
354
- const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
355
- horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
356
- verticalAlignment = isVerticalValid ? verticalAlignment : 'top';
357
- return `${horizontalAlignment} ${verticalAlignment}`;
358
- };
359
- if (!cfBackgroundImageUrl) {
360
- return;
361
- }
362
- let backgroundImage;
363
- let backgroundImageSet;
364
- if (typeof cfBackgroundImageUrl === 'string') {
365
- backgroundImage = `url(${cfBackgroundImageUrl})`;
366
- }
367
- else {
368
- const imgSet = cfBackgroundImageUrl.srcSet?.join(',');
369
- backgroundImage = `url(${cfBackgroundImageUrl.url})`;
370
- backgroundImageSet = `image-set(${imgSet})`;
371
- }
372
- return {
373
- backgroundImage,
374
- backgroundImage2: backgroundImageSet,
375
- backgroundRepeat: cfBackgroundImageOptions?.scaling === 'tile' ? 'repeat' : 'no-repeat',
376
- backgroundPosition: matchBackgroundPosition(cfBackgroundImageOptions?.alignment),
377
- backgroundSize: matchBackgroundSize(cfBackgroundImageOptions?.scaling),
378
- };
379
- };
380
-
381
- const toCSSAttribute = (key) => {
382
- let val = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
383
- // Remove the number from the end of the key to allow for overrides on style properties
384
- val = val.replace(/\d+$/, '');
385
- return val;
386
- };
387
- /**
388
- * Turns a list of CSSProperties into a joined CSS string that can be
389
- * used for <style> tags. Per default it creates a minimized version.
390
- * For editor mode, use the `useWhitespaces` flag to create a more readable version.
391
- *
392
- * @param cssProperties list of CSS properties
393
- * @param useWhitespaces adds whitespaces and newlines between each rule
394
- * @returns a string of CSS rules
395
- */
396
- const stringifyCssProperties = (cssProperties, useWhitespaces = false) => {
397
- const rules = Object.entries(cssProperties)
398
- .filter(([, value]) => value !== undefined)
399
- .map(([key, value]) => useWhitespaces ? `${toCSSAttribute(key)}: ${value};` : `${toCSSAttribute(key)}:${value};`);
400
- return rules.join(useWhitespaces ? '\n' : '');
401
- };
402
- const buildStyleTag = ({ styles, nodeId }) => {
403
- const generatedStyles = stringifyCssProperties(styles, true);
404
- const className = `cfstyles-${nodeId ? nodeId : md5(generatedStyles)}`;
405
- const styleRule = `.${className}{ ${generatedStyles} }`;
406
- return [className, styleRule];
407
- };
408
- /**
409
- * Takes plain design values and transforms them into CSS properties. Undefined values will
410
- * be filtered out.
411
- *
412
- * **Example Input**
413
- * ```
414
- * values = {
415
- * cfVisibility: 'visible',
416
- * cfMargin: '10px',
417
- * cfFlexReverse: true,
418
- * cfImageOptions: { objectFit: 'cover' },
419
- * // ...
420
- * }
421
- * ```
422
- * **Example Output**
423
- * ```
424
- * cssProperties = {
425
- * margin: '10px',
426
- * flexDirection: 'row-reverse',
427
- * objectFit: 'cover',
428
- * // ...
429
- * }
430
- * ```
431
- */
432
- const buildCfStyles = (values) => {
433
- const cssProperties = {
434
- boxSizing: 'border-box',
435
- ...transformVisibility(values.cfVisibility),
436
- margin: values.cfMargin,
437
- padding: values.cfPadding,
438
- backgroundColor: values.cfBackgroundColor,
439
- width: transformFill(values.cfWidth || values.cfImageOptions?.width),
440
- height: transformFill(values.cfHeight || values.cfImageOptions?.height),
441
- maxWidth: values.cfMaxWidth,
442
- ...transformGridColumn(values.cfColumnSpan),
443
- ...transformBorderStyle(values.cfBorder),
444
- borderRadius: values.cfBorderRadius,
445
- gap: values.cfGap,
446
- ...transformAlignment(values.cfHorizontalAlignment, values.cfVerticalAlignment, values.cfFlexDirection),
447
- flexDirection: values.cfFlexReverse && values.cfFlexDirection
448
- ? `${values.cfFlexDirection}-reverse`
449
- : values.cfFlexDirection,
450
- flexWrap: values.cfFlexWrap,
451
- ...transformBackgroundImage(values.cfBackgroundImageUrl, values.cfBackgroundImageOptions),
452
- fontSize: values.cfFontSize,
453
- fontWeight: values.cfTextBold ? 'bold' : values.cfFontWeight,
454
- fontStyle: values.cfTextItalic ? 'italic' : undefined,
455
- textDecoration: values.cfTextUnderline ? 'underline' : undefined,
456
- lineHeight: values.cfLineHeight,
457
- letterSpacing: values.cfLetterSpacing,
458
- color: values.cfTextColor,
459
- textAlign: values.cfTextAlign,
460
- textTransform: values.cfTextTransform,
461
- objectFit: values.cfImageOptions?.objectFit,
462
- objectPosition: values.cfImageOptions?.objectPosition,
463
- };
464
- const cssPropertiesWithoutUndefined = Object.fromEntries(Object.entries(cssProperties).filter(([, value]) => value !== undefined));
465
- return cssPropertiesWithoutUndefined;
466
- };
467
- /**
468
- * Container/section default behavior:
469
- * Default height => height: EMPTY_CONTAINER_HEIGHT
470
- * If a container component has children => height: 'fit-content'
471
- */
472
- const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
473
- if (!blockId || !isContentfulStructureComponent(blockId) || value !== 'auto') {
474
- return value;
475
- }
476
- if (children.length) {
477
- return '100%';
478
- }
479
- return EMPTY_CONTAINER_HEIGHT$1;
480
- };
481
-
482
- // These styles get added to every component, user custom or contentful provided
483
- const builtInStyles = {
484
- cfVerticalAlignment: {
485
- validations: {
486
- in: [
487
- {
488
- value: 'start',
489
- displayName: 'Align left',
490
- },
491
- {
492
- value: 'center',
493
- displayName: 'Align center',
494
- },
495
- {
496
- value: 'end',
497
- displayName: 'Align right',
498
- },
499
- ],
500
- },
315
+ const optionalBuiltInStyles = {
316
+ cfFontSize: {
317
+ displayName: 'Font Size',
501
318
  type: 'Text',
502
319
  group: 'style',
503
- description: 'The vertical alignment of the section',
504
- defaultValue: 'center',
505
- displayName: 'Vertical alignment',
320
+ description: 'The font size of the element',
321
+ defaultValue: '16px',
506
322
  },
507
- cfHorizontalAlignment: {
323
+ cfFontWeight: {
508
324
  validations: {
509
325
  in: [
510
326
  {
511
- value: 'start',
512
- displayName: 'Align top',
327
+ value: '400',
328
+ displayName: 'Normal',
513
329
  },
514
330
  {
515
- value: 'center',
516
- displayName: 'Align center',
331
+ value: '500',
332
+ displayName: 'Medium',
517
333
  },
518
334
  {
519
- value: 'end',
520
- displayName: 'Align bottom',
335
+ value: '600',
336
+ displayName: 'Semi Bold',
521
337
  },
522
338
  ],
523
339
  },
524
- type: 'Text',
525
- group: 'style',
526
- description: 'The horizontal alignment of the section',
527
- defaultValue: 'center',
528
- displayName: 'Horizontal alignment',
529
- },
530
- cfVisibility: {
531
- displayName: 'Visibility toggle',
532
- type: 'Boolean',
533
- group: 'style',
534
- defaultValue: true,
535
- description: 'The visibility of the component',
536
- },
537
- cfMargin: {
538
- displayName: 'Margin',
539
- type: 'Text',
540
- group: 'style',
541
- description: 'The margin of the section',
542
- defaultValue: '0 0 0 0',
543
- },
544
- cfPadding: {
545
- displayName: 'Padding',
546
- type: 'Text',
547
- group: 'style',
548
- description: 'The padding of the section',
549
- defaultValue: '0 0 0 0',
550
- },
551
- cfBackgroundColor: {
552
- displayName: 'Background color',
553
- type: 'Text',
554
- group: 'style',
555
- description: 'The background color of the section',
556
- defaultValue: 'rgba(0, 0, 0, 0)',
557
- },
558
- cfWidth: {
559
- displayName: 'Width',
560
- type: 'Text',
561
- group: 'style',
562
- description: 'The width of the section',
563
- defaultValue: '100%',
564
- },
565
- cfHeight: {
566
- displayName: 'Height',
567
- type: 'Text',
568
- group: 'style',
569
- description: 'The height of the section',
570
- defaultValue: 'fit-content',
571
- },
572
- cfMaxWidth: {
573
- displayName: 'Max width',
574
- type: 'Text',
575
- group: 'style',
576
- description: 'The max-width of the section',
577
- defaultValue: 'none',
578
- },
579
- cfFlexDirection: {
580
- displayName: 'Direction',
581
- type: 'Text',
582
- group: 'style',
583
- description: 'The orientation of the section',
584
- defaultValue: 'column',
585
- },
586
- cfFlexReverse: {
587
- displayName: 'Reverse Direction',
588
- type: 'Boolean',
589
- group: 'style',
590
- description: 'Toggle the flex direction to be reversed',
591
- defaultValue: false,
592
- },
593
- cfFlexWrap: {
594
- displayName: 'Wrap objects',
595
- type: 'Text',
596
- group: 'style',
597
- description: 'Wrap objects',
598
- defaultValue: 'nowrap',
599
- },
600
- cfBorder: {
601
- displayName: 'Border',
602
- type: 'Text',
603
- group: 'style',
604
- description: 'The border of the section',
605
- defaultValue: '0px solid rgba(0, 0, 0, 0)',
606
- },
607
- cfGap: {
608
- displayName: 'Gap',
609
- type: 'Text',
610
- group: 'style',
611
- description: 'The spacing between the elements of the section',
612
- defaultValue: '0px',
613
- },
614
- cfHyperlink: {
615
- displayName: 'URL',
616
- type: 'Hyperlink',
617
- defaultValue: '',
618
- validations: {
619
- format: 'URL',
620
- bindingSourceType: ['entry', 'experience', 'manual'],
621
- },
622
- description: 'hyperlink for section or container',
623
- },
624
- cfOpenInNewTab: {
625
- displayName: 'URL behaviour',
626
- type: 'Boolean',
627
- defaultValue: false,
628
- description: 'Open in new tab',
629
- },
630
- };
631
- const optionalBuiltInStyles = {
632
- cfFontSize: {
633
- displayName: 'Font Size',
634
- type: 'Text',
635
- group: 'style',
636
- description: 'The font size of the element',
637
- defaultValue: '16px',
638
- },
639
- cfFontWeight: {
640
- validations: {
641
- in: [
642
- {
643
- value: '400',
644
- displayName: 'Normal',
645
- },
646
- {
647
- value: '500',
648
- displayName: 'Medium',
649
- },
650
- {
651
- value: '600',
652
- displayName: 'Semi Bold',
653
- },
654
- ],
655
- },
656
- displayName: 'Font Weight',
340
+ displayName: 'Font Weight',
657
341
  type: 'Text',
658
342
  group: 'style',
659
343
  description: 'The font weight of the element',
@@ -1271,477 +955,206 @@ var CodeNames$1;
1271
955
  CodeNames["Custom"] = "custom";
1272
956
  })(CodeNames$1 || (CodeNames$1 = {}));
1273
957
 
1274
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1275
- function get(obj, path) {
1276
- if (!path.length) {
1277
- return obj;
1278
- }
1279
- try {
1280
- const [currentPath, ...nextPath] = path;
1281
- return get(obj[currentPath], nextPath);
1282
- }
1283
- catch (err) {
958
+ const MEDIA_QUERY_REGEXP = /(<|>)(\d{1,})(px|cm|mm|in|pt|pc)$/;
959
+ const toCSSMediaQuery = ({ query }) => {
960
+ if (query === '*')
1284
961
  return undefined;
962
+ const match = query.match(MEDIA_QUERY_REGEXP);
963
+ if (!match)
964
+ return undefined;
965
+ const [, operator, value, unit] = match;
966
+ if (operator === '<') {
967
+ const maxScreenWidth = Number(value) - 1;
968
+ return `(max-width: ${maxScreenWidth}${unit})`;
1285
969
  }
1286
- }
1287
-
1288
- const getBoundValue = (entryOrAsset, path) => {
1289
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1290
- return value && typeof value == 'object' && value.url
1291
- ? value.url
1292
- : value;
1293
- };
1294
-
1295
- const transformRichText = (entryOrAsset, entityStore, path) => {
1296
- const value = getBoundValue(entryOrAsset, path);
1297
- if (typeof value === 'string') {
1298
- return {
1299
- data: {},
1300
- content: [
1301
- {
1302
- nodeType: BLOCKS.PARAGRAPH,
1303
- data: {},
1304
- content: [
1305
- {
1306
- data: {},
1307
- nodeType: 'text',
1308
- value: value,
1309
- marks: [],
1310
- },
1311
- ],
1312
- },
1313
- ],
1314
- nodeType: BLOCKS.DOCUMENT,
1315
- };
1316
- }
1317
- if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
1318
- // resolve any links to assets/entries/hyperlinks
1319
- const richTextDocument = value;
1320
- resolveLinks(richTextDocument, entityStore);
1321
- return richTextDocument;
970
+ else if (operator === '>') {
971
+ const minScreenWidth = Number(value) + 1;
972
+ return `(min-width: ${minScreenWidth}${unit})`;
1322
973
  }
1323
974
  return undefined;
1324
975
  };
1325
- const isLinkTarget = (node) => {
1326
- return node?.data?.target?.sys?.type === 'Link';
976
+ // Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
977
+ const findLast = (array, predicate) => {
978
+ return array.reverse().find(predicate);
1327
979
  };
1328
- const resolveLinks = (node, entityStore) => {
1329
- if (!node)
1330
- return;
1331
- // Resolve link if current node has one
1332
- if (isLinkTarget(node)) {
1333
- const entity = entityStore.getEntityFromLink(node.data.target);
1334
- if (entity) {
1335
- node.data.target = entity;
1336
- }
1337
- }
1338
- // Process content array if it exists
1339
- if ('content' in node && Array.isArray(node.content)) {
1340
- node.content.forEach((childNode) => resolveLinks(childNode, entityStore));
1341
- }
980
+ // Initialise media query matchers. This won't include the always matching fallback breakpoint.
981
+ const mediaQueryMatcher = (breakpoints) => {
982
+ const mediaQueryMatches = {};
983
+ const mediaQueryMatchers = breakpoints
984
+ .map((breakpoint) => {
985
+ const cssMediaQuery = toCSSMediaQuery(breakpoint);
986
+ if (!cssMediaQuery)
987
+ return undefined;
988
+ if (typeof window === 'undefined')
989
+ return undefined;
990
+ const mediaQueryMatcher = window.matchMedia(cssMediaQuery);
991
+ mediaQueryMatches[breakpoint.id] = mediaQueryMatcher.matches;
992
+ return { id: breakpoint.id, signal: mediaQueryMatcher };
993
+ })
994
+ .filter((matcher) => !!matcher);
995
+ return [mediaQueryMatchers, mediaQueryMatches];
1342
996
  };
1343
-
1344
- function getOptimizedImageUrl(url, width, quality, format) {
1345
- if (url.startsWith('//')) {
1346
- url = 'https:' + url;
1347
- }
1348
- const params = new URLSearchParams();
1349
- if (width) {
1350
- params.append('w', width.toString());
1351
- }
1352
- if (quality && quality > 0 && quality < 100) {
1353
- params.append('q', quality.toString());
1354
- }
1355
- if (format) {
1356
- params.append('fm', format);
1357
- }
1358
- const queryString = params.toString();
1359
- return `${url}${queryString ? '?' + queryString : ''}`;
1360
- }
1361
-
1362
- function validateParams(file, quality, format) {
1363
- if (!file.details.image) {
1364
- throw Error('No image in file asset to transform');
1365
- }
1366
- if (quality < 0 || quality > 100) {
1367
- throw Error('Quality must be between 0 and 100');
1368
- }
1369
- if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
1370
- throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
1371
- }
1372
- return true;
1373
- }
1374
-
1375
- const MAX_WIDTH_ALLOWED$1 = 2000;
1376
- const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', format) => {
1377
- const qualityNumber = Number(quality.replace('%', ''));
1378
- if (!validateParams(file, qualityNumber, format)) ;
1379
- if (!validateParams(file, qualityNumber, format)) ;
1380
- const url = file.url;
1381
- const { width1x, width2x } = getWidths(widthStyle, file);
1382
- const imageUrl1x = getOptimizedImageUrl(url, width1x, qualityNumber, format);
1383
- const imageUrl2x = getOptimizedImageUrl(url, width2x, qualityNumber, format);
1384
- const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
1385
- const returnedUrl = getOptimizedImageUrl(url, width2x, qualityNumber, format);
1386
- const optimizedBackgroundImageAsset = {
1387
- url: returnedUrl,
1388
- srcSet,
1389
- file,
997
+ const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
998
+ // The breakpoints are ordered (desktop-first: descending by screen width)
999
+ const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
1000
+ id,
1001
+ index,
1002
+ // The fallback breakpoint with wildcard query will always match
1003
+ isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
1004
+ }));
1005
+ // Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
1006
+ const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
1007
+ return mostSpecificIndex ?? fallbackBreakpointIndex;
1008
+ };
1009
+ const getFallbackBreakpointIndex = (breakpoints) => {
1010
+ // We assume that there will be a single breakpoint which uses the wildcard query.
1011
+ // If there is none, we just take the first one in the list.
1012
+ return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
1013
+ };
1014
+ const builtInStylesWithDesignTokens = [
1015
+ 'cfMargin',
1016
+ 'cfPadding',
1017
+ 'cfGap',
1018
+ 'cfWidth',
1019
+ 'cfHeight',
1020
+ 'cfBackgroundColor',
1021
+ 'cfBorder',
1022
+ 'cfBorderRadius',
1023
+ 'cfFontSize',
1024
+ 'cfLineHeight',
1025
+ 'cfLetterSpacing',
1026
+ 'cfTextColor',
1027
+ 'cfMaxWidth',
1028
+ ];
1029
+ const isValidBreakpointValue = (value) => {
1030
+ return value !== undefined && value !== null && value !== '';
1031
+ };
1032
+ const getValueForBreakpoint = (valuesByBreakpoint, breakpoints, activeBreakpointIndex, fallbackBreakpointIndex, variableName, resolveDesignTokens = true) => {
1033
+ const eventuallyResolveDesignTokens = (value) => {
1034
+ // For some built-in design properties, we support design tokens
1035
+ if (builtInStylesWithDesignTokens.includes(variableName)) {
1036
+ return getDesignTokenRegistration(value, variableName);
1037
+ }
1038
+ // For all other properties, we just return the breakpoint-specific value
1039
+ return value;
1390
1040
  };
1391
- return optimizedBackgroundImageAsset;
1392
- function getWidths(widthStyle, file) {
1393
- let width1x = 0;
1394
- let width2x = 0;
1395
- const intrinsicImageWidth = file.details.image.width;
1396
- if (widthStyle.endsWith('px')) {
1397
- width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
1041
+ if (valuesByBreakpoint instanceof Object) {
1042
+ // Assume that the values are sorted by media query to apply the cascading CSS logic
1043
+ for (let index = activeBreakpointIndex; index >= 0; index--) {
1044
+ const breakpointId = breakpoints[index]?.id;
1045
+ if (isValidBreakpointValue(valuesByBreakpoint[breakpointId])) {
1046
+ // If the value is defined, we use it and stop the breakpoints cascade
1047
+ if (resolveDesignTokens) {
1048
+ return eventuallyResolveDesignTokens(valuesByBreakpoint[breakpointId]);
1049
+ }
1050
+ return valuesByBreakpoint[breakpointId];
1051
+ }
1398
1052
  }
1399
- else {
1400
- width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
1053
+ const fallbackBreakpointId = breakpoints[fallbackBreakpointIndex]?.id;
1054
+ if (isValidBreakpointValue(valuesByBreakpoint[fallbackBreakpointId])) {
1055
+ if (resolveDesignTokens) {
1056
+ return eventuallyResolveDesignTokens(valuesByBreakpoint[fallbackBreakpointId]);
1057
+ }
1058
+ return valuesByBreakpoint[fallbackBreakpointId];
1401
1059
  }
1402
- width2x = Math.min(width1x * 2, intrinsicImageWidth);
1403
- return { width1x, width2x };
1404
1060
  }
1405
- };
1406
-
1407
- const MAX_WIDTH_ALLOWED = 4000;
1408
- const getOptimizedImageAsset = ({ file, sizes, loading, quality = '100%', format, }) => {
1409
- const qualityNumber = Number(quality.replace('%', ''));
1410
- if (!validateParams(file, qualityNumber, format)) ;
1411
- const url = file.url;
1412
- const maxWidth = Math.min(file.details.image.width, MAX_WIDTH_ALLOWED);
1413
- const numOfParts = Math.max(2, Math.ceil(maxWidth / 500));
1414
- const widthParts = Array.from({ length: numOfParts }, (_, index) => Math.ceil((index + 1) * (maxWidth / numOfParts)));
1415
- const srcSet = sizes
1416
- ? widthParts.map((width) => `${getOptimizedImageUrl(url, width, qualityNumber, format)} ${width}w`)
1417
- : [];
1418
- const intrinsicImageWidth = file.details.image.width;
1419
- if (intrinsicImageWidth > MAX_WIDTH_ALLOWED) {
1420
- srcSet.push(`${getOptimizedImageUrl(url, undefined, qualityNumber, format)} ${intrinsicImageWidth}w`);
1061
+ else {
1062
+ // Old design properties did not support breakpoints, keep for backward compatibility
1063
+ return valuesByBreakpoint;
1421
1064
  }
1422
- const returnedUrl = getOptimizedImageUrl(url, file.details.image.width > 2000 ? 2000 : undefined, qualityNumber, format);
1423
- const optimizedImageAsset = {
1424
- url: returnedUrl,
1425
- srcSet,
1426
- sizes,
1427
- file,
1428
- loading,
1429
- };
1430
- return optimizedImageAsset;
1431
1065
  };
1432
1066
 
1433
- const transformMedia = (asset, variables, resolveDesignValue, variableName, path) => {
1434
- let value;
1435
- // If it is not a deep path and not pointing to the file of the asset,
1436
- // it is just pointing to a normal field and therefore we just resolve the value as normal field
1437
- if (!isDeepPath(path) && !lastPathNamedSegmentEq(path, 'file')) {
1438
- return getBoundValue(asset, path);
1439
- }
1440
- //TODO: this will be better served by injectable type transformers instead of if statement
1441
- if (variableName === 'cfImageAsset') {
1442
- const optionsVariableName = 'cfImageOptions';
1443
- const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
1444
- ? variables[optionsVariableName].valuesByBreakpoint
1445
- : {}, optionsVariableName);
1446
- if (!options) {
1447
- console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
1067
+ const CF_DEBUG_KEY$1 = 'cf_debug';
1068
+ let DebugLogger$1 = class DebugLogger {
1069
+ constructor() {
1070
+ // Public methods for logging
1071
+ this.error = this.logger('error');
1072
+ this.warn = this.logger('warn');
1073
+ this.log = this.logger('log');
1074
+ this.debug = this.logger('debug');
1075
+ if (typeof localStorage === 'undefined') {
1076
+ this.enabled = false;
1448
1077
  return;
1449
1078
  }
1450
- try {
1451
- value = getOptimizedImageAsset({
1452
- file: asset.fields.file,
1453
- loading: options.loading,
1454
- sizes: options.targetSize,
1455
- quality: options.quality,
1456
- format: options.format,
1457
- });
1458
- return value;
1459
- }
1460
- catch (error) {
1461
- console.error('Error transforming image asset', error);
1079
+ // Default to checking localStorage for the debug mode on initialization if in browser
1080
+ this.enabled = localStorage.getItem(CF_DEBUG_KEY$1) === 'true';
1081
+ }
1082
+ static getInstance() {
1083
+ if (this.instance === null) {
1084
+ this.instance = new DebugLogger();
1462
1085
  }
1463
- return;
1086
+ return this.instance;
1464
1087
  }
1465
- if (variableName === 'cfBackgroundImageUrl') {
1466
- let width = resolveDesignValue(variables['cfWidth']?.type === 'DesignValue' ? variables['cfWidth'].valuesByBreakpoint : {}, 'cfWidth') || '100%';
1467
- const optionsVariableName = 'cfBackgroundImageOptions';
1468
- const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
1469
- ? variables[optionsVariableName].valuesByBreakpoint
1470
- : {}, optionsVariableName);
1471
- if (!options) {
1472
- console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
1088
+ getEnabled() {
1089
+ return this.enabled;
1090
+ }
1091
+ setEnabled(enabled) {
1092
+ this.enabled = enabled;
1093
+ if (typeof localStorage === 'undefined') {
1473
1094
  return;
1474
1095
  }
1475
- try {
1476
- // Target width (px/rem/em) will be applied to the css url if it's lower than the original image width (in px)
1477
- const assetDetails = asset.fields.file?.details;
1478
- const assetWidth = assetDetails?.image?.width || 0; // This is always in px
1479
- const targetWidthObject = parseCSSValue(options.targetSize); // Contains value and unit (px/rem/em) so convert and then compare to assetWidth
1480
- const targetValue = targetWidthObject
1481
- ? getTargetValueInPixels(targetWidthObject)
1482
- : assetWidth;
1483
- if (targetValue < assetWidth)
1484
- width = `${targetValue}px`;
1485
- value = getOptimizedBackgroundImageAsset(asset.fields.file, width, options.quality, options.format);
1486
- return value;
1096
+ if (enabled) {
1097
+ localStorage.setItem(CF_DEBUG_KEY$1, 'true');
1487
1098
  }
1488
- catch (error) {
1489
- console.error('Error transforming image asset', error);
1099
+ else {
1100
+ localStorage.removeItem(CF_DEBUG_KEY$1);
1490
1101
  }
1491
- return;
1492
1102
  }
1493
- return asset.fields.file?.url;
1103
+ // Log method for different levels (error, warn, log)
1104
+ logger(level) {
1105
+ return (...args) => {
1106
+ if (this.enabled) {
1107
+ console[level]('[cf-experiences-sdk]', ...args);
1108
+ }
1109
+ };
1110
+ }
1494
1111
  };
1112
+ DebugLogger$1.instance = null;
1113
+ DebugLogger$1.getInstance();
1495
1114
 
1496
- function getResolvedEntryFromLink(entryOrAsset, path, entityStore) {
1497
- if (entryOrAsset.sys.type === 'Asset') {
1498
- return entryOrAsset;
1115
+ const findOutermostCoordinates = (first, second) => {
1116
+ return {
1117
+ top: Math.min(first.top, second.top),
1118
+ right: Math.max(first.right, second.right),
1119
+ bottom: Math.max(first.bottom, second.bottom),
1120
+ left: Math.min(first.left, second.left),
1121
+ };
1122
+ };
1123
+ const getElementCoordinates = (element) => {
1124
+ const rect = element.getBoundingClientRect();
1125
+ /**
1126
+ * If element does not have children, or element has it's own width or height,
1127
+ * return the element's coordinates.
1128
+ */
1129
+ if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
1130
+ return rect;
1499
1131
  }
1500
- const value = get(entryOrAsset, path.split('/').slice(2, -1));
1501
- if (value?.sys.type !== 'Link') {
1502
- console.warn(`Expected a link to a reference, but got: ${JSON.stringify(value)}`);
1503
- return;
1132
+ const rects = [];
1133
+ /**
1134
+ * If element has children, or element does not have it's own width and height,
1135
+ * we find the cordinates of the children, and assume the outermost coordinates of the children
1136
+ * as the coordinate of the element.
1137
+ *
1138
+ * E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
1139
+ * The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
1140
+ */
1141
+ for (const child of element.children) {
1142
+ const childRect = getElementCoordinates(child);
1143
+ if (childRect.width !== 0 || childRect.height !== 0) {
1144
+ const { top, right, bottom, left } = childRect;
1145
+ rects.push({ top, right, bottom, left });
1146
+ }
1504
1147
  }
1505
- //Look up the reference in the entity store
1506
- const resolvedEntity = entityStore.getEntityFromLink(value);
1507
- if (!resolvedEntity) {
1508
- return;
1148
+ if (rects.length === 0) {
1149
+ return rect;
1509
1150
  }
1510
- //resolve any embedded links - we currently only support 2 levels deep
1511
- const fields = resolvedEntity.fields || {};
1512
- Object.entries(fields).forEach(([fieldKey, field]) => {
1513
- if (field && field.sys?.type === 'Link') {
1514
- const entity = entityStore.getEntityFromLink(field);
1515
- if (entity) {
1516
- resolvedEntity.fields[fieldKey] = entity;
1517
- }
1518
- }
1519
- else if (field && Array.isArray(field)) {
1520
- resolvedEntity.fields[fieldKey] = field.map((innerField) => {
1521
- if (innerField && innerField.sys?.type === 'Link') {
1522
- const entity = entityStore.getEntityFromLink(innerField);
1523
- if (entity) {
1524
- return entity;
1525
- }
1526
- }
1527
- return innerField;
1528
- });
1529
- }
1530
- });
1531
- return resolvedEntity;
1532
- }
1533
-
1534
- function getArrayValue(entryOrAsset, path, entityStore) {
1535
- if (entryOrAsset.sys.type === 'Asset') {
1536
- return entryOrAsset;
1537
- }
1538
- const arrayValue = get(entryOrAsset, path.split('/').slice(2, -1));
1539
- if (!isArray(arrayValue)) {
1540
- console.warn(`Expected a value to be an array, but got: ${JSON.stringify(arrayValue)}`);
1541
- return;
1542
- }
1543
- const result = arrayValue.map((value) => {
1544
- if (typeof value === 'string') {
1545
- return value;
1546
- }
1547
- else if (value?.sys?.type === 'Link') {
1548
- const resolvedEntity = entityStore.getEntityFromLink(value);
1549
- if (!resolvedEntity) {
1550
- return;
1551
- }
1552
- //resolve any embedded links - we currently only support 2 levels deep
1553
- const fields = resolvedEntity.fields || {};
1554
- Object.entries(fields).forEach(([fieldKey, field]) => {
1555
- if (field && field.sys?.type === 'Link') {
1556
- const entity = entityStore.getEntityFromLink(field);
1557
- if (entity) {
1558
- resolvedEntity.fields[fieldKey] = entity;
1559
- }
1560
- }
1561
- });
1562
- return resolvedEntity;
1563
- }
1564
- else {
1565
- console.warn(`Expected value to be a string or Link, but got: ${JSON.stringify(value)}`);
1566
- return undefined;
1567
- }
1151
+ const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
1152
+ return DOMRect.fromRect({
1153
+ x: left,
1154
+ y: top,
1155
+ height: bottom - top,
1156
+ width: right - left,
1568
1157
  });
1569
- return result;
1570
- }
1571
-
1572
- const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableType, path) => {
1573
- const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
1574
- if (!entityOrAsset)
1575
- return;
1576
- switch (variableType) {
1577
- case 'Media':
1578
- // If we bound a normal entry field to the media variable we just return the bound value
1579
- if (entityOrAsset.sys.type === 'Entry') {
1580
- return getBoundValue(entityOrAsset, path);
1581
- }
1582
- return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
1583
- case 'RichText':
1584
- return transformRichText(entityOrAsset, entityStore, path);
1585
- case 'Array':
1586
- return getArrayValue(entityOrAsset, path, entityStore);
1587
- case 'Link':
1588
- return getResolvedEntryFromLink(entityOrAsset, path, entityStore);
1589
- default:
1590
- return getBoundValue(entityOrAsset, path);
1591
- }
1592
- };
1593
-
1594
- const getDataFromTree = (tree) => {
1595
- let dataSource = {};
1596
- let unboundValues = {};
1597
- const queue = [...tree.root.children];
1598
- while (queue.length) {
1599
- const node = queue.shift();
1600
- if (!node) {
1601
- continue;
1602
- }
1603
- dataSource = { ...dataSource, ...node.data.dataSource };
1604
- unboundValues = { ...unboundValues, ...node.data.unboundValues };
1605
- if (node.children.length) {
1606
- queue.push(...node.children);
1607
- }
1608
- }
1609
- return {
1610
- dataSource,
1611
- unboundValues,
1612
- };
1613
- };
1614
- function parseCSSValue(input) {
1615
- const regex = /^(\d+(\.\d+)?)(px|em|rem)$/;
1616
- const match = input.match(regex);
1617
- if (match) {
1618
- return {
1619
- value: parseFloat(match[1]),
1620
- unit: match[3],
1621
- };
1622
- }
1623
- return null;
1624
- }
1625
- function getTargetValueInPixels(targetWidthObject) {
1626
- switch (targetWidthObject.unit) {
1627
- case 'px':
1628
- return targetWidthObject.value;
1629
- case 'em':
1630
- return targetWidthObject.value * 16;
1631
- case 'rem':
1632
- return targetWidthObject.value * 16;
1633
- default:
1634
- return targetWidthObject.value;
1635
- }
1636
- }
1637
-
1638
- const MEDIA_QUERY_REGEXP = /(<|>)(\d{1,})(px|cm|mm|in|pt|pc)$/;
1639
- const toCSSMediaQuery = ({ query }) => {
1640
- if (query === '*')
1641
- return undefined;
1642
- const match = query.match(MEDIA_QUERY_REGEXP);
1643
- if (!match)
1644
- return undefined;
1645
- const [, operator, value, unit] = match;
1646
- if (operator === '<') {
1647
- const maxScreenWidth = Number(value) - 1;
1648
- return `(max-width: ${maxScreenWidth}${unit})`;
1649
- }
1650
- else if (operator === '>') {
1651
- const minScreenWidth = Number(value) + 1;
1652
- return `(min-width: ${minScreenWidth}${unit})`;
1653
- }
1654
- return undefined;
1655
- };
1656
- // Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
1657
- const findLast = (array, predicate) => {
1658
- return array.reverse().find(predicate);
1659
- };
1660
- // Initialise media query matchers. This won't include the always matching fallback breakpoint.
1661
- const mediaQueryMatcher = (breakpoints) => {
1662
- const mediaQueryMatches = {};
1663
- const mediaQueryMatchers = breakpoints
1664
- .map((breakpoint) => {
1665
- const cssMediaQuery = toCSSMediaQuery(breakpoint);
1666
- if (!cssMediaQuery)
1667
- return undefined;
1668
- if (typeof window === 'undefined')
1669
- return undefined;
1670
- const mediaQueryMatcher = window.matchMedia(cssMediaQuery);
1671
- mediaQueryMatches[breakpoint.id] = mediaQueryMatcher.matches;
1672
- return { id: breakpoint.id, signal: mediaQueryMatcher };
1673
- })
1674
- .filter((matcher) => !!matcher);
1675
- return [mediaQueryMatchers, mediaQueryMatches];
1676
- };
1677
- const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
1678
- // The breakpoints are ordered (desktop-first: descending by screen width)
1679
- const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
1680
- id,
1681
- index,
1682
- // The fallback breakpoint with wildcard query will always match
1683
- isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
1684
- }));
1685
- // Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
1686
- const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
1687
- return mostSpecificIndex ?? fallbackBreakpointIndex;
1688
- };
1689
- const getFallbackBreakpointIndex = (breakpoints) => {
1690
- // We assume that there will be a single breakpoint which uses the wildcard query.
1691
- // If there is none, we just take the first one in the list.
1692
- return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
1693
- };
1694
- const builtInStylesWithDesignTokens = [
1695
- 'cfMargin',
1696
- 'cfPadding',
1697
- 'cfGap',
1698
- 'cfWidth',
1699
- 'cfHeight',
1700
- 'cfBackgroundColor',
1701
- 'cfBorder',
1702
- 'cfBorderRadius',
1703
- 'cfFontSize',
1704
- 'cfLineHeight',
1705
- 'cfLetterSpacing',
1706
- 'cfTextColor',
1707
- 'cfMaxWidth',
1708
- ];
1709
- const isValidBreakpointValue = (value) => {
1710
- return value !== undefined && value !== null && value !== '';
1711
- };
1712
- const getValueForBreakpoint = (valuesByBreakpoint, breakpoints, activeBreakpointIndex, fallbackBreakpointIndex, variableName, resolveDesignTokens = true) => {
1713
- const eventuallyResolveDesignTokens = (value) => {
1714
- // For some built-in design properties, we support design tokens
1715
- if (builtInStylesWithDesignTokens.includes(variableName)) {
1716
- return getDesignTokenRegistration(value, variableName);
1717
- }
1718
- // For all other properties, we just return the breakpoint-specific value
1719
- return value;
1720
- };
1721
- if (valuesByBreakpoint instanceof Object) {
1722
- // Assume that the values are sorted by media query to apply the cascading CSS logic
1723
- for (let index = activeBreakpointIndex; index >= 0; index--) {
1724
- const breakpointId = breakpoints[index]?.id;
1725
- if (isValidBreakpointValue(valuesByBreakpoint[breakpointId])) {
1726
- // If the value is defined, we use it and stop the breakpoints cascade
1727
- if (resolveDesignTokens) {
1728
- return eventuallyResolveDesignTokens(valuesByBreakpoint[breakpointId]);
1729
- }
1730
- return valuesByBreakpoint[breakpointId];
1731
- }
1732
- }
1733
- const fallbackBreakpointId = breakpoints[fallbackBreakpointIndex]?.id;
1734
- if (isValidBreakpointValue(valuesByBreakpoint[fallbackBreakpointId])) {
1735
- if (resolveDesignTokens) {
1736
- return eventuallyResolveDesignTokens(valuesByBreakpoint[fallbackBreakpointId]);
1737
- }
1738
- return valuesByBreakpoint[fallbackBreakpointId];
1739
- }
1740
- }
1741
- else {
1742
- // Old design properties did not support breakpoints, keep for backward compatibility
1743
- return valuesByBreakpoint;
1744
- }
1745
1158
  };
1746
1159
 
1747
1160
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1847,149 +1260,736 @@ const parseDeepPath = (deepPathCandidate) => {
1847
1260
  if (!fieldChunks.every(isValidFieldChunk)) {
1848
1261
  return null;
1849
1262
  }
1850
- return {
1851
- key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
1852
- fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
1853
- };
1854
- };
1855
- const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
1856
- const chunks = [];
1857
- let currentChunk = [];
1858
- const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
1859
- const excludeEmptyChunks = (chunk) => chunk.length > 0;
1860
- for (let i = 0; i < segments.length; i++) {
1861
- const isInitialElement = i === 0;
1862
- const segment = segments[i];
1863
- if (isInitialElement) {
1864
- currentChunk = [segment];
1263
+ return {
1264
+ key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
1265
+ fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
1266
+ };
1267
+ };
1268
+ const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
1269
+ const chunks = [];
1270
+ let currentChunk = [];
1271
+ const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
1272
+ const excludeEmptyChunks = (chunk) => chunk.length > 0;
1273
+ for (let i = 0; i < segments.length; i++) {
1274
+ const isInitialElement = i === 0;
1275
+ const segment = segments[i];
1276
+ if (isInitialElement) {
1277
+ currentChunk = [segment];
1278
+ }
1279
+ else if (isSegmentBeginningOfChunk(segment)) {
1280
+ chunks.push(currentChunk);
1281
+ currentChunk = [segment];
1282
+ }
1283
+ else {
1284
+ currentChunk.push(segment);
1285
+ }
1286
+ }
1287
+ chunks.push(currentChunk);
1288
+ return chunks.filter(excludeEmptyChunks);
1289
+ };
1290
+ const lastPathNamedSegmentEq = (path, expectedName) => {
1291
+ // `/key123/fields/featureImage/~locale/fields/file/~locale`
1292
+ // ['', 'key123', 'fields', 'featureImage', '~locale', 'fields', 'file', '~locale']
1293
+ const segments = path.split('/');
1294
+ if (segments.length < 2) {
1295
+ console.warn(`[experiences-sdk-react] Attempting to check whether last named segment of the path (${path}) equals to '${expectedName}', but the path doesn't have enough segments.`);
1296
+ return false;
1297
+ }
1298
+ const secondLast = segments[segments.length - 2]; // skipping trailing '~locale'
1299
+ return secondLast === expectedName;
1300
+ };
1301
+
1302
+ const resolveHyperlinkPattern = (pattern, entry, locale) => {
1303
+ if (!entry || !locale)
1304
+ return null;
1305
+ const variables = {
1306
+ entry,
1307
+ locale,
1308
+ };
1309
+ return buildTemplate({ template: pattern, context: variables });
1310
+ };
1311
+ function getValue(obj, path) {
1312
+ return path
1313
+ .replace(/\[/g, '.')
1314
+ .replace(/\]/g, '')
1315
+ .split('.')
1316
+ .reduce((o, k) => (o || {})[k], obj);
1317
+ }
1318
+ function addLocale(str, locale) {
1319
+ const fieldsIndicator = 'fields';
1320
+ const fieldsIndex = str.indexOf(fieldsIndicator);
1321
+ if (fieldsIndex !== -1) {
1322
+ const dotIndex = str.indexOf('.', fieldsIndex + fieldsIndicator.length + 1); // +1 for '.'
1323
+ if (dotIndex !== -1) {
1324
+ return str.slice(0, dotIndex + 1) + locale + '.' + str.slice(dotIndex + 1);
1325
+ }
1326
+ }
1327
+ return str;
1328
+ }
1329
+ function getTemplateValue(ctx, path) {
1330
+ const pathWithLocale = addLocale(path, ctx.locale);
1331
+ const retrievedValue = getValue(ctx, pathWithLocale);
1332
+ return typeof retrievedValue === 'object' && retrievedValue !== null
1333
+ ? retrievedValue[ctx.locale]
1334
+ : retrievedValue;
1335
+ }
1336
+ function buildTemplate({ template, context, }) {
1337
+ const localeVariable = /{\s*locale\s*}/g;
1338
+ // e.g. "{ page.sys.id }"
1339
+ const variables = /{\s*([\S]+?)\s*}/g;
1340
+ return (template
1341
+ // first replace the locale pattern
1342
+ .replace(localeVariable, context.locale)
1343
+ // then resolve the remaining variables
1344
+ .replace(variables, (_, path) => {
1345
+ const fallback = path + '_NOT_FOUND';
1346
+ const value = getTemplateValue(context, path) ?? fallback;
1347
+ // using _.result didn't gave proper results so we run our own version of it
1348
+ return String(typeof value === 'function' ? value() : value);
1349
+ }));
1350
+ }
1351
+
1352
+ const stylesToKeep = ['cfImageAsset'];
1353
+ const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.includes(style));
1354
+ const propsToRemove = ['cfHyperlink', 'cfOpenInNewTab', 'cfSsrClassName'];
1355
+ const sanitizeNodeProps = (nodeProps) => {
1356
+ return omit(nodeProps, stylesToRemove, propsToRemove);
1357
+ };
1358
+
1359
+ const transformVisibility = (value) => {
1360
+ if (value === false) {
1361
+ return {
1362
+ display: 'none !important',
1363
+ };
1364
+ }
1365
+ // Don't explicitly set anything when visible to not overwrite values like `grid` or `flex`.
1366
+ return {};
1367
+ };
1368
+ // Keep this for backwards compatibility - deleting this would be a breaking change
1369
+ // because existing components on a users experience will have the width value as fill
1370
+ // rather than 100%
1371
+ const transformFill = (value) => (value === 'fill' ? '100%' : value);
1372
+ const transformGridColumn = (span) => {
1373
+ if (!span) {
1374
+ return {};
1375
+ }
1376
+ return {
1377
+ gridColumn: `span ${span}`,
1378
+ };
1379
+ };
1380
+ const transformBorderStyle = (value) => {
1381
+ if (!value)
1382
+ return {};
1383
+ const parts = value.split(' ');
1384
+ // Just accept the passed value
1385
+ if (parts.length < 3)
1386
+ return { border: value };
1387
+ const [borderSize, borderStyle, ...borderColorParts] = parts;
1388
+ const borderColor = borderColorParts.join(' ');
1389
+ return {
1390
+ border: `${borderSize} ${borderStyle} ${borderColor}`,
1391
+ };
1392
+ };
1393
+ const transformAlignment = (cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection = 'column') => cfFlexDirection === 'row'
1394
+ ? {
1395
+ alignItems: cfHorizontalAlignment,
1396
+ justifyContent: cfVerticalAlignment === 'center' ? `safe ${cfVerticalAlignment}` : cfVerticalAlignment,
1397
+ }
1398
+ : {
1399
+ alignItems: cfVerticalAlignment,
1400
+ justifyContent: cfHorizontalAlignment === 'center'
1401
+ ? `safe ${cfHorizontalAlignment}`
1402
+ : cfHorizontalAlignment,
1403
+ };
1404
+ const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions) => {
1405
+ const matchBackgroundSize = (scaling) => {
1406
+ if ('fill' === scaling)
1407
+ return 'cover';
1408
+ if ('fit' === scaling)
1409
+ return 'contain';
1410
+ };
1411
+ const matchBackgroundPosition = (alignment) => {
1412
+ if (!alignment || 'string' !== typeof alignment) {
1413
+ return;
1414
+ }
1415
+ let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
1416
+ // Special case for handling single values
1417
+ // for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
1418
+ if (horizontalAlignment && !verticalAlignment) {
1419
+ const singleValue = horizontalAlignment;
1420
+ switch (singleValue) {
1421
+ case 'left':
1422
+ horizontalAlignment = 'left';
1423
+ verticalAlignment = 'center';
1424
+ break;
1425
+ case 'right':
1426
+ horizontalAlignment = 'right';
1427
+ verticalAlignment = 'center';
1428
+ break;
1429
+ case 'center':
1430
+ horizontalAlignment = 'center';
1431
+ verticalAlignment = 'center';
1432
+ break;
1433
+ case 'top':
1434
+ horizontalAlignment = 'center';
1435
+ verticalAlignment = 'top';
1436
+ break;
1437
+ case 'bottom':
1438
+ horizontalAlignment = 'center';
1439
+ verticalAlignment = 'bottom';
1440
+ break;
1441
+ // just fall down to the normal validation logic for horiz and vert
1442
+ }
1443
+ }
1444
+ const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
1445
+ const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
1446
+ horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
1447
+ verticalAlignment = isVerticalValid ? verticalAlignment : 'top';
1448
+ return `${horizontalAlignment} ${verticalAlignment}`;
1449
+ };
1450
+ if (!cfBackgroundImageUrl) {
1451
+ return;
1452
+ }
1453
+ let backgroundImage;
1454
+ let backgroundImageSet;
1455
+ if (typeof cfBackgroundImageUrl === 'string') {
1456
+ backgroundImage = `url(${cfBackgroundImageUrl})`;
1457
+ }
1458
+ else {
1459
+ const imgSet = cfBackgroundImageUrl.srcSet?.join(',');
1460
+ backgroundImage = `url(${cfBackgroundImageUrl.url})`;
1461
+ backgroundImageSet = `image-set(${imgSet})`;
1462
+ }
1463
+ return {
1464
+ backgroundImage,
1465
+ backgroundImage2: backgroundImageSet,
1466
+ backgroundRepeat: cfBackgroundImageOptions?.scaling === 'tile' ? 'repeat' : 'no-repeat',
1467
+ backgroundPosition: matchBackgroundPosition(cfBackgroundImageOptions?.alignment),
1468
+ backgroundSize: matchBackgroundSize(cfBackgroundImageOptions?.scaling),
1469
+ };
1470
+ };
1471
+
1472
+ const toCSSAttribute = (key) => {
1473
+ let val = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
1474
+ // Remove the number from the end of the key to allow for overrides on style properties
1475
+ val = val.replace(/\d+$/, '');
1476
+ return val;
1477
+ };
1478
+ /**
1479
+ * Turns a list of CSSProperties into a joined CSS string that can be
1480
+ * used for <style> tags. Per default it creates a minimized version.
1481
+ * For editor mode, use the `useWhitespaces` flag to create a more readable version.
1482
+ *
1483
+ * @param cssProperties list of CSS properties
1484
+ * @param useWhitespaces adds whitespaces and newlines between each rule
1485
+ * @returns a string of CSS rules
1486
+ */
1487
+ const stringifyCssProperties = (cssProperties, useWhitespaces = false) => {
1488
+ const rules = Object.entries(cssProperties)
1489
+ .filter(([, value]) => value !== undefined)
1490
+ .map(([key, value]) => useWhitespaces ? `${toCSSAttribute(key)}: ${value};` : `${toCSSAttribute(key)}:${value};`);
1491
+ return rules.join(useWhitespaces ? '\n' : '');
1492
+ };
1493
+ const buildStyleTag = ({ styles, nodeId }) => {
1494
+ const generatedStyles = stringifyCssProperties(styles, true);
1495
+ const className = `cfstyles-${nodeId ? nodeId : md5(generatedStyles)}`;
1496
+ const styleRule = `.${className}{ ${generatedStyles} }`;
1497
+ return [className, styleRule];
1498
+ };
1499
+ /**
1500
+ * Takes plain design values and transforms them into CSS properties. Undefined values will
1501
+ * be filtered out.
1502
+ *
1503
+ * **Example Input**
1504
+ * ```
1505
+ * values = {
1506
+ * cfVisibility: 'visible',
1507
+ * cfMargin: '10px',
1508
+ * cfFlexReverse: true,
1509
+ * cfImageOptions: { objectFit: 'cover' },
1510
+ * // ...
1511
+ * }
1512
+ * ```
1513
+ * **Example Output**
1514
+ * ```
1515
+ * cssProperties = {
1516
+ * margin: '10px',
1517
+ * flexDirection: 'row-reverse',
1518
+ * objectFit: 'cover',
1519
+ * // ...
1520
+ * }
1521
+ * ```
1522
+ */
1523
+ const buildCfStyles = (values) => {
1524
+ const cssProperties = {
1525
+ boxSizing: 'border-box',
1526
+ ...transformVisibility(values.cfVisibility),
1527
+ margin: values.cfMargin,
1528
+ padding: values.cfPadding,
1529
+ backgroundColor: values.cfBackgroundColor,
1530
+ width: transformFill(values.cfWidth || values.cfImageOptions?.width),
1531
+ height: transformFill(values.cfHeight || values.cfImageOptions?.height),
1532
+ maxWidth: values.cfMaxWidth,
1533
+ ...transformGridColumn(values.cfColumnSpan),
1534
+ ...transformBorderStyle(values.cfBorder),
1535
+ borderRadius: values.cfBorderRadius,
1536
+ gap: values.cfGap,
1537
+ ...transformAlignment(values.cfHorizontalAlignment, values.cfVerticalAlignment, values.cfFlexDirection),
1538
+ flexDirection: values.cfFlexReverse && values.cfFlexDirection
1539
+ ? `${values.cfFlexDirection}-reverse`
1540
+ : values.cfFlexDirection,
1541
+ flexWrap: values.cfFlexWrap,
1542
+ ...transformBackgroundImage(values.cfBackgroundImageUrl, values.cfBackgroundImageOptions),
1543
+ fontSize: values.cfFontSize,
1544
+ fontWeight: values.cfTextBold ? 'bold' : values.cfFontWeight,
1545
+ fontStyle: values.cfTextItalic ? 'italic' : undefined,
1546
+ textDecoration: values.cfTextUnderline ? 'underline' : undefined,
1547
+ lineHeight: values.cfLineHeight,
1548
+ letterSpacing: values.cfLetterSpacing,
1549
+ color: values.cfTextColor,
1550
+ textAlign: values.cfTextAlign,
1551
+ textTransform: values.cfTextTransform,
1552
+ objectFit: values.cfImageOptions?.objectFit,
1553
+ objectPosition: values.cfImageOptions?.objectPosition,
1554
+ };
1555
+ const cssPropertiesWithoutUndefined = Object.fromEntries(Object.entries(cssProperties).filter(([, value]) => value !== undefined));
1556
+ return cssPropertiesWithoutUndefined;
1557
+ };
1558
+ /**
1559
+ * Container/section default behavior:
1560
+ * Default height => height: EMPTY_CONTAINER_HEIGHT
1561
+ * If a container component has children => height: 'fit-content'
1562
+ */
1563
+ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
1564
+ if (!blockId || !isContentfulStructureComponent(blockId) || value !== 'auto') {
1565
+ return value;
1566
+ }
1567
+ if (children.length) {
1568
+ return '100%';
1569
+ }
1570
+ return EMPTY_CONTAINER_HEIGHT$1;
1571
+ };
1572
+
1573
+ function getOptimizedImageUrl(url, width, quality, format) {
1574
+ if (url.startsWith('//')) {
1575
+ url = 'https:' + url;
1576
+ }
1577
+ const params = new URLSearchParams();
1578
+ if (width) {
1579
+ params.append('w', width.toString());
1580
+ }
1581
+ if (quality && quality > 0 && quality < 100) {
1582
+ params.append('q', quality.toString());
1583
+ }
1584
+ if (format) {
1585
+ params.append('fm', format);
1586
+ }
1587
+ const queryString = params.toString();
1588
+ return `${url}${queryString ? '?' + queryString : ''}`;
1589
+ }
1590
+
1591
+ function validateParams(file, quality, format) {
1592
+ if (!file.details.image) {
1593
+ throw Error('No image in file asset to transform');
1594
+ }
1595
+ if (quality < 0 || quality > 100) {
1596
+ throw Error('Quality must be between 0 and 100');
1597
+ }
1598
+ if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
1599
+ throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
1600
+ }
1601
+ return true;
1602
+ }
1603
+
1604
+ const MAX_WIDTH_ALLOWED$1 = 2000;
1605
+ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', format) => {
1606
+ const qualityNumber = Number(quality.replace('%', ''));
1607
+ if (!validateParams(file, qualityNumber, format)) ;
1608
+ if (!validateParams(file, qualityNumber, format)) ;
1609
+ const url = file.url;
1610
+ const { width1x, width2x } = getWidths(widthStyle, file);
1611
+ const imageUrl1x = getOptimizedImageUrl(url, width1x, qualityNumber, format);
1612
+ const imageUrl2x = getOptimizedImageUrl(url, width2x, qualityNumber, format);
1613
+ const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
1614
+ const returnedUrl = getOptimizedImageUrl(url, width2x, qualityNumber, format);
1615
+ const optimizedBackgroundImageAsset = {
1616
+ url: returnedUrl,
1617
+ srcSet,
1618
+ file,
1619
+ };
1620
+ return optimizedBackgroundImageAsset;
1621
+ function getWidths(widthStyle, file) {
1622
+ let width1x = 0;
1623
+ let width2x = 0;
1624
+ const intrinsicImageWidth = file.details.image.width;
1625
+ if (widthStyle.endsWith('px')) {
1626
+ width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
1627
+ }
1628
+ else {
1629
+ width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
1630
+ }
1631
+ width2x = Math.min(width1x * 2, intrinsicImageWidth);
1632
+ return { width1x, width2x };
1633
+ }
1634
+ };
1635
+
1636
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1637
+ function get(obj, path) {
1638
+ if (!path.length) {
1639
+ return obj;
1640
+ }
1641
+ try {
1642
+ const [currentPath, ...nextPath] = path;
1643
+ return get(obj[currentPath], nextPath);
1644
+ }
1645
+ catch (err) {
1646
+ return undefined;
1647
+ }
1648
+ }
1649
+
1650
+ const getBoundValue = (entryOrAsset, path) => {
1651
+ const value = get(entryOrAsset, path.split('/').slice(2, -1));
1652
+ return value && typeof value == 'object' && value.url
1653
+ ? value.url
1654
+ : value;
1655
+ };
1656
+
1657
+ const transformRichText = (entryOrAsset, entityStore, path) => {
1658
+ const value = getBoundValue(entryOrAsset, path);
1659
+ if (typeof value === 'string') {
1660
+ return {
1661
+ data: {},
1662
+ content: [
1663
+ {
1664
+ nodeType: BLOCKS.PARAGRAPH,
1665
+ data: {},
1666
+ content: [
1667
+ {
1668
+ data: {},
1669
+ nodeType: 'text',
1670
+ value: value,
1671
+ marks: [],
1672
+ },
1673
+ ],
1674
+ },
1675
+ ],
1676
+ nodeType: BLOCKS.DOCUMENT,
1677
+ };
1678
+ }
1679
+ if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
1680
+ // resolve any links to assets/entries/hyperlinks
1681
+ const richTextDocument = value;
1682
+ resolveLinks(richTextDocument, entityStore);
1683
+ return richTextDocument;
1684
+ }
1685
+ return undefined;
1686
+ };
1687
+ const isLinkTarget = (node) => {
1688
+ return node?.data?.target?.sys?.type === 'Link';
1689
+ };
1690
+ const resolveLinks = (node, entityStore) => {
1691
+ if (!node)
1692
+ return;
1693
+ // Resolve link if current node has one
1694
+ if (isLinkTarget(node)) {
1695
+ const entity = entityStore.getEntityFromLink(node.data.target);
1696
+ if (entity) {
1697
+ node.data.target = entity;
1698
+ }
1699
+ }
1700
+ // Process content array if it exists
1701
+ if ('content' in node && Array.isArray(node.content)) {
1702
+ node.content.forEach((childNode) => resolveLinks(childNode, entityStore));
1703
+ }
1704
+ };
1705
+
1706
+ const MAX_WIDTH_ALLOWED = 4000;
1707
+ const getOptimizedImageAsset = ({ file, sizes, loading, quality = '100%', format, }) => {
1708
+ const qualityNumber = Number(quality.replace('%', ''));
1709
+ if (!validateParams(file, qualityNumber, format)) ;
1710
+ const url = file.url;
1711
+ const maxWidth = Math.min(file.details.image.width, MAX_WIDTH_ALLOWED);
1712
+ const numOfParts = Math.max(2, Math.ceil(maxWidth / 500));
1713
+ const widthParts = Array.from({ length: numOfParts }, (_, index) => Math.ceil((index + 1) * (maxWidth / numOfParts)));
1714
+ const srcSet = sizes
1715
+ ? widthParts.map((width) => `${getOptimizedImageUrl(url, width, qualityNumber, format)} ${width}w`)
1716
+ : [];
1717
+ const intrinsicImageWidth = file.details.image.width;
1718
+ if (intrinsicImageWidth > MAX_WIDTH_ALLOWED) {
1719
+ srcSet.push(`${getOptimizedImageUrl(url, undefined, qualityNumber, format)} ${intrinsicImageWidth}w`);
1720
+ }
1721
+ const returnedUrl = getOptimizedImageUrl(url, file.details.image.width > 2000 ? 2000 : undefined, qualityNumber, format);
1722
+ const optimizedImageAsset = {
1723
+ url: returnedUrl,
1724
+ srcSet,
1725
+ sizes,
1726
+ file,
1727
+ loading,
1728
+ };
1729
+ return optimizedImageAsset;
1730
+ };
1731
+
1732
+ const transformMedia = (asset, variables, resolveDesignValue, variableName, path) => {
1733
+ let value;
1734
+ // If it is not a deep path and not pointing to the file of the asset,
1735
+ // it is just pointing to a normal field and therefore we just resolve the value as normal field
1736
+ if (!isDeepPath(path) && !lastPathNamedSegmentEq(path, 'file')) {
1737
+ return getBoundValue(asset, path);
1738
+ }
1739
+ //TODO: this will be better served by injectable type transformers instead of if statement
1740
+ if (variableName === 'cfImageAsset') {
1741
+ const optionsVariableName = 'cfImageOptions';
1742
+ const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
1743
+ ? variables[optionsVariableName].valuesByBreakpoint
1744
+ : {}, optionsVariableName);
1745
+ if (!options) {
1746
+ console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
1747
+ return;
1748
+ }
1749
+ try {
1750
+ value = getOptimizedImageAsset({
1751
+ file: asset.fields.file,
1752
+ loading: options.loading,
1753
+ sizes: options.targetSize,
1754
+ quality: options.quality,
1755
+ format: options.format,
1756
+ });
1757
+ return value;
1758
+ }
1759
+ catch (error) {
1760
+ console.error('Error transforming image asset', error);
1761
+ }
1762
+ return;
1763
+ }
1764
+ if (variableName === 'cfBackgroundImageUrl') {
1765
+ let width = resolveDesignValue(variables['cfWidth']?.type === 'DesignValue' ? variables['cfWidth'].valuesByBreakpoint : {}, 'cfWidth') || '100%';
1766
+ const optionsVariableName = 'cfBackgroundImageOptions';
1767
+ const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
1768
+ ? variables[optionsVariableName].valuesByBreakpoint
1769
+ : {}, optionsVariableName);
1770
+ if (!options) {
1771
+ console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
1772
+ return;
1773
+ }
1774
+ try {
1775
+ // Target width (px/rem/em) will be applied to the css url if it's lower than the original image width (in px)
1776
+ const assetDetails = asset.fields.file?.details;
1777
+ const assetWidth = assetDetails?.image?.width || 0; // This is always in px
1778
+ const targetWidthObject = parseCSSValue(options.targetSize); // Contains value and unit (px/rem/em) so convert and then compare to assetWidth
1779
+ const targetValue = targetWidthObject
1780
+ ? getTargetValueInPixels(targetWidthObject)
1781
+ : assetWidth;
1782
+ if (targetValue < assetWidth)
1783
+ width = `${targetValue}px`;
1784
+ value = getOptimizedBackgroundImageAsset(asset.fields.file, width, options.quality, options.format);
1785
+ return value;
1786
+ }
1787
+ catch (error) {
1788
+ console.error('Error transforming image asset', error);
1789
+ }
1790
+ return;
1791
+ }
1792
+ return asset.fields.file?.url;
1793
+ };
1794
+
1795
+ function getResolvedEntryFromLink(entryOrAsset, path, entityStore) {
1796
+ if (entryOrAsset.sys.type === 'Asset') {
1797
+ return entryOrAsset;
1798
+ }
1799
+ const value = get(entryOrAsset, path.split('/').slice(2, -1));
1800
+ if (value?.sys.type !== 'Link') {
1801
+ console.warn(`Expected a link to a reference, but got: ${JSON.stringify(value)}`);
1802
+ return;
1803
+ }
1804
+ //Look up the reference in the entity store
1805
+ const resolvedEntity = entityStore.getEntityFromLink(value);
1806
+ if (!resolvedEntity) {
1807
+ return;
1808
+ }
1809
+ //resolve any embedded links - we currently only support 2 levels deep
1810
+ const fields = resolvedEntity.fields || {};
1811
+ Object.entries(fields).forEach(([fieldKey, field]) => {
1812
+ if (field && field.sys?.type === 'Link') {
1813
+ const entity = entityStore.getEntityFromLink(field);
1814
+ if (entity) {
1815
+ resolvedEntity.fields[fieldKey] = entity;
1816
+ }
1817
+ }
1818
+ else if (field && Array.isArray(field)) {
1819
+ resolvedEntity.fields[fieldKey] = field.map((innerField) => {
1820
+ if (innerField && innerField.sys?.type === 'Link') {
1821
+ const entity = entityStore.getEntityFromLink(innerField);
1822
+ if (entity) {
1823
+ return entity;
1824
+ }
1825
+ }
1826
+ return innerField;
1827
+ });
1828
+ }
1829
+ });
1830
+ return resolvedEntity;
1831
+ }
1832
+
1833
+ function getArrayValue(entryOrAsset, path, entityStore) {
1834
+ if (entryOrAsset.sys.type === 'Asset') {
1835
+ return entryOrAsset;
1836
+ }
1837
+ const arrayValue = get(entryOrAsset, path.split('/').slice(2, -1));
1838
+ if (!isArray(arrayValue)) {
1839
+ console.warn(`Expected a value to be an array, but got: ${JSON.stringify(arrayValue)}`);
1840
+ return;
1841
+ }
1842
+ const result = arrayValue.map((value) => {
1843
+ if (typeof value === 'string') {
1844
+ return value;
1865
1845
  }
1866
- else if (isSegmentBeginningOfChunk(segment)) {
1867
- chunks.push(currentChunk);
1868
- currentChunk = [segment];
1846
+ else if (value?.sys?.type === 'Link') {
1847
+ const resolvedEntity = entityStore.getEntityFromLink(value);
1848
+ if (!resolvedEntity) {
1849
+ return;
1850
+ }
1851
+ //resolve any embedded links - we currently only support 2 levels deep
1852
+ const fields = resolvedEntity.fields || {};
1853
+ Object.entries(fields).forEach(([fieldKey, field]) => {
1854
+ if (field && field.sys?.type === 'Link') {
1855
+ const entity = entityStore.getEntityFromLink(field);
1856
+ if (entity) {
1857
+ resolvedEntity.fields[fieldKey] = entity;
1858
+ }
1859
+ }
1860
+ });
1861
+ return resolvedEntity;
1869
1862
  }
1870
1863
  else {
1871
- currentChunk.push(segment);
1864
+ console.warn(`Expected value to be a string or Link, but got: ${JSON.stringify(value)}`);
1865
+ return undefined;
1872
1866
  }
1867
+ });
1868
+ return result;
1869
+ }
1870
+
1871
+ const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableType, path) => {
1872
+ const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
1873
+ if (!entityOrAsset)
1874
+ return;
1875
+ switch (variableType) {
1876
+ case 'Media':
1877
+ // If we bound a normal entry field to the media variable we just return the bound value
1878
+ if (entityOrAsset.sys.type === 'Entry') {
1879
+ return getBoundValue(entityOrAsset, path);
1880
+ }
1881
+ return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
1882
+ case 'RichText':
1883
+ return transformRichText(entityOrAsset, entityStore, path);
1884
+ case 'Array':
1885
+ return getArrayValue(entityOrAsset, path, entityStore);
1886
+ case 'Link':
1887
+ return getResolvedEntryFromLink(entityOrAsset, path, entityStore);
1888
+ default:
1889
+ return getBoundValue(entityOrAsset, path);
1873
1890
  }
1874
- chunks.push(currentChunk);
1875
- return chunks.filter(excludeEmptyChunks);
1876
- };
1877
- const lastPathNamedSegmentEq = (path, expectedName) => {
1878
- // `/key123/fields/featureImage/~locale/fields/file/~locale`
1879
- // ['', 'key123', 'fields', 'featureImage', '~locale', 'fields', 'file', '~locale']
1880
- const segments = path.split('/');
1881
- if (segments.length < 2) {
1882
- console.warn(`[experiences-sdk-react] Attempting to check whether last named segment of the path (${path}) equals to '${expectedName}', but the path doesn't have enough segments.`);
1883
- return false;
1884
- }
1885
- const secondLast = segments[segments.length - 2]; // skipping trailing '~locale'
1886
- return secondLast === expectedName;
1887
1891
  };
1888
1892
 
1889
- const resolveHyperlinkPattern = (pattern, entry, locale) => {
1890
- if (!entry || !locale)
1891
- return null;
1892
- const variables = {
1893
- entry,
1894
- locale,
1893
+ const getDataFromTree = (tree) => {
1894
+ let dataSource = {};
1895
+ let unboundValues = {};
1896
+ const queue = [...tree.root.children];
1897
+ while (queue.length) {
1898
+ const node = queue.shift();
1899
+ if (!node) {
1900
+ continue;
1901
+ }
1902
+ dataSource = { ...dataSource, ...node.data.dataSource };
1903
+ unboundValues = { ...unboundValues, ...node.data.unboundValues };
1904
+ if (node.children.length) {
1905
+ queue.push(...node.children);
1906
+ }
1907
+ }
1908
+ return {
1909
+ dataSource,
1910
+ unboundValues,
1895
1911
  };
1896
- return buildTemplate({ template: pattern, context: variables });
1897
1912
  };
1898
- function getValue(obj, path) {
1899
- return path
1900
- .replace(/\[/g, '.')
1901
- .replace(/\]/g, '')
1902
- .split('.')
1903
- .reduce((o, k) => (o || {})[k], obj);
1904
- }
1905
- function addLocale(str, locale) {
1906
- const fieldsIndicator = 'fields';
1907
- const fieldsIndex = str.indexOf(fieldsIndicator);
1908
- if (fieldsIndex !== -1) {
1909
- const dotIndex = str.indexOf('.', fieldsIndex + fieldsIndicator.length + 1); // +1 for '.'
1910
- if (dotIndex !== -1) {
1911
- return str.slice(0, dotIndex + 1) + locale + '.' + str.slice(dotIndex + 1);
1912
- }
1913
+ function parseCSSValue(input) {
1914
+ const regex = /^(\d+(\.\d+)?)(px|em|rem)$/;
1915
+ const match = input.match(regex);
1916
+ if (match) {
1917
+ return {
1918
+ value: parseFloat(match[1]),
1919
+ unit: match[3],
1920
+ };
1913
1921
  }
1914
- return str;
1915
- }
1916
- function getTemplateValue(ctx, path) {
1917
- const pathWithLocale = addLocale(path, ctx.locale);
1918
- const retrievedValue = getValue(ctx, pathWithLocale);
1919
- return typeof retrievedValue === 'object' && retrievedValue !== null
1920
- ? retrievedValue[ctx.locale]
1921
- : retrievedValue;
1922
+ return null;
1922
1923
  }
1923
- function buildTemplate({ template, context, }) {
1924
- const localeVariable = /{\s*locale\s*}/g;
1925
- // e.g. "{ page.sys.id }"
1926
- const variables = /{\s*([\S]+?)\s*}/g;
1927
- return (template
1928
- // first replace the locale pattern
1929
- .replace(localeVariable, context.locale)
1930
- // then resolve the remaining variables
1931
- .replace(variables, (_, path) => {
1932
- const fallback = path + '_NOT_FOUND';
1933
- const value = getTemplateValue(context, path) ?? fallback;
1934
- // using _.result didn't gave proper results so we run our own version of it
1935
- return String(typeof value === 'function' ? value() : value);
1936
- }));
1924
+ function getTargetValueInPixels(targetWidthObject) {
1925
+ switch (targetWidthObject.unit) {
1926
+ case 'px':
1927
+ return targetWidthObject.value;
1928
+ case 'em':
1929
+ return targetWidthObject.value * 16;
1930
+ case 'rem':
1931
+ return targetWidthObject.value * 16;
1932
+ default:
1933
+ return targetWidthObject.value;
1934
+ }
1937
1935
  }
1938
1936
 
1939
- const stylesToKeep = ['cfImageAsset'];
1940
- const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.includes(style));
1941
- const propsToRemove = ['cfHyperlink', 'cfOpenInNewTab', 'cfSsrClassName'];
1942
- const sanitizeNodeProps = (nodeProps) => {
1943
- return omit(nodeProps, stylesToRemove, propsToRemove);
1944
- };
1945
-
1946
- const CF_DEBUG_KEY$1 = 'cf_debug';
1947
- let DebugLogger$1 = class DebugLogger {
1948
- constructor() {
1949
- // Public methods for logging
1950
- this.error = this.logger('error');
1951
- this.warn = this.logger('warn');
1952
- this.log = this.logger('log');
1953
- this.debug = this.logger('debug');
1954
- if (typeof localStorage === 'undefined') {
1955
- this.enabled = false;
1956
- return;
1957
- }
1958
- // Default to checking localStorage for the debug mode on initialization if in browser
1959
- this.enabled = localStorage.getItem(CF_DEBUG_KEY$1) === 'true';
1937
+ class ParseError extends Error {
1938
+ constructor(message) {
1939
+ super(message);
1960
1940
  }
1961
- static getInstance() {
1962
- if (this.instance === null) {
1963
- this.instance = new DebugLogger();
1941
+ }
1942
+ const isValidJsonObject = (s) => {
1943
+ try {
1944
+ const result = JSON.parse(s);
1945
+ if ('object' !== typeof result) {
1946
+ return false;
1964
1947
  }
1965
- return this.instance;
1948
+ return true;
1966
1949
  }
1967
- getEnabled() {
1968
- return this.enabled;
1950
+ catch (e) {
1951
+ return false;
1969
1952
  }
1970
- setEnabled(enabled) {
1971
- this.enabled = enabled;
1972
- if (typeof localStorage === 'undefined') {
1973
- return;
1974
- }
1975
- if (enabled) {
1976
- localStorage.setItem(CF_DEBUG_KEY$1, 'true');
1977
- }
1978
- else {
1979
- localStorage.removeItem(CF_DEBUG_KEY$1);
1953
+ };
1954
+ const doesMismatchMessageSchema = (event) => {
1955
+ try {
1956
+ tryParseMessage(event);
1957
+ return false;
1958
+ }
1959
+ catch (e) {
1960
+ if (e instanceof ParseError) {
1961
+ return e.message;
1980
1962
  }
1963
+ throw e;
1981
1964
  }
1982
- // Log method for different levels (error, warn, log)
1983
- logger(level) {
1984
- return (...args) => {
1985
- if (this.enabled) {
1986
- console[level]('[cf-experiences-sdk]', ...args);
1987
- }
1988
- };
1965
+ };
1966
+ const tryParseMessage = (event) => {
1967
+ if (!event.data) {
1968
+ throw new ParseError('Field event.data is missing');
1969
+ }
1970
+ if ('string' !== typeof event.data) {
1971
+ throw new ParseError(`Field event.data must be a string, instead of '${typeof event.data}'`);
1972
+ }
1973
+ if (!isValidJsonObject(event.data)) {
1974
+ throw new ParseError('Field event.data must be a valid JSON object serialized as string');
1975
+ }
1976
+ const eventData = JSON.parse(event.data);
1977
+ if (!eventData.source) {
1978
+ throw new ParseError(`Field eventData.source must be equal to 'composability-app'`);
1989
1979
  }
1980
+ if ('composability-app' !== eventData.source) {
1981
+ throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
1982
+ }
1983
+ // check eventData.eventType
1984
+ const supportedEventTypes = Object.values(INCOMING_EVENTS$1);
1985
+ if (!supportedEventTypes.includes(eventData.eventType)) {
1986
+ // Expected message: This message is handled in the EntityStore to store fetched entities
1987
+ if (eventData.eventType !== PostMessageMethods$3.REQUESTED_ENTITIES) {
1988
+ throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
1989
+ }
1990
+ }
1991
+ return eventData;
1990
1992
  };
1991
- DebugLogger$1.instance = null;
1992
- DebugLogger$1.getInstance();
1993
1993
 
1994
1994
  const sendMessage = (eventType, data) => {
1995
1995
  if (typeof window === 'undefined') {
@@ -5375,8 +5375,8 @@ const getTooltipPositions = ({ previewSize, tooltipRect, coordinates, }) => {
5375
5375
  return newTooltipStyles;
5376
5376
  };
5377
5377
 
5378
- var css_248z$1 = ".styles-module_DraggableComponent__oyE7Q,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO) {\n position: relative;\n transition: background-color 0.2s;\n pointer-events: all;\n box-sizing: border-box;\n cursor: grab;\n}\n\n.styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -2px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm:before {\n outline-offset: -1px;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm {\n pointer-events: all;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_isAssembly__HstYv {\n width: 100%;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_fullHeight__afMfT {\n height: 100%;\n}\n\n.styles-module_isRoot__c-c-x,\n.styles-module_isEmptyCanvas__Mm6Al {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__XZ1Ej {\n min-height: 80px;\n min-width: 80px;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_isRoot__c-c-x):not(.styles-module_DraggableClone__CdKIH):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_isDestination__sE70P:not(.styles-module_isRoot__c-c-x):before {\n transition:\n outline 0.2s,\n background-color 0.2s;\n outline: 2px dashed var(--exp-builder-blue400);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n z-index: 2;\n}\n\n.styles-module_DraggableClone__CdKIH:before {\n outline: 2px solid var(--exp-builder-blue500);\n}\n\n.styles-module_DropzoneClone__xiT8j,\n.styles-module_DraggableClone__CdKIH,\n.styles-module_DropzoneClone__xiT8j *,\n.styles-module_DraggableClone__CdKIH * {\n pointer-events: none !important;\n}\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4) :not(.styles-module_DraggableComponent__oyE7Q) {\n pointer-events: none;\n}\n\n.styles-module_isDraggingThisComponent__yCZTp {\n overflow: hidden;\n}\n\n.styles-module_isSelected__c2QEJ:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_tooltipWrapper__kqvmR {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n z-index: 10;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4 .styles-module_tooltipWrapper__kqvmR {\n display: none;\n}\n\n.styles-module_overlay__knwhE {\n position: absolute;\n display: flex;\n align-items: center;\n min-width: max-content;\n height: 24px;\n z-index: 2;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 14px;\n font-weight: 500;\n background-color: var(--exp-builder-gray500);\n color: var(--exp-builder-color-white);\n padding: 4px 12px 4px 12px;\n transition: opacity 0.1s;\n opacity: 0;\n text-wrap: nowrap;\n}\n\n.styles-module_overlayContainer__lUsiC {\n opacity: 0;\n}\n\n.styles-module_overlayAssembly__3BKl4 {\n background-color: var(--exp-builder-purple600);\n}\n\n.styles-module_isDragging__hldL4 > .styles-module_overlay__knwhE,\n.styles-module_isDragging__hldL4 > .styles-module_overlayContainer__lUsiC {\n opacity: 0 !important;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_Dropzone__3R-sm):before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_isHoveringComponent__f7G5m > div > .styles-module_overlay__knwhE,\n.styles-module_DraggableComponent__oyE7Q:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)) > div > .styles-module_overlay__knwhE {\n opacity: 1;\n}\n\n/* hovering related component in layers tab */\n\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_DraggableComponent__oyE7Q:before {\n outline: 2px dashed var(--exp-builder-gray500);\n}\n\n/* hovering component in layers tab */\n\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-gray500);\n}\n\n/* hovering related pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m) .styles-module_isAssemblyBlock__goT9z:not(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isHoveringComponent__f7G5m .styles-module_isAssemblyBlock__goT9z:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:before,\n.styles-module_isAssemblyBlock__goT9z:hover .styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_isAssemblyBlock__goT9z:before {\n outline: 2px dashed var(--exp-builder-purple600);\n}\n\n/* hovering pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z.styles-module_isHoveringComponent__f7G5m:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-purple600);\n}\n";
5379
- var styles$1 = {"DraggableComponent":"styles-module_DraggableComponent__oyE7Q","Dropzone":"styles-module_Dropzone__3R-sm","isSlot":"styles-module_isSlot__HI9yO","isDragging":"styles-module_isDragging__hldL4","isAssembly":"styles-module_isAssembly__HstYv","fullHeight":"styles-module_fullHeight__afMfT","isRoot":"styles-module_isRoot__c-c-x","isEmptyCanvas":"styles-module_isEmptyCanvas__Mm6Al","isEmptyZone":"styles-module_isEmptyZone__XZ1Ej","DraggableClone":"styles-module_DraggableClone__CdKIH","isDestination":"styles-module_isDestination__sE70P","DropzoneClone":"styles-module_DropzoneClone__xiT8j","isDraggingThisComponent":"styles-module_isDraggingThisComponent__yCZTp","isSelected":"styles-module_isSelected__c2QEJ","tooltipWrapper":"styles-module_tooltipWrapper__kqvmR","overlay":"styles-module_overlay__knwhE","overlayContainer":"styles-module_overlayContainer__lUsiC","overlayAssembly":"styles-module_overlayAssembly__3BKl4","isHoveringComponent":"styles-module_isHoveringComponent__f7G5m","isAssemblyBlock":"styles-module_isAssemblyBlock__goT9z"};
5378
+ var css_248z$1 = ".styles-module_DraggableComponent__oyE7Q,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO) {\n position: relative;\n transition: background-color 0.2s;\n pointer-events: all;\n box-sizing: border-box;\n cursor: grab;\n}\n\n.styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -2px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm:before {\n outline-offset: -1px;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm {\n pointer-events: all;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_fullHeight__afMfT {\n height: 100%;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_fullWidth__Od117 {\n width: 100%;\n}\n\n.styles-module_isRoot__c-c-x,\n.styles-module_isEmptyCanvas__Mm6Al {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__XZ1Ej {\n min-height: 80px;\n min-width: 80px;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_isRoot__c-c-x):not(.styles-module_DraggableClone__CdKIH):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_isDestination__sE70P:not(.styles-module_isRoot__c-c-x):before {\n transition:\n outline 0.2s,\n background-color 0.2s;\n outline: 2px dashed var(--exp-builder-blue400);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n z-index: 2;\n}\n\n.styles-module_DraggableClone__CdKIH:before {\n outline: 2px solid var(--exp-builder-blue500);\n}\n\n.styles-module_DropzoneClone__xiT8j,\n.styles-module_DraggableClone__CdKIH,\n.styles-module_DropzoneClone__xiT8j *,\n.styles-module_DraggableClone__CdKIH * {\n pointer-events: none !important;\n}\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4) :not(.styles-module_DraggableComponent__oyE7Q) {\n pointer-events: none;\n}\n\n.styles-module_isDraggingThisComponent__yCZTp {\n overflow: hidden;\n}\n\n.styles-module_isSelected__c2QEJ:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_tooltipWrapper__kqvmR {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n z-index: 10;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4 .styles-module_tooltipWrapper__kqvmR {\n display: none;\n}\n\n.styles-module_overlay__knwhE {\n position: absolute;\n display: flex;\n align-items: center;\n min-width: max-content;\n height: 24px;\n z-index: 2;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 14px;\n font-weight: 500;\n background-color: var(--exp-builder-gray500);\n color: var(--exp-builder-color-white);\n padding: 4px 12px 4px 12px;\n transition: opacity 0.1s;\n opacity: 0;\n text-wrap: nowrap;\n}\n\n.styles-module_overlayContainer__lUsiC {\n opacity: 0;\n}\n\n.styles-module_overlayAssembly__3BKl4 {\n background-color: var(--exp-builder-purple600);\n}\n\n.styles-module_isDragging__hldL4 > .styles-module_overlay__knwhE,\n.styles-module_isDragging__hldL4 > .styles-module_overlayContainer__lUsiC {\n opacity: 0 !important;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_Dropzone__3R-sm):before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_isHoveringComponent__f7G5m > div > .styles-module_overlay__knwhE,\n.styles-module_DraggableComponent__oyE7Q:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)) > div > .styles-module_overlay__knwhE {\n opacity: 1;\n}\n\n/* hovering related component in layers tab */\n\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_DraggableComponent__oyE7Q:before {\n outline: 2px dashed var(--exp-builder-gray500);\n}\n\n/* hovering component in layers tab */\n\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-gray500);\n}\n\n/* hovering related pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m) .styles-module_isAssemblyBlock__goT9z:not(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isHoveringComponent__f7G5m .styles-module_isAssemblyBlock__goT9z:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:before,\n.styles-module_isAssemblyBlock__goT9z:hover .styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_isAssemblyBlock__goT9z:before {\n outline: 2px dashed var(--exp-builder-purple600);\n}\n\n/* hovering pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z.styles-module_isHoveringComponent__f7G5m:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-purple600);\n}\n";
5379
+ var styles$1 = {"DraggableComponent":"styles-module_DraggableComponent__oyE7Q","Dropzone":"styles-module_Dropzone__3R-sm","isSlot":"styles-module_isSlot__HI9yO","isDragging":"styles-module_isDragging__hldL4","fullHeight":"styles-module_fullHeight__afMfT","fullWidth":"styles-module_fullWidth__Od117","isRoot":"styles-module_isRoot__c-c-x","isEmptyCanvas":"styles-module_isEmptyCanvas__Mm6Al","isEmptyZone":"styles-module_isEmptyZone__XZ1Ej","DraggableClone":"styles-module_DraggableClone__CdKIH","isDestination":"styles-module_isDestination__sE70P","DropzoneClone":"styles-module_DropzoneClone__xiT8j","isDraggingThisComponent":"styles-module_isDraggingThisComponent__yCZTp","isSelected":"styles-module_isSelected__c2QEJ","tooltipWrapper":"styles-module_tooltipWrapper__kqvmR","overlay":"styles-module_overlay__knwhE","overlayContainer":"styles-module_overlayContainer__lUsiC","overlayAssembly":"styles-module_overlayAssembly__3BKl4","isHoveringComponent":"styles-module_isHoveringComponent__f7G5m","isAssemblyBlock":"styles-module_isAssemblyBlock__goT9z"};
5380
5380
  styleInject(css_248z$1);
5381
5381
 
5382
5382
  const Tooltip = ({ coordinates, id, label, isAssemblyBlock, isContainer, isSelected, }) => {
@@ -5734,6 +5734,10 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5734
5734
  ? node.children.length === 1 &&
5735
5735
  resolveDesignValue(node?.children[0]?.data.props.cfHeight?.valuesByBreakpoint ?? {}, 'cfHeight') === '100%'
5736
5736
  : false;
5737
+ const isPatternWrapperComponentFullWidth = isRootAssembly
5738
+ ? node.children.length === 1 &&
5739
+ resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
5740
+ : false;
5737
5741
  return (React.createElement(Droppable, { droppableId: zoneId, direction: direction, isDropDisabled: !isDropzoneEnabled, renderClone: (provided, snapshot, rubic) => (React.createElement(EditorBlockClone, { node: content[rubic.source.index], resolveDesignValue: resolveDesignValue, provided: provided, snapshot: snapshot, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds })) }, (provided, snapshot) => {
5738
5742
  return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
5739
5743
  if (dragProps?.innerRef) {
@@ -5746,9 +5750,9 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
5746
5750
  [styles$1.isDestination]: isDestination && !isAssembly,
5747
5751
  [styles$1.isRoot]: isRootZone,
5748
5752
  [styles$1.isEmptyZone]: !content.length,
5749
- [styles$1.isAssembly]: isRootAssembly,
5750
5753
  [styles$1.isSlot]: Boolean(slotId),
5751
5754
  [styles$1.fullHeight]: isPatternWrapperComponentFullHeight,
5755
+ [styles$1.fullWidth]: isPatternWrapperComponentFullWidth,
5752
5756
  }) },
5753
5757
  isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
5754
5758
  .filter((node) => node.data.slotId === slotId)