@contentful/experiences-core 1.36.0 → 1.37.0-dev-20250423T1119-4e477a9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fetchers/createExperience.d.ts +10 -3
- package/dist/fetchers/fetchAllEntities.d.ts +2 -2
- package/dist/fetchers/fetchById.d.ts +2 -1
- package/dist/fetchers/fetchBySlug.d.ts +2 -1
- package/dist/fetchers/fetchExperienceEntry.d.ts +30 -0
- package/dist/fetchers/fetchReferencedEntities.d.ts +28 -0
- package/dist/index.d.ts +11 -8
- package/dist/index.js +1434 -1299
- package/dist/index.js.map +1 -1
- package/dist/utils/localizeEntity.d.ts +24 -0
- package/dist/utils/styleUtils/ssrStyles.d.ts +5 -3
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import md5 from 'md5';
|
|
2
1
|
import { z, ZodIssueCode } from 'zod';
|
|
2
|
+
import { omit, isArray, uniqBy } from 'lodash-es';
|
|
3
|
+
import md5 from 'md5';
|
|
3
4
|
import { BLOCKS } from '@contentful/rich-text-types';
|
|
4
|
-
import { isArray, omit, uniqBy } from 'lodash-es';
|
|
5
5
|
|
|
6
6
|
const INCOMING_EVENTS = {
|
|
7
7
|
RequestEditorMode: 'requestEditorMode',
|
|
@@ -151,514 +151,173 @@ const isStructureWithRelativeHeight = (componentId, height) => {
|
|
|
151
151
|
return isContentfulStructureComponent(componentId) && !height?.toString().endsWith('px');
|
|
152
152
|
};
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
154
|
+
// These styles get added to every component, user custom or contentful provided
|
|
155
|
+
const builtInStyles = {
|
|
156
|
+
cfVerticalAlignment: {
|
|
157
|
+
validations: {
|
|
158
|
+
in: [
|
|
159
|
+
{
|
|
160
|
+
value: 'start',
|
|
161
|
+
displayName: 'Align left',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
value: 'center',
|
|
165
|
+
displayName: 'Align center',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
value: 'end',
|
|
169
|
+
displayName: 'Align right',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
type: 'Text',
|
|
174
|
+
group: 'style',
|
|
175
|
+
description: 'The vertical alignment of the section',
|
|
176
|
+
defaultValue: 'center',
|
|
177
|
+
displayName: 'Vertical alignment',
|
|
178
|
+
},
|
|
179
|
+
cfHorizontalAlignment: {
|
|
180
|
+
validations: {
|
|
181
|
+
in: [
|
|
182
|
+
{
|
|
183
|
+
value: 'start',
|
|
184
|
+
displayName: 'Align top',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
value: 'center',
|
|
188
|
+
displayName: 'Align center',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
value: 'end',
|
|
192
|
+
displayName: 'Align bottom',
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
type: 'Text',
|
|
197
|
+
group: 'style',
|
|
198
|
+
description: 'The horizontal alignment of the section',
|
|
199
|
+
defaultValue: 'center',
|
|
200
|
+
displayName: 'Horizontal alignment',
|
|
201
|
+
},
|
|
202
|
+
cfVisibility: {
|
|
203
|
+
displayName: 'Visibility toggle',
|
|
204
|
+
type: 'Boolean',
|
|
205
|
+
group: 'style',
|
|
206
|
+
defaultValue: true,
|
|
207
|
+
description: 'The visibility of the component',
|
|
208
|
+
},
|
|
209
|
+
cfMargin: {
|
|
210
|
+
displayName: 'Margin',
|
|
211
|
+
type: 'Text',
|
|
212
|
+
group: 'style',
|
|
213
|
+
description: 'The margin of the section',
|
|
214
|
+
defaultValue: '0 0 0 0',
|
|
215
|
+
},
|
|
216
|
+
cfPadding: {
|
|
217
|
+
displayName: 'Padding',
|
|
218
|
+
type: 'Text',
|
|
219
|
+
group: 'style',
|
|
220
|
+
description: 'The padding of the section',
|
|
221
|
+
defaultValue: '0 0 0 0',
|
|
222
|
+
},
|
|
223
|
+
cfBackgroundColor: {
|
|
224
|
+
displayName: 'Background color',
|
|
225
|
+
type: 'Text',
|
|
226
|
+
group: 'style',
|
|
227
|
+
description: 'The background color of the section',
|
|
228
|
+
defaultValue: 'rgba(0, 0, 0, 0)',
|
|
229
|
+
},
|
|
230
|
+
cfWidth: {
|
|
231
|
+
displayName: 'Width',
|
|
232
|
+
type: 'Text',
|
|
233
|
+
group: 'style',
|
|
234
|
+
description: 'The width of the section',
|
|
235
|
+
defaultValue: '100%',
|
|
236
|
+
},
|
|
237
|
+
cfHeight: {
|
|
238
|
+
displayName: 'Height',
|
|
239
|
+
type: 'Text',
|
|
240
|
+
group: 'style',
|
|
241
|
+
description: 'The height of the section',
|
|
242
|
+
defaultValue: 'fit-content',
|
|
243
|
+
},
|
|
244
|
+
cfMaxWidth: {
|
|
245
|
+
displayName: 'Max width',
|
|
246
|
+
type: 'Text',
|
|
247
|
+
group: 'style',
|
|
248
|
+
description: 'The max-width of the section',
|
|
249
|
+
defaultValue: 'none',
|
|
250
|
+
},
|
|
251
|
+
cfFlexDirection: {
|
|
252
|
+
displayName: 'Direction',
|
|
253
|
+
type: 'Text',
|
|
254
|
+
group: 'style',
|
|
255
|
+
description: 'The orientation of the section',
|
|
256
|
+
defaultValue: 'column',
|
|
257
|
+
},
|
|
258
|
+
cfFlexReverse: {
|
|
259
|
+
displayName: 'Reverse Direction',
|
|
260
|
+
type: 'Boolean',
|
|
261
|
+
group: 'style',
|
|
262
|
+
description: 'Toggle the flex direction to be reversed',
|
|
263
|
+
defaultValue: false,
|
|
264
|
+
},
|
|
265
|
+
cfFlexWrap: {
|
|
266
|
+
displayName: 'Wrap objects',
|
|
267
|
+
type: 'Text',
|
|
268
|
+
group: 'style',
|
|
269
|
+
description: 'Wrap objects',
|
|
270
|
+
defaultValue: 'nowrap',
|
|
271
|
+
},
|
|
272
|
+
cfBorder: {
|
|
273
|
+
displayName: 'Border',
|
|
274
|
+
type: 'Text',
|
|
275
|
+
group: 'style',
|
|
276
|
+
description: 'The border of the section',
|
|
277
|
+
defaultValue: '0px solid rgba(0, 0, 0, 0)',
|
|
278
|
+
},
|
|
279
|
+
cfGap: {
|
|
280
|
+
displayName: 'Gap',
|
|
281
|
+
type: 'Text',
|
|
282
|
+
group: 'style',
|
|
283
|
+
description: 'The spacing between the elements of the section',
|
|
284
|
+
defaultValue: '0px',
|
|
285
|
+
},
|
|
286
|
+
cfHyperlink: {
|
|
287
|
+
displayName: 'URL',
|
|
288
|
+
type: 'Hyperlink',
|
|
289
|
+
defaultValue: '',
|
|
290
|
+
validations: {
|
|
291
|
+
format: 'URL',
|
|
292
|
+
bindingSourceType: ['entry', 'experience', 'manual'],
|
|
293
|
+
},
|
|
294
|
+
description: 'hyperlink for section or container',
|
|
295
|
+
},
|
|
296
|
+
cfOpenInNewTab: {
|
|
297
|
+
displayName: 'URL behaviour',
|
|
298
|
+
type: 'Boolean',
|
|
299
|
+
defaultValue: false,
|
|
300
|
+
description: 'Open in new tab',
|
|
301
|
+
},
|
|
254
302
|
};
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const transformVisibility = (value) => {
|
|
265
|
-
if (value === false) {
|
|
266
|
-
return {
|
|
267
|
-
display: 'none !important',
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
// Don't explicitly set anything when visible to not overwrite values like `grid` or `flex`.
|
|
271
|
-
return {};
|
|
272
|
-
};
|
|
273
|
-
// Keep this for backwards compatibility - deleting this would be a breaking change
|
|
274
|
-
// because existing components on a users experience will have the width value as fill
|
|
275
|
-
// rather than 100%
|
|
276
|
-
const transformFill = (value) => (value === 'fill' ? '100%' : value);
|
|
277
|
-
const transformGridColumn = (span) => {
|
|
278
|
-
if (!span) {
|
|
279
|
-
return {};
|
|
280
|
-
}
|
|
281
|
-
return {
|
|
282
|
-
gridColumn: `span ${span}`,
|
|
283
|
-
};
|
|
284
|
-
};
|
|
285
|
-
const transformBorderStyle = (value) => {
|
|
286
|
-
if (!value)
|
|
287
|
-
return {};
|
|
288
|
-
const parts = value.split(' ');
|
|
289
|
-
// Just accept the passed value
|
|
290
|
-
if (parts.length < 3)
|
|
291
|
-
return { border: value };
|
|
292
|
-
const [borderSize, borderStyle, ...borderColorParts] = parts;
|
|
293
|
-
const borderColor = borderColorParts.join(' ');
|
|
294
|
-
return {
|
|
295
|
-
border: `${borderSize} ${borderStyle} ${borderColor}`,
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
|
-
const transformAlignment = (cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection = 'column') => cfFlexDirection === 'row'
|
|
299
|
-
? {
|
|
300
|
-
alignItems: cfHorizontalAlignment,
|
|
301
|
-
justifyContent: cfVerticalAlignment === 'center' ? `safe ${cfVerticalAlignment}` : cfVerticalAlignment,
|
|
302
|
-
}
|
|
303
|
-
: {
|
|
304
|
-
alignItems: cfVerticalAlignment,
|
|
305
|
-
justifyContent: cfHorizontalAlignment === 'center'
|
|
306
|
-
? `safe ${cfHorizontalAlignment}`
|
|
307
|
-
: cfHorizontalAlignment,
|
|
308
|
-
};
|
|
309
|
-
const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions) => {
|
|
310
|
-
const matchBackgroundSize = (scaling) => {
|
|
311
|
-
if ('fill' === scaling)
|
|
312
|
-
return 'cover';
|
|
313
|
-
if ('fit' === scaling)
|
|
314
|
-
return 'contain';
|
|
315
|
-
};
|
|
316
|
-
const matchBackgroundPosition = (alignment) => {
|
|
317
|
-
if (!alignment || 'string' !== typeof alignment) {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
|
|
321
|
-
// Special case for handling single values
|
|
322
|
-
// for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
|
|
323
|
-
if (horizontalAlignment && !verticalAlignment) {
|
|
324
|
-
const singleValue = horizontalAlignment;
|
|
325
|
-
switch (singleValue) {
|
|
326
|
-
case 'left':
|
|
327
|
-
horizontalAlignment = 'left';
|
|
328
|
-
verticalAlignment = 'center';
|
|
329
|
-
break;
|
|
330
|
-
case 'right':
|
|
331
|
-
horizontalAlignment = 'right';
|
|
332
|
-
verticalAlignment = 'center';
|
|
333
|
-
break;
|
|
334
|
-
case 'center':
|
|
335
|
-
horizontalAlignment = 'center';
|
|
336
|
-
verticalAlignment = 'center';
|
|
337
|
-
break;
|
|
338
|
-
case 'top':
|
|
339
|
-
horizontalAlignment = 'center';
|
|
340
|
-
verticalAlignment = 'top';
|
|
341
|
-
break;
|
|
342
|
-
case 'bottom':
|
|
343
|
-
horizontalAlignment = 'center';
|
|
344
|
-
verticalAlignment = 'bottom';
|
|
345
|
-
break;
|
|
346
|
-
// just fall down to the normal validation logic for horiz and vert
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
|
|
350
|
-
const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
|
|
351
|
-
horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
|
|
352
|
-
verticalAlignment = isVerticalValid ? verticalAlignment : 'top';
|
|
353
|
-
return `${horizontalAlignment} ${verticalAlignment}`;
|
|
354
|
-
};
|
|
355
|
-
if (!cfBackgroundImageUrl) {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
let backgroundImage;
|
|
359
|
-
let backgroundImageSet;
|
|
360
|
-
if (typeof cfBackgroundImageUrl === 'string') {
|
|
361
|
-
backgroundImage = `url(${cfBackgroundImageUrl})`;
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
const imgSet = cfBackgroundImageUrl.srcSet?.join(',');
|
|
365
|
-
backgroundImage = `url(${cfBackgroundImageUrl.url})`;
|
|
366
|
-
backgroundImageSet = `image-set(${imgSet})`;
|
|
367
|
-
}
|
|
368
|
-
return {
|
|
369
|
-
backgroundImage,
|
|
370
|
-
backgroundImage2: backgroundImageSet,
|
|
371
|
-
backgroundRepeat: cfBackgroundImageOptions?.scaling === 'tile' ? 'repeat' : 'no-repeat',
|
|
372
|
-
backgroundPosition: matchBackgroundPosition(cfBackgroundImageOptions?.alignment),
|
|
373
|
-
backgroundSize: matchBackgroundSize(cfBackgroundImageOptions?.scaling),
|
|
374
|
-
};
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
const toCSSAttribute = (key) => {
|
|
378
|
-
let val = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
|
379
|
-
// Remove the number from the end of the key to allow for overrides on style properties
|
|
380
|
-
val = val.replace(/\d+$/, '');
|
|
381
|
-
return val;
|
|
382
|
-
};
|
|
383
|
-
/**
|
|
384
|
-
* Turns a list of CSSProperties into a joined CSS string that can be
|
|
385
|
-
* used for <style> tags. Per default it creates a minimized version.
|
|
386
|
-
* For editor mode, use the `useWhitespaces` flag to create a more readable version.
|
|
387
|
-
*
|
|
388
|
-
* @param cssProperties list of CSS properties
|
|
389
|
-
* @param useWhitespaces adds whitespaces and newlines between each rule
|
|
390
|
-
* @returns a string of CSS rules
|
|
391
|
-
*/
|
|
392
|
-
const stringifyCssProperties = (cssProperties, useWhitespaces = false) => {
|
|
393
|
-
const rules = Object.entries(cssProperties)
|
|
394
|
-
.filter(([, value]) => value !== undefined)
|
|
395
|
-
.map(([key, value]) => useWhitespaces ? `${toCSSAttribute(key)}: ${value};` : `${toCSSAttribute(key)}:${value};`);
|
|
396
|
-
return rules.join(useWhitespaces ? '\n' : '');
|
|
397
|
-
};
|
|
398
|
-
const buildStyleTag = ({ styles, nodeId }) => {
|
|
399
|
-
const generatedStyles = stringifyCssProperties(styles, true);
|
|
400
|
-
const className = `cfstyles-${nodeId ? nodeId : md5(generatedStyles)}`;
|
|
401
|
-
const styleRule = `.${className}{ ${generatedStyles} }`;
|
|
402
|
-
return [className, styleRule];
|
|
403
|
-
};
|
|
404
|
-
/**
|
|
405
|
-
* Takes plain design values and transforms them into CSS properties. Undefined values will
|
|
406
|
-
* be filtered out.
|
|
407
|
-
*
|
|
408
|
-
* **Example Input**
|
|
409
|
-
* ```
|
|
410
|
-
* values = {
|
|
411
|
-
* cfVisibility: 'visible',
|
|
412
|
-
* cfMargin: '10px',
|
|
413
|
-
* cfFlexReverse: true,
|
|
414
|
-
* cfImageOptions: { objectFit: 'cover' },
|
|
415
|
-
* // ...
|
|
416
|
-
* }
|
|
417
|
-
* ```
|
|
418
|
-
* **Example Output**
|
|
419
|
-
* ```
|
|
420
|
-
* cssProperties = {
|
|
421
|
-
* margin: '10px',
|
|
422
|
-
* flexDirection: 'row-reverse',
|
|
423
|
-
* objectFit: 'cover',
|
|
424
|
-
* // ...
|
|
425
|
-
* }
|
|
426
|
-
* ```
|
|
427
|
-
*/
|
|
428
|
-
const buildCfStyles = (values) => {
|
|
429
|
-
const cssProperties = {
|
|
430
|
-
boxSizing: 'border-box',
|
|
431
|
-
...transformVisibility(values.cfVisibility),
|
|
432
|
-
margin: values.cfMargin,
|
|
433
|
-
padding: values.cfPadding,
|
|
434
|
-
backgroundColor: values.cfBackgroundColor,
|
|
435
|
-
width: transformFill(values.cfWidth || values.cfImageOptions?.width),
|
|
436
|
-
height: transformFill(values.cfHeight || values.cfImageOptions?.height),
|
|
437
|
-
maxWidth: values.cfMaxWidth,
|
|
438
|
-
...transformGridColumn(values.cfColumnSpan),
|
|
439
|
-
...transformBorderStyle(values.cfBorder),
|
|
440
|
-
borderRadius: values.cfBorderRadius,
|
|
441
|
-
gap: values.cfGap,
|
|
442
|
-
...transformAlignment(values.cfHorizontalAlignment, values.cfVerticalAlignment, values.cfFlexDirection),
|
|
443
|
-
flexDirection: values.cfFlexReverse && values.cfFlexDirection
|
|
444
|
-
? `${values.cfFlexDirection}-reverse`
|
|
445
|
-
: values.cfFlexDirection,
|
|
446
|
-
flexWrap: values.cfFlexWrap,
|
|
447
|
-
...transformBackgroundImage(values.cfBackgroundImageUrl, values.cfBackgroundImageOptions),
|
|
448
|
-
fontSize: values.cfFontSize,
|
|
449
|
-
fontWeight: values.cfTextBold ? 'bold' : values.cfFontWeight,
|
|
450
|
-
fontStyle: values.cfTextItalic ? 'italic' : undefined,
|
|
451
|
-
textDecoration: values.cfTextUnderline ? 'underline' : undefined,
|
|
452
|
-
lineHeight: values.cfLineHeight,
|
|
453
|
-
letterSpacing: values.cfLetterSpacing,
|
|
454
|
-
color: values.cfTextColor,
|
|
455
|
-
textAlign: values.cfTextAlign,
|
|
456
|
-
textTransform: values.cfTextTransform,
|
|
457
|
-
objectFit: values.cfImageOptions?.objectFit,
|
|
458
|
-
objectPosition: values.cfImageOptions?.objectPosition,
|
|
459
|
-
};
|
|
460
|
-
const cssPropertiesWithoutUndefined = Object.fromEntries(Object.entries(cssProperties).filter(([, value]) => value !== undefined));
|
|
461
|
-
return cssPropertiesWithoutUndefined;
|
|
462
|
-
};
|
|
463
|
-
/**
|
|
464
|
-
* **Only meant to be used in editor mode!**
|
|
465
|
-
*
|
|
466
|
-
* If the node is an empty structure component with a relative height (e.g. '100%'),
|
|
467
|
-
* it might render with a zero height. In this case, add a min height of 80px to ensure
|
|
468
|
-
* that child nodes can be added via drag & drop.
|
|
469
|
-
*/
|
|
470
|
-
const addMinHeightForEmptyStructures = (cssProperties, node) => {
|
|
471
|
-
if (!node.children.length &&
|
|
472
|
-
isStructureWithRelativeHeight(node.definitionId, cssProperties.height)) {
|
|
473
|
-
return {
|
|
474
|
-
...cssProperties,
|
|
475
|
-
minHeight: EMPTY_CONTAINER_HEIGHT,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
return cssProperties;
|
|
479
|
-
};
|
|
480
|
-
/**
|
|
481
|
-
* Container/section default behavior:
|
|
482
|
-
* Default height => height: EMPTY_CONTAINER_HEIGHT
|
|
483
|
-
* If a container component has children => height: 'fit-content'
|
|
484
|
-
*/
|
|
485
|
-
const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
|
|
486
|
-
if (!blockId || !isContentfulStructureComponent(blockId) || value !== 'auto') {
|
|
487
|
-
return value;
|
|
488
|
-
}
|
|
489
|
-
if (children.length) {
|
|
490
|
-
return '100%';
|
|
491
|
-
}
|
|
492
|
-
return EMPTY_CONTAINER_HEIGHT;
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
// These styles get added to every component, user custom or contentful provided
|
|
496
|
-
const builtInStyles = {
|
|
497
|
-
cfVerticalAlignment: {
|
|
303
|
+
const optionalBuiltInStyles = {
|
|
304
|
+
cfFontSize: {
|
|
305
|
+
displayName: 'Font Size',
|
|
306
|
+
type: 'Text',
|
|
307
|
+
group: 'style',
|
|
308
|
+
description: 'The font size of the element',
|
|
309
|
+
defaultValue: '16px',
|
|
310
|
+
},
|
|
311
|
+
cfFontWeight: {
|
|
498
312
|
validations: {
|
|
499
313
|
in: [
|
|
500
314
|
{
|
|
501
|
-
value: '
|
|
502
|
-
displayName: '
|
|
315
|
+
value: '400',
|
|
316
|
+
displayName: 'Normal',
|
|
503
317
|
},
|
|
504
318
|
{
|
|
505
|
-
value: '
|
|
506
|
-
displayName: '
|
|
507
|
-
},
|
|
508
|
-
{
|
|
509
|
-
value: 'end',
|
|
510
|
-
displayName: 'Align right',
|
|
511
|
-
},
|
|
512
|
-
],
|
|
513
|
-
},
|
|
514
|
-
type: 'Text',
|
|
515
|
-
group: 'style',
|
|
516
|
-
description: 'The vertical alignment of the section',
|
|
517
|
-
defaultValue: 'center',
|
|
518
|
-
displayName: 'Vertical alignment',
|
|
519
|
-
},
|
|
520
|
-
cfHorizontalAlignment: {
|
|
521
|
-
validations: {
|
|
522
|
-
in: [
|
|
523
|
-
{
|
|
524
|
-
value: 'start',
|
|
525
|
-
displayName: 'Align top',
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
value: 'center',
|
|
529
|
-
displayName: 'Align center',
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
value: 'end',
|
|
533
|
-
displayName: 'Align bottom',
|
|
534
|
-
},
|
|
535
|
-
],
|
|
536
|
-
},
|
|
537
|
-
type: 'Text',
|
|
538
|
-
group: 'style',
|
|
539
|
-
description: 'The horizontal alignment of the section',
|
|
540
|
-
defaultValue: 'center',
|
|
541
|
-
displayName: 'Horizontal alignment',
|
|
542
|
-
},
|
|
543
|
-
cfVisibility: {
|
|
544
|
-
displayName: 'Visibility toggle',
|
|
545
|
-
type: 'Boolean',
|
|
546
|
-
group: 'style',
|
|
547
|
-
defaultValue: true,
|
|
548
|
-
description: 'The visibility of the component',
|
|
549
|
-
},
|
|
550
|
-
cfMargin: {
|
|
551
|
-
displayName: 'Margin',
|
|
552
|
-
type: 'Text',
|
|
553
|
-
group: 'style',
|
|
554
|
-
description: 'The margin of the section',
|
|
555
|
-
defaultValue: '0 0 0 0',
|
|
556
|
-
},
|
|
557
|
-
cfPadding: {
|
|
558
|
-
displayName: 'Padding',
|
|
559
|
-
type: 'Text',
|
|
560
|
-
group: 'style',
|
|
561
|
-
description: 'The padding of the section',
|
|
562
|
-
defaultValue: '0 0 0 0',
|
|
563
|
-
},
|
|
564
|
-
cfBackgroundColor: {
|
|
565
|
-
displayName: 'Background color',
|
|
566
|
-
type: 'Text',
|
|
567
|
-
group: 'style',
|
|
568
|
-
description: 'The background color of the section',
|
|
569
|
-
defaultValue: 'rgba(0, 0, 0, 0)',
|
|
570
|
-
},
|
|
571
|
-
cfWidth: {
|
|
572
|
-
displayName: 'Width',
|
|
573
|
-
type: 'Text',
|
|
574
|
-
group: 'style',
|
|
575
|
-
description: 'The width of the section',
|
|
576
|
-
defaultValue: '100%',
|
|
577
|
-
},
|
|
578
|
-
cfHeight: {
|
|
579
|
-
displayName: 'Height',
|
|
580
|
-
type: 'Text',
|
|
581
|
-
group: 'style',
|
|
582
|
-
description: 'The height of the section',
|
|
583
|
-
defaultValue: 'fit-content',
|
|
584
|
-
},
|
|
585
|
-
cfMaxWidth: {
|
|
586
|
-
displayName: 'Max width',
|
|
587
|
-
type: 'Text',
|
|
588
|
-
group: 'style',
|
|
589
|
-
description: 'The max-width of the section',
|
|
590
|
-
defaultValue: 'none',
|
|
591
|
-
},
|
|
592
|
-
cfFlexDirection: {
|
|
593
|
-
displayName: 'Direction',
|
|
594
|
-
type: 'Text',
|
|
595
|
-
group: 'style',
|
|
596
|
-
description: 'The orientation of the section',
|
|
597
|
-
defaultValue: 'column',
|
|
598
|
-
},
|
|
599
|
-
cfFlexReverse: {
|
|
600
|
-
displayName: 'Reverse Direction',
|
|
601
|
-
type: 'Boolean',
|
|
602
|
-
group: 'style',
|
|
603
|
-
description: 'Toggle the flex direction to be reversed',
|
|
604
|
-
defaultValue: false,
|
|
605
|
-
},
|
|
606
|
-
cfFlexWrap: {
|
|
607
|
-
displayName: 'Wrap objects',
|
|
608
|
-
type: 'Text',
|
|
609
|
-
group: 'style',
|
|
610
|
-
description: 'Wrap objects',
|
|
611
|
-
defaultValue: 'nowrap',
|
|
612
|
-
},
|
|
613
|
-
cfBorder: {
|
|
614
|
-
displayName: 'Border',
|
|
615
|
-
type: 'Text',
|
|
616
|
-
group: 'style',
|
|
617
|
-
description: 'The border of the section',
|
|
618
|
-
defaultValue: '0px solid rgba(0, 0, 0, 0)',
|
|
619
|
-
},
|
|
620
|
-
cfGap: {
|
|
621
|
-
displayName: 'Gap',
|
|
622
|
-
type: 'Text',
|
|
623
|
-
group: 'style',
|
|
624
|
-
description: 'The spacing between the elements of the section',
|
|
625
|
-
defaultValue: '0px',
|
|
626
|
-
},
|
|
627
|
-
cfHyperlink: {
|
|
628
|
-
displayName: 'URL',
|
|
629
|
-
type: 'Hyperlink',
|
|
630
|
-
defaultValue: '',
|
|
631
|
-
validations: {
|
|
632
|
-
format: 'URL',
|
|
633
|
-
bindingSourceType: ['entry', 'experience', 'manual'],
|
|
634
|
-
},
|
|
635
|
-
description: 'hyperlink for section or container',
|
|
636
|
-
},
|
|
637
|
-
cfOpenInNewTab: {
|
|
638
|
-
displayName: 'URL behaviour',
|
|
639
|
-
type: 'Boolean',
|
|
640
|
-
defaultValue: false,
|
|
641
|
-
description: 'Open in new tab',
|
|
642
|
-
},
|
|
643
|
-
};
|
|
644
|
-
const optionalBuiltInStyles = {
|
|
645
|
-
cfFontSize: {
|
|
646
|
-
displayName: 'Font Size',
|
|
647
|
-
type: 'Text',
|
|
648
|
-
group: 'style',
|
|
649
|
-
description: 'The font size of the element',
|
|
650
|
-
defaultValue: '16px',
|
|
651
|
-
},
|
|
652
|
-
cfFontWeight: {
|
|
653
|
-
validations: {
|
|
654
|
-
in: [
|
|
655
|
-
{
|
|
656
|
-
value: '400',
|
|
657
|
-
displayName: 'Normal',
|
|
658
|
-
},
|
|
659
|
-
{
|
|
660
|
-
value: '500',
|
|
661
|
-
displayName: 'Medium',
|
|
319
|
+
value: '500',
|
|
320
|
+
displayName: 'Medium',
|
|
662
321
|
},
|
|
663
322
|
{
|
|
664
323
|
value: '600',
|
|
@@ -1372,278 +1031,1017 @@ const breakpointsRefinement = (value, ctx) => {
|
|
|
1372
1031
|
if (q2 === '*') {
|
|
1373
1032
|
return 1;
|
|
1374
1033
|
}
|
|
1375
|
-
return q1 > q2 ? -1 : 1;
|
|
1376
|
-
});
|
|
1377
|
-
if (originalQueries.join('') !== queries.join('')) {
|
|
1378
|
-
ctx.addIssue({
|
|
1379
|
-
code: z.ZodIssueCode.custom,
|
|
1380
|
-
message: `Breakpoints should be ordered from largest to smallest pixel value`,
|
|
1381
|
-
});
|
|
1034
|
+
return q1 > q2 ? -1 : 1;
|
|
1035
|
+
});
|
|
1036
|
+
if (originalQueries.join('') !== queries.join('')) {
|
|
1037
|
+
ctx.addIssue({
|
|
1038
|
+
code: z.ZodIssueCode.custom,
|
|
1039
|
+
message: `Breakpoints should be ordered from largest to smallest pixel value`,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
const ComponentTreeSchema = z
|
|
1044
|
+
.object({
|
|
1045
|
+
breakpoints: z.array(BreakpointSchema).superRefine(breakpointsRefinement),
|
|
1046
|
+
children: z.array(ComponentTreeNodeSchema),
|
|
1047
|
+
schemaVersion: SchemaVersions,
|
|
1048
|
+
})
|
|
1049
|
+
.strict();
|
|
1050
|
+
const localeWrapper = (fieldSchema) => z.record(z.string(), fieldSchema);
|
|
1051
|
+
z.object({
|
|
1052
|
+
componentTree: localeWrapper(ComponentTreeSchema),
|
|
1053
|
+
dataSource: localeWrapper(DataSourceSchema),
|
|
1054
|
+
unboundValues: localeWrapper(UnboundValuesSchema),
|
|
1055
|
+
usedComponents: localeWrapper(UsedComponentsSchema).optional(),
|
|
1056
|
+
componentSettings: localeWrapper(ComponentSettingsSchema).optional(),
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
z.object({
|
|
1060
|
+
id: DefinitionPropertyKeySchema,
|
|
1061
|
+
variables: z.record(DefinitionPropertyKeySchema, ComponentVariableSchema.extend({
|
|
1062
|
+
defaultValue: PrimitiveValueSchema.optional(),
|
|
1063
|
+
}).superRefine((val, ctx) => {
|
|
1064
|
+
switch (val.type) {
|
|
1065
|
+
case 'Array':
|
|
1066
|
+
if (typeof val.defaultValue !== 'undefined') {
|
|
1067
|
+
ctx.addIssue({
|
|
1068
|
+
code: z.ZodIssueCode.custom,
|
|
1069
|
+
message: `defaultValue is not supported for "Array" type for ${ctx.path.join('.')}`,
|
|
1070
|
+
fatal: false,
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
break;
|
|
1074
|
+
case 'Boolean':
|
|
1075
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'boolean') {
|
|
1076
|
+
ctx.addIssue({
|
|
1077
|
+
code: z.ZodIssueCode.custom,
|
|
1078
|
+
message: `defaultValue must be a boolean when type is "Boolean" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1079
|
+
fatal: false,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
break;
|
|
1083
|
+
case 'Date':
|
|
1084
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1085
|
+
ctx.addIssue({
|
|
1086
|
+
code: z.ZodIssueCode.custom,
|
|
1087
|
+
message: `defaultValue must be a string when type is "Date" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1088
|
+
fatal: false,
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
break;
|
|
1092
|
+
case 'Hyperlink':
|
|
1093
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1094
|
+
ctx.addIssue({
|
|
1095
|
+
code: z.ZodIssueCode.custom,
|
|
1096
|
+
message: `defaultValue must be a string when type is "Hyperlink" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1097
|
+
fatal: false,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
break;
|
|
1101
|
+
case 'Link':
|
|
1102
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1103
|
+
ctx.addIssue({
|
|
1104
|
+
code: z.ZodIssueCode.custom,
|
|
1105
|
+
message: `defaultValue is not supported for "Link" type for ${ctx.path.join('.')}`,
|
|
1106
|
+
fatal: false,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
break;
|
|
1110
|
+
case 'Location':
|
|
1111
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1112
|
+
ctx.addIssue({
|
|
1113
|
+
code: z.ZodIssueCode.custom,
|
|
1114
|
+
message: `defaultValue must be an object when type is "Location" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1115
|
+
fatal: false,
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
break;
|
|
1119
|
+
case 'Media':
|
|
1120
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1121
|
+
ctx.addIssue({
|
|
1122
|
+
code: z.ZodIssueCode.custom,
|
|
1123
|
+
message: `defaultValue must be a string when type is "Media" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1124
|
+
fatal: false,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
break;
|
|
1128
|
+
case 'Number':
|
|
1129
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'number') {
|
|
1130
|
+
ctx.addIssue({
|
|
1131
|
+
code: z.ZodIssueCode.custom,
|
|
1132
|
+
message: `defaultValue must be a number when type is "Number" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1133
|
+
fatal: false,
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
break;
|
|
1137
|
+
case 'Object':
|
|
1138
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1139
|
+
ctx.addIssue({
|
|
1140
|
+
code: z.ZodIssueCode.custom,
|
|
1141
|
+
message: `defaultValue must be an object when type is "Object" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1142
|
+
fatal: false,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
break;
|
|
1146
|
+
case 'RichText':
|
|
1147
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1148
|
+
ctx.addIssue({
|
|
1149
|
+
code: z.ZodIssueCode.custom,
|
|
1150
|
+
message: `defaultValue must be an object when type is "RichText" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1151
|
+
fatal: false,
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
break;
|
|
1155
|
+
case 'Text':
|
|
1156
|
+
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1157
|
+
ctx.addIssue({
|
|
1158
|
+
code: z.ZodIssueCode.custom,
|
|
1159
|
+
message: `defaultValue must be a string when type is "Text" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1160
|
+
fatal: false,
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
})),
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
var CodeNames;
|
|
1169
|
+
(function (CodeNames) {
|
|
1170
|
+
CodeNames["Type"] = "type";
|
|
1171
|
+
CodeNames["Required"] = "required";
|
|
1172
|
+
CodeNames["Unexpected"] = "unexpected";
|
|
1173
|
+
CodeNames["Regex"] = "regex";
|
|
1174
|
+
CodeNames["In"] = "in";
|
|
1175
|
+
CodeNames["Size"] = "size";
|
|
1176
|
+
CodeNames["Custom"] = "custom";
|
|
1177
|
+
})(CodeNames || (CodeNames = {}));
|
|
1178
|
+
const convertInvalidType = (issue) => {
|
|
1179
|
+
const name = issue.received === 'undefined' ? CodeNames.Required : CodeNames.Type;
|
|
1180
|
+
const details = issue.received === 'undefined'
|
|
1181
|
+
? `The property "${issue.path.slice(-1)}" is required here`
|
|
1182
|
+
: `The type of "${issue.path.slice(-1)}" is incorrect, expected type: ${issue.expected}`;
|
|
1183
|
+
return {
|
|
1184
|
+
details: details,
|
|
1185
|
+
name: name,
|
|
1186
|
+
path: issue.path,
|
|
1187
|
+
value: issue.received.toString(),
|
|
1188
|
+
};
|
|
1189
|
+
};
|
|
1190
|
+
const convertUnrecognizedKeys = (issue) => {
|
|
1191
|
+
const missingProperties = issue.keys.map((k) => `"${k}"`).join(', ');
|
|
1192
|
+
return {
|
|
1193
|
+
details: issue.keys.length > 1
|
|
1194
|
+
? `The properties ${missingProperties} are not expected`
|
|
1195
|
+
: `The property ${missingProperties} is not expected`,
|
|
1196
|
+
name: CodeNames.Unexpected,
|
|
1197
|
+
path: issue.path,
|
|
1198
|
+
};
|
|
1199
|
+
};
|
|
1200
|
+
const convertInvalidString = (issue) => {
|
|
1201
|
+
return {
|
|
1202
|
+
details: issue.message || 'Invalid string',
|
|
1203
|
+
name: issue.validation === 'regex' ? CodeNames.Regex : CodeNames.Unexpected,
|
|
1204
|
+
path: issue.path,
|
|
1205
|
+
};
|
|
1206
|
+
};
|
|
1207
|
+
const convertInvalidEnumValue = (issue) => {
|
|
1208
|
+
return {
|
|
1209
|
+
details: issue.message || 'Value must be one of expected values',
|
|
1210
|
+
name: CodeNames.In,
|
|
1211
|
+
path: issue.path,
|
|
1212
|
+
value: issue.received.toString(),
|
|
1213
|
+
expected: issue.options,
|
|
1214
|
+
};
|
|
1215
|
+
};
|
|
1216
|
+
const convertInvalidLiteral = (issue) => {
|
|
1217
|
+
return {
|
|
1218
|
+
details: issue.message || 'Value must be one of expected values',
|
|
1219
|
+
name: CodeNames.In,
|
|
1220
|
+
path: issue.path,
|
|
1221
|
+
value: issue.received,
|
|
1222
|
+
expected: [issue.expected],
|
|
1223
|
+
};
|
|
1224
|
+
};
|
|
1225
|
+
const convertTooBig = (issue) => {
|
|
1226
|
+
return {
|
|
1227
|
+
details: issue.message || `Size should be at most ${issue.maximum}`,
|
|
1228
|
+
name: CodeNames.Size,
|
|
1229
|
+
path: issue.path,
|
|
1230
|
+
max: issue.maximum,
|
|
1231
|
+
};
|
|
1232
|
+
};
|
|
1233
|
+
const convertTooSmall = (issue) => {
|
|
1234
|
+
return {
|
|
1235
|
+
details: issue.message || `Size should be at least ${issue.minimum}`,
|
|
1236
|
+
name: CodeNames.Size,
|
|
1237
|
+
path: issue.path,
|
|
1238
|
+
min: issue.minimum,
|
|
1239
|
+
};
|
|
1240
|
+
};
|
|
1241
|
+
const defaultConversion = (issue) => {
|
|
1242
|
+
return {
|
|
1243
|
+
details: issue.message || 'An unexpected error occurred',
|
|
1244
|
+
name: CodeNames.Custom,
|
|
1245
|
+
path: issue.path.map(String),
|
|
1246
|
+
};
|
|
1247
|
+
};
|
|
1248
|
+
const zodToContentfulError = (issue) => {
|
|
1249
|
+
switch (issue.code) {
|
|
1250
|
+
case ZodIssueCode.invalid_type:
|
|
1251
|
+
return convertInvalidType(issue);
|
|
1252
|
+
case ZodIssueCode.unrecognized_keys:
|
|
1253
|
+
return convertUnrecognizedKeys(issue);
|
|
1254
|
+
case ZodIssueCode.invalid_enum_value:
|
|
1255
|
+
return convertInvalidEnumValue(issue);
|
|
1256
|
+
case ZodIssueCode.invalid_string:
|
|
1257
|
+
return convertInvalidString(issue);
|
|
1258
|
+
case ZodIssueCode.too_small:
|
|
1259
|
+
return convertTooSmall(issue);
|
|
1260
|
+
case ZodIssueCode.too_big:
|
|
1261
|
+
return convertTooBig(issue);
|
|
1262
|
+
case ZodIssueCode.invalid_literal:
|
|
1263
|
+
return convertInvalidLiteral(issue);
|
|
1264
|
+
default:
|
|
1265
|
+
return defaultConversion(issue);
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const validateBreakpointsDefinition = (breakpoints) => {
|
|
1270
|
+
const result = z
|
|
1271
|
+
.array(BreakpointSchema)
|
|
1272
|
+
.superRefine(breakpointsRefinement)
|
|
1273
|
+
.safeParse(breakpoints);
|
|
1274
|
+
if (!result.success) {
|
|
1275
|
+
return {
|
|
1276
|
+
success: false,
|
|
1277
|
+
errors: result.error.issues.map(zodToContentfulError),
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
return { success: true };
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
let breakpointsRegistry = [];
|
|
1284
|
+
/**
|
|
1285
|
+
* Register custom breakpoints
|
|
1286
|
+
* @param breakpoints - [{[key:string]: string}]
|
|
1287
|
+
* @returns void
|
|
1288
|
+
*/
|
|
1289
|
+
const defineBreakpoints = (breakpoints) => {
|
|
1290
|
+
Object.assign(breakpointsRegistry, breakpoints);
|
|
1291
|
+
};
|
|
1292
|
+
const runBreakpointsValidation = () => {
|
|
1293
|
+
if (!breakpointsRegistry.length)
|
|
1294
|
+
return;
|
|
1295
|
+
const validation = validateBreakpointsDefinition(breakpointsRegistry);
|
|
1296
|
+
if (!validation.success) {
|
|
1297
|
+
throw new Error(`Invalid breakpoints definition. Failed with errors: \n${JSON.stringify(validation.errors, null, 2)}`);
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
// Used in the tests to get a breakpoint registration
|
|
1301
|
+
const getBreakpointRegistration = (id) => breakpointsRegistry.find((breakpoint) => breakpoint.id === id);
|
|
1302
|
+
// Used in the tests to reset the registry
|
|
1303
|
+
const resetBreakpointsRegistry = () => {
|
|
1304
|
+
breakpointsRegistry = [];
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
const MEDIA_QUERY_REGEXP = /(<|>)(\d{1,})(px|cm|mm|in|pt|pc)$/;
|
|
1308
|
+
const toCSSMediaQuery = ({ query }) => {
|
|
1309
|
+
if (query === '*')
|
|
1310
|
+
return undefined;
|
|
1311
|
+
const match = query.match(MEDIA_QUERY_REGEXP);
|
|
1312
|
+
if (!match)
|
|
1313
|
+
return undefined;
|
|
1314
|
+
const [, operator, value, unit] = match;
|
|
1315
|
+
if (operator === '<') {
|
|
1316
|
+
const maxScreenWidth = Number(value) - 1;
|
|
1317
|
+
return `(max-width: ${maxScreenWidth}${unit})`;
|
|
1318
|
+
}
|
|
1319
|
+
else if (operator === '>') {
|
|
1320
|
+
const minScreenWidth = Number(value) + 1;
|
|
1321
|
+
return `(min-width: ${minScreenWidth}${unit})`;
|
|
1322
|
+
}
|
|
1323
|
+
return undefined;
|
|
1324
|
+
};
|
|
1325
|
+
// Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
|
|
1326
|
+
const findLast = (array, predicate) => {
|
|
1327
|
+
return array.reverse().find(predicate);
|
|
1328
|
+
};
|
|
1329
|
+
// Initialise media query matchers. This won't include the always matching fallback breakpoint.
|
|
1330
|
+
const mediaQueryMatcher = (breakpoints) => {
|
|
1331
|
+
const mediaQueryMatches = {};
|
|
1332
|
+
const mediaQueryMatchers = breakpoints
|
|
1333
|
+
.map((breakpoint) => {
|
|
1334
|
+
const cssMediaQuery = toCSSMediaQuery(breakpoint);
|
|
1335
|
+
if (!cssMediaQuery)
|
|
1336
|
+
return undefined;
|
|
1337
|
+
if (typeof window === 'undefined')
|
|
1338
|
+
return undefined;
|
|
1339
|
+
const mediaQueryMatcher = window.matchMedia(cssMediaQuery);
|
|
1340
|
+
mediaQueryMatches[breakpoint.id] = mediaQueryMatcher.matches;
|
|
1341
|
+
return { id: breakpoint.id, signal: mediaQueryMatcher };
|
|
1342
|
+
})
|
|
1343
|
+
.filter((matcher) => !!matcher);
|
|
1344
|
+
return [mediaQueryMatchers, mediaQueryMatches];
|
|
1345
|
+
};
|
|
1346
|
+
const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
|
|
1347
|
+
// The breakpoints are ordered (desktop-first: descending by screen width)
|
|
1348
|
+
const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
|
|
1349
|
+
id,
|
|
1350
|
+
index,
|
|
1351
|
+
// The fallback breakpoint with wildcard query will always match
|
|
1352
|
+
isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
|
|
1353
|
+
}));
|
|
1354
|
+
// Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
|
|
1355
|
+
const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
|
|
1356
|
+
return mostSpecificIndex ?? fallbackBreakpointIndex;
|
|
1357
|
+
};
|
|
1358
|
+
const getFallbackBreakpointIndex = (breakpoints) => {
|
|
1359
|
+
// We assume that there will be a single breakpoint which uses the wildcard query.
|
|
1360
|
+
// If there is none, we just take the first one in the list.
|
|
1361
|
+
return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
|
|
1362
|
+
};
|
|
1363
|
+
const builtInStylesWithDesignTokens = [
|
|
1364
|
+
'cfMargin',
|
|
1365
|
+
'cfPadding',
|
|
1366
|
+
'cfGap',
|
|
1367
|
+
'cfWidth',
|
|
1368
|
+
'cfHeight',
|
|
1369
|
+
'cfBackgroundColor',
|
|
1370
|
+
'cfBorder',
|
|
1371
|
+
'cfBorderRadius',
|
|
1372
|
+
'cfFontSize',
|
|
1373
|
+
'cfLineHeight',
|
|
1374
|
+
'cfLetterSpacing',
|
|
1375
|
+
'cfTextColor',
|
|
1376
|
+
'cfMaxWidth',
|
|
1377
|
+
];
|
|
1378
|
+
const isValidBreakpointValue = (value) => {
|
|
1379
|
+
return value !== undefined && value !== null && value !== '';
|
|
1380
|
+
};
|
|
1381
|
+
const getValueForBreakpoint = (valuesByBreakpoint, breakpoints, activeBreakpointIndex, fallbackBreakpointIndex, variableName, resolveDesignTokens = true) => {
|
|
1382
|
+
const eventuallyResolveDesignTokens = (value) => {
|
|
1383
|
+
// For some built-in design properties, we support design tokens
|
|
1384
|
+
if (builtInStylesWithDesignTokens.includes(variableName)) {
|
|
1385
|
+
return getDesignTokenRegistration(value, variableName);
|
|
1386
|
+
}
|
|
1387
|
+
// For all other properties, we just return the breakpoint-specific value
|
|
1388
|
+
return value;
|
|
1389
|
+
};
|
|
1390
|
+
if (valuesByBreakpoint instanceof Object) {
|
|
1391
|
+
// Assume that the values are sorted by media query to apply the cascading CSS logic
|
|
1392
|
+
for (let index = activeBreakpointIndex; index >= 0; index--) {
|
|
1393
|
+
const breakpointId = breakpoints[index]?.id;
|
|
1394
|
+
if (isValidBreakpointValue(valuesByBreakpoint[breakpointId])) {
|
|
1395
|
+
// If the value is defined, we use it and stop the breakpoints cascade
|
|
1396
|
+
if (resolveDesignTokens) {
|
|
1397
|
+
return eventuallyResolveDesignTokens(valuesByBreakpoint[breakpointId]);
|
|
1398
|
+
}
|
|
1399
|
+
return valuesByBreakpoint[breakpointId];
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
const fallbackBreakpointId = breakpoints[fallbackBreakpointIndex]?.id;
|
|
1403
|
+
if (isValidBreakpointValue(valuesByBreakpoint[fallbackBreakpointId])) {
|
|
1404
|
+
if (resolveDesignTokens) {
|
|
1405
|
+
return eventuallyResolveDesignTokens(valuesByBreakpoint[fallbackBreakpointId]);
|
|
1406
|
+
}
|
|
1407
|
+
return valuesByBreakpoint[fallbackBreakpointId];
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
// Old design properties did not support breakpoints, keep for backward compatibility
|
|
1412
|
+
return valuesByBreakpoint;
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
const CF_DEBUG_KEY = 'cf_debug';
|
|
1417
|
+
class DebugLogger {
|
|
1418
|
+
constructor() {
|
|
1419
|
+
// Public methods for logging
|
|
1420
|
+
this.error = this.logger('error');
|
|
1421
|
+
this.warn = this.logger('warn');
|
|
1422
|
+
this.log = this.logger('log');
|
|
1423
|
+
this.debug = this.logger('debug');
|
|
1424
|
+
if (typeof localStorage === 'undefined') {
|
|
1425
|
+
this.enabled = false;
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
// Default to checking localStorage for the debug mode on initialization if in browser
|
|
1429
|
+
this.enabled = localStorage.getItem(CF_DEBUG_KEY) === 'true';
|
|
1430
|
+
}
|
|
1431
|
+
static getInstance() {
|
|
1432
|
+
if (this.instance === null) {
|
|
1433
|
+
this.instance = new DebugLogger();
|
|
1434
|
+
}
|
|
1435
|
+
return this.instance;
|
|
1436
|
+
}
|
|
1437
|
+
getEnabled() {
|
|
1438
|
+
return this.enabled;
|
|
1439
|
+
}
|
|
1440
|
+
setEnabled(enabled) {
|
|
1441
|
+
this.enabled = enabled;
|
|
1442
|
+
if (typeof localStorage === 'undefined') {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
if (enabled) {
|
|
1446
|
+
localStorage.setItem(CF_DEBUG_KEY, 'true');
|
|
1447
|
+
}
|
|
1448
|
+
else {
|
|
1449
|
+
localStorage.removeItem(CF_DEBUG_KEY);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
// Log method for different levels (error, warn, log)
|
|
1453
|
+
logger(level) {
|
|
1454
|
+
return (...args) => {
|
|
1455
|
+
if (this.enabled) {
|
|
1456
|
+
console[level]('[cf-experiences-sdk]', ...args);
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
DebugLogger.instance = null;
|
|
1462
|
+
const debug = DebugLogger.getInstance();
|
|
1463
|
+
const enableDebug = () => {
|
|
1464
|
+
debug.setEnabled(true);
|
|
1465
|
+
console.log('Debug mode enabled');
|
|
1466
|
+
};
|
|
1467
|
+
const disableDebug = () => {
|
|
1468
|
+
debug.setEnabled(false);
|
|
1469
|
+
console.log('Debug mode disabled');
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
const findOutermostCoordinates = (first, second) => {
|
|
1473
|
+
return {
|
|
1474
|
+
top: Math.min(first.top, second.top),
|
|
1475
|
+
right: Math.max(first.right, second.right),
|
|
1476
|
+
bottom: Math.max(first.bottom, second.bottom),
|
|
1477
|
+
left: Math.min(first.left, second.left),
|
|
1478
|
+
};
|
|
1479
|
+
};
|
|
1480
|
+
const getElementCoordinates = (element) => {
|
|
1481
|
+
const rect = element.getBoundingClientRect();
|
|
1482
|
+
/**
|
|
1483
|
+
* If element does not have children, or element has it's own width or height,
|
|
1484
|
+
* return the element's coordinates.
|
|
1485
|
+
*/
|
|
1486
|
+
if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
|
|
1487
|
+
return rect;
|
|
1488
|
+
}
|
|
1489
|
+
const rects = [];
|
|
1490
|
+
/**
|
|
1491
|
+
* If element has children, or element does not have it's own width and height,
|
|
1492
|
+
* we find the cordinates of the children, and assume the outermost coordinates of the children
|
|
1493
|
+
* as the coordinate of the element.
|
|
1494
|
+
*
|
|
1495
|
+
* E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
|
|
1496
|
+
* The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
|
|
1497
|
+
*/
|
|
1498
|
+
for (const child of element.children) {
|
|
1499
|
+
const childRect = getElementCoordinates(child);
|
|
1500
|
+
if (childRect.width !== 0 || childRect.height !== 0) {
|
|
1501
|
+
const { top, right, bottom, left } = childRect;
|
|
1502
|
+
rects.push({ top, right, bottom, left });
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
if (rects.length === 0) {
|
|
1506
|
+
return rect;
|
|
1507
|
+
}
|
|
1508
|
+
const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
|
|
1509
|
+
return DOMRect.fromRect({
|
|
1510
|
+
x: left,
|
|
1511
|
+
y: top,
|
|
1512
|
+
height: bottom - top,
|
|
1513
|
+
width: right - left,
|
|
1514
|
+
});
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1518
|
+
const isLinkToAsset = (variable) => {
|
|
1519
|
+
if (!variable)
|
|
1520
|
+
return false;
|
|
1521
|
+
if (typeof variable !== 'object')
|
|
1522
|
+
return false;
|
|
1523
|
+
return (variable.sys?.linkType === 'Asset' &&
|
|
1524
|
+
typeof variable.sys?.id === 'string' &&
|
|
1525
|
+
!!variable.sys?.id &&
|
|
1526
|
+
variable.sys?.type === 'Link');
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
const isLink = (maybeLink) => {
|
|
1530
|
+
if (maybeLink === null)
|
|
1531
|
+
return false;
|
|
1532
|
+
if (typeof maybeLink !== 'object')
|
|
1533
|
+
return false;
|
|
1534
|
+
const link = maybeLink;
|
|
1535
|
+
return Boolean(link.sys?.id) && link.sys?.type === 'Link';
|
|
1536
|
+
};
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* Localizes the provided entry or asset to match the regular format of CDA/CPA entities.
|
|
1540
|
+
* Note that this function does not apply a fallback to the default locale nor does it check
|
|
1541
|
+
* the content type for the localization setting of each field.
|
|
1542
|
+
* It will simply resolve each field to the requested locale. As using single and multiple
|
|
1543
|
+
* reference fields is still considered an experimental feature, this function does not handle
|
|
1544
|
+
* recursive localization of deeper referenced entities.
|
|
1545
|
+
*
|
|
1546
|
+
* If the entity is already localized, it will return the entity as is.
|
|
1547
|
+
*
|
|
1548
|
+
* Note that localization is later on determined by the existence of the `sys.locale` property (matching the API shape).
|
|
1549
|
+
*
|
|
1550
|
+
* @example
|
|
1551
|
+
* ```
|
|
1552
|
+
* const multiLocaleEntry = { fields: { title: { 'en-US': 'Hello' } } };
|
|
1553
|
+
* const localizedEntry = localizeEntity(multiLocaleEntry, 'en-US');
|
|
1554
|
+
* console.log(localizedEntry.fields.title); // 'Hello'
|
|
1555
|
+
* ```
|
|
1556
|
+
*/
|
|
1557
|
+
function localizeEntity(entity, locale) {
|
|
1558
|
+
if (!entity || !entity.fields) {
|
|
1559
|
+
throw new Error('Invalid entity provided');
|
|
1560
|
+
}
|
|
1561
|
+
if (entity.sys.locale) {
|
|
1562
|
+
return entity;
|
|
1563
|
+
}
|
|
1564
|
+
const cloned = structuredClone(entity);
|
|
1565
|
+
// Set the requested locale as entry locale to follow the API shape:
|
|
1566
|
+
// https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/common-resource-attributes
|
|
1567
|
+
cloned.sys.locale = locale;
|
|
1568
|
+
for (const key in cloned.fields) {
|
|
1569
|
+
cloned.fields[key] = cloned.fields[key][locale];
|
|
1570
|
+
}
|
|
1571
|
+
return cloned;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* This module encapsulates format of the path to a deep reference.
|
|
1576
|
+
*/
|
|
1577
|
+
const parseDataSourcePathIntoFieldset = (path) => {
|
|
1578
|
+
const parsedPath = parseDeepPath(path);
|
|
1579
|
+
if (null === parsedPath) {
|
|
1580
|
+
throw new Error(`Cannot parse path '${path}' as deep path`);
|
|
1581
|
+
}
|
|
1582
|
+
return parsedPath.fields.map((field) => [null, field, '~locale']);
|
|
1583
|
+
};
|
|
1584
|
+
/**
|
|
1585
|
+
* Parse path into components, supports L1 references (one reference follow) atm.
|
|
1586
|
+
* @param path from data source. eg. `/uuid123/fields/image/~locale/fields/file/~locale`
|
|
1587
|
+
* eg. `/uuid123/fields/file/~locale/fields/title/~locale`
|
|
1588
|
+
* @returns
|
|
1589
|
+
*/
|
|
1590
|
+
const parseDataSourcePathWithL1DeepBindings = (path) => {
|
|
1591
|
+
const parsedPath = parseDeepPath(path);
|
|
1592
|
+
if (null === parsedPath) {
|
|
1593
|
+
throw new Error(`Cannot parse path '${path}' as deep path`);
|
|
1594
|
+
}
|
|
1595
|
+
return {
|
|
1596
|
+
key: parsedPath.key,
|
|
1597
|
+
field: parsedPath.fields[0],
|
|
1598
|
+
referentField: parsedPath.fields[1],
|
|
1599
|
+
};
|
|
1600
|
+
};
|
|
1601
|
+
/**
|
|
1602
|
+
* Detects if paths is valid deep-path, like:
|
|
1603
|
+
* - /gV6yKXp61hfYrR7rEyKxY/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
|
|
1604
|
+
* or regular, like:
|
|
1605
|
+
* - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
|
|
1606
|
+
* @returns
|
|
1607
|
+
*/
|
|
1608
|
+
const isDeepPath = (deepPathCandidate) => {
|
|
1609
|
+
const deepPathParsed = parseDeepPath(deepPathCandidate);
|
|
1610
|
+
if (!deepPathParsed) {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
return deepPathParsed.fields.length > 1;
|
|
1614
|
+
};
|
|
1615
|
+
const parseDeepPath = (deepPathCandidate) => {
|
|
1616
|
+
// ALGORITHM:
|
|
1617
|
+
// We start with deep path in form:
|
|
1618
|
+
// /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
|
|
1619
|
+
// First turn string into array of segments
|
|
1620
|
+
// ['', 'uuid123', 'fields', 'mainStory', '~locale', 'fields', 'cover', '~locale', 'fields', 'title', '~locale']
|
|
1621
|
+
// Then group segments into intermediate represenatation - chunks, where each non-initial chunk starts with 'fields'
|
|
1622
|
+
// [
|
|
1623
|
+
// [ "", "uuid123" ],
|
|
1624
|
+
// [ "fields", "mainStory", "~locale" ],
|
|
1625
|
+
// [ "fields", "cover", "~locale" ],
|
|
1626
|
+
// [ "fields", "title", "~locale" ]
|
|
1627
|
+
// ]
|
|
1628
|
+
// Then check "initial" chunk for corretness
|
|
1629
|
+
// Then check all "field-leading" chunks for correctness
|
|
1630
|
+
const isValidInitialChunk = (initialChunk) => {
|
|
1631
|
+
// must have start with '' and have at least 2 segments, second non-empty
|
|
1632
|
+
// eg. /-_432uuid123123
|
|
1633
|
+
return /^\/([^/^~]+)$/.test(initialChunk.join('/'));
|
|
1634
|
+
};
|
|
1635
|
+
const isValidFieldChunk = (fieldChunk) => {
|
|
1636
|
+
// must start with 'fields' and have at least 3 segments, second non-empty and last segment must be '~locale'
|
|
1637
|
+
// eg. fields/-32234mainStory/~locale
|
|
1638
|
+
return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
|
|
1639
|
+
};
|
|
1640
|
+
const deepPathSegments = deepPathCandidate.split('/');
|
|
1641
|
+
const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
|
|
1642
|
+
if (chunks.length <= 1) {
|
|
1643
|
+
return null; // malformed path, even regular paths have at least 2 chunks
|
|
1644
|
+
}
|
|
1645
|
+
else if (chunks.length === 2) {
|
|
1646
|
+
return null; // deep paths have at least 3 chunks
|
|
1647
|
+
}
|
|
1648
|
+
// With 3+ chunks we can now check for deep path correctness
|
|
1649
|
+
const [initialChunk, ...fieldChunks] = chunks;
|
|
1650
|
+
if (!isValidInitialChunk(initialChunk)) {
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
if (!fieldChunks.every(isValidFieldChunk)) {
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
return {
|
|
1657
|
+
key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
|
|
1658
|
+
fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
|
|
1659
|
+
};
|
|
1660
|
+
};
|
|
1661
|
+
const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
|
|
1662
|
+
const chunks = [];
|
|
1663
|
+
let currentChunk = [];
|
|
1664
|
+
const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
|
|
1665
|
+
const excludeEmptyChunks = (chunk) => chunk.length > 0;
|
|
1666
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1667
|
+
const isInitialElement = i === 0;
|
|
1668
|
+
const segment = segments[i];
|
|
1669
|
+
if (isInitialElement) {
|
|
1670
|
+
currentChunk = [segment];
|
|
1671
|
+
}
|
|
1672
|
+
else if (isSegmentBeginningOfChunk(segment)) {
|
|
1673
|
+
chunks.push(currentChunk);
|
|
1674
|
+
currentChunk = [segment];
|
|
1675
|
+
}
|
|
1676
|
+
else {
|
|
1677
|
+
currentChunk.push(segment);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
chunks.push(currentChunk);
|
|
1681
|
+
return chunks.filter(excludeEmptyChunks);
|
|
1682
|
+
};
|
|
1683
|
+
const lastPathNamedSegmentEq = (path, expectedName) => {
|
|
1684
|
+
// `/key123/fields/featureImage/~locale/fields/file/~locale`
|
|
1685
|
+
// ['', 'key123', 'fields', 'featureImage', '~locale', 'fields', 'file', '~locale']
|
|
1686
|
+
const segments = path.split('/');
|
|
1687
|
+
if (segments.length < 2) {
|
|
1688
|
+
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.`);
|
|
1689
|
+
return false;
|
|
1690
|
+
}
|
|
1691
|
+
const secondLast = segments[segments.length - 2]; // skipping trailing '~locale'
|
|
1692
|
+
return secondLast === expectedName;
|
|
1693
|
+
};
|
|
1694
|
+
|
|
1695
|
+
const resolveHyperlinkPattern = (pattern, entry, locale) => {
|
|
1696
|
+
if (!entry || !locale)
|
|
1697
|
+
return null;
|
|
1698
|
+
const variables = {
|
|
1699
|
+
entry,
|
|
1700
|
+
locale,
|
|
1701
|
+
};
|
|
1702
|
+
return buildTemplate({ template: pattern, context: variables });
|
|
1703
|
+
};
|
|
1704
|
+
function getValue(obj, path) {
|
|
1705
|
+
return path
|
|
1706
|
+
.replace(/\[/g, '.')
|
|
1707
|
+
.replace(/\]/g, '')
|
|
1708
|
+
.split('.')
|
|
1709
|
+
.reduce((o, k) => (o || {})[k], obj);
|
|
1710
|
+
}
|
|
1711
|
+
function addLocale(str, locale) {
|
|
1712
|
+
const fieldsIndicator = 'fields';
|
|
1713
|
+
const fieldsIndex = str.indexOf(fieldsIndicator);
|
|
1714
|
+
if (fieldsIndex !== -1) {
|
|
1715
|
+
const dotIndex = str.indexOf('.', fieldsIndex + fieldsIndicator.length + 1); // +1 for '.'
|
|
1716
|
+
if (dotIndex !== -1) {
|
|
1717
|
+
return str.slice(0, dotIndex + 1) + locale + '.' + str.slice(dotIndex + 1);
|
|
1718
|
+
}
|
|
1382
1719
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1720
|
+
return str;
|
|
1721
|
+
}
|
|
1722
|
+
function getTemplateValue(ctx, path) {
|
|
1723
|
+
const pathWithLocale = addLocale(path, ctx.locale);
|
|
1724
|
+
const retrievedValue = getValue(ctx, pathWithLocale);
|
|
1725
|
+
return typeof retrievedValue === 'object' && retrievedValue !== null
|
|
1726
|
+
? retrievedValue[ctx.locale]
|
|
1727
|
+
: retrievedValue;
|
|
1728
|
+
}
|
|
1729
|
+
function buildTemplate({ template, context, }) {
|
|
1730
|
+
const localeVariable = /{\s*locale\s*}/g;
|
|
1731
|
+
// e.g. "{ page.sys.id }"
|
|
1732
|
+
const variables = /{\s*([\S]+?)\s*}/g;
|
|
1733
|
+
return (template
|
|
1734
|
+
// first replace the locale pattern
|
|
1735
|
+
.replace(localeVariable, context.locale)
|
|
1736
|
+
// then resolve the remaining variables
|
|
1737
|
+
.replace(variables, (_, path) => {
|
|
1738
|
+
const fallback = path + '_NOT_FOUND';
|
|
1739
|
+
const value = getTemplateValue(context, path) ?? fallback;
|
|
1740
|
+
// using _.result didn't gave proper results so we run our own version of it
|
|
1741
|
+
return String(typeof value === 'function' ? value() : value);
|
|
1742
|
+
}));
|
|
1743
|
+
}
|
|
1399
1744
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
case 'Array':
|
|
1407
|
-
if (typeof val.defaultValue !== 'undefined') {
|
|
1408
|
-
ctx.addIssue({
|
|
1409
|
-
code: z.ZodIssueCode.custom,
|
|
1410
|
-
message: `defaultValue is not supported for "Array" type for ${ctx.path.join('.')}`,
|
|
1411
|
-
fatal: false,
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
break;
|
|
1415
|
-
case 'Boolean':
|
|
1416
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'boolean') {
|
|
1417
|
-
ctx.addIssue({
|
|
1418
|
-
code: z.ZodIssueCode.custom,
|
|
1419
|
-
message: `defaultValue must be a boolean when type is "Boolean" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1420
|
-
fatal: false,
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
break;
|
|
1424
|
-
case 'Date':
|
|
1425
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1426
|
-
ctx.addIssue({
|
|
1427
|
-
code: z.ZodIssueCode.custom,
|
|
1428
|
-
message: `defaultValue must be a string when type is "Date" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1429
|
-
fatal: false,
|
|
1430
|
-
});
|
|
1431
|
-
}
|
|
1432
|
-
break;
|
|
1433
|
-
case 'Hyperlink':
|
|
1434
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1435
|
-
ctx.addIssue({
|
|
1436
|
-
code: z.ZodIssueCode.custom,
|
|
1437
|
-
message: `defaultValue must be a string when type is "Hyperlink" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1438
|
-
fatal: false,
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
break;
|
|
1442
|
-
case 'Link':
|
|
1443
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1444
|
-
ctx.addIssue({
|
|
1445
|
-
code: z.ZodIssueCode.custom,
|
|
1446
|
-
message: `defaultValue is not supported for "Link" type for ${ctx.path.join('.')}`,
|
|
1447
|
-
fatal: false,
|
|
1448
|
-
});
|
|
1449
|
-
}
|
|
1450
|
-
break;
|
|
1451
|
-
case 'Location':
|
|
1452
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1453
|
-
ctx.addIssue({
|
|
1454
|
-
code: z.ZodIssueCode.custom,
|
|
1455
|
-
message: `defaultValue must be an object when type is "Location" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1456
|
-
fatal: false,
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
break;
|
|
1460
|
-
case 'Media':
|
|
1461
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1462
|
-
ctx.addIssue({
|
|
1463
|
-
code: z.ZodIssueCode.custom,
|
|
1464
|
-
message: `defaultValue must be a string when type is "Media" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1465
|
-
fatal: false,
|
|
1466
|
-
});
|
|
1467
|
-
}
|
|
1468
|
-
break;
|
|
1469
|
-
case 'Number':
|
|
1470
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'number') {
|
|
1471
|
-
ctx.addIssue({
|
|
1472
|
-
code: z.ZodIssueCode.custom,
|
|
1473
|
-
message: `defaultValue must be a number when type is "Number" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1474
|
-
fatal: false,
|
|
1475
|
-
});
|
|
1476
|
-
}
|
|
1477
|
-
break;
|
|
1478
|
-
case 'Object':
|
|
1479
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1480
|
-
ctx.addIssue({
|
|
1481
|
-
code: z.ZodIssueCode.custom,
|
|
1482
|
-
message: `defaultValue must be an object when type is "Object" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1483
|
-
fatal: false,
|
|
1484
|
-
});
|
|
1485
|
-
}
|
|
1486
|
-
break;
|
|
1487
|
-
case 'RichText':
|
|
1488
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'object') {
|
|
1489
|
-
ctx.addIssue({
|
|
1490
|
-
code: z.ZodIssueCode.custom,
|
|
1491
|
-
message: `defaultValue must be an object when type is "RichText" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1492
|
-
fatal: false,
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
break;
|
|
1496
|
-
case 'Text':
|
|
1497
|
-
if (typeof val.defaultValue !== 'undefined' && typeof val.defaultValue !== 'string') {
|
|
1498
|
-
ctx.addIssue({
|
|
1499
|
-
code: z.ZodIssueCode.custom,
|
|
1500
|
-
message: `defaultValue must be a string when type is "Text" for ${ctx.path.join('.')}, got ${typeof val.defaultValue} instead`,
|
|
1501
|
-
fatal: false,
|
|
1502
|
-
});
|
|
1503
|
-
}
|
|
1504
|
-
break;
|
|
1505
|
-
}
|
|
1506
|
-
})),
|
|
1507
|
-
});
|
|
1745
|
+
const stylesToKeep = ['cfImageAsset'];
|
|
1746
|
+
const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.includes(style));
|
|
1747
|
+
const propsToRemove = ['cfHyperlink', 'cfOpenInNewTab', 'cfSsrClassName'];
|
|
1748
|
+
const sanitizeNodeProps = (nodeProps) => {
|
|
1749
|
+
return omit(nodeProps, stylesToRemove, propsToRemove);
|
|
1750
|
+
};
|
|
1508
1751
|
|
|
1509
|
-
|
|
1510
|
-
(
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1752
|
+
const transformVisibility = (value) => {
|
|
1753
|
+
if (value === false) {
|
|
1754
|
+
return {
|
|
1755
|
+
display: 'none !important',
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
// Don't explicitly set anything when visible to not overwrite values like `grid` or `flex`.
|
|
1759
|
+
return {};
|
|
1760
|
+
};
|
|
1761
|
+
// Keep this for backwards compatibility - deleting this would be a breaking change
|
|
1762
|
+
// because existing components on a users experience will have the width value as fill
|
|
1763
|
+
// rather than 100%
|
|
1764
|
+
const transformFill = (value) => (value === 'fill' ? '100%' : value);
|
|
1765
|
+
const transformGridColumn = (span) => {
|
|
1766
|
+
if (!span) {
|
|
1767
|
+
return {};
|
|
1768
|
+
}
|
|
1524
1769
|
return {
|
|
1525
|
-
|
|
1526
|
-
name: name,
|
|
1527
|
-
path: issue.path,
|
|
1528
|
-
value: issue.received.toString(),
|
|
1770
|
+
gridColumn: `span ${span}`,
|
|
1529
1771
|
};
|
|
1530
1772
|
};
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1773
|
+
const transformBorderStyle = (value) => {
|
|
1774
|
+
if (!value)
|
|
1775
|
+
return {};
|
|
1776
|
+
const parts = value.split(' ');
|
|
1777
|
+
// Just accept the passed value
|
|
1778
|
+
if (parts.length < 3)
|
|
1779
|
+
return { border: value };
|
|
1780
|
+
const [borderSize, borderStyle, ...borderColorParts] = parts;
|
|
1781
|
+
const borderColor = borderColorParts.join(' ');
|
|
1533
1782
|
return {
|
|
1534
|
-
|
|
1535
|
-
? `The properties ${missingProperties} are not expected`
|
|
1536
|
-
: `The property ${missingProperties} is not expected`,
|
|
1537
|
-
name: CodeNames.Unexpected,
|
|
1538
|
-
path: issue.path,
|
|
1783
|
+
border: `${borderSize} ${borderStyle} ${borderColor}`,
|
|
1539
1784
|
};
|
|
1540
1785
|
};
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1786
|
+
const transformAlignment = (cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection = 'column') => cfFlexDirection === 'row'
|
|
1787
|
+
? {
|
|
1788
|
+
alignItems: cfHorizontalAlignment,
|
|
1789
|
+
justifyContent: cfVerticalAlignment === 'center' ? `safe ${cfVerticalAlignment}` : cfVerticalAlignment,
|
|
1790
|
+
}
|
|
1791
|
+
: {
|
|
1792
|
+
alignItems: cfVerticalAlignment,
|
|
1793
|
+
justifyContent: cfHorizontalAlignment === 'center'
|
|
1794
|
+
? `safe ${cfHorizontalAlignment}`
|
|
1795
|
+
: cfHorizontalAlignment,
|
|
1796
|
+
};
|
|
1797
|
+
const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions) => {
|
|
1798
|
+
const matchBackgroundSize = (scaling) => {
|
|
1799
|
+
if ('fill' === scaling)
|
|
1800
|
+
return 'cover';
|
|
1801
|
+
if ('fit' === scaling)
|
|
1802
|
+
return 'contain';
|
|
1803
|
+
};
|
|
1804
|
+
const matchBackgroundPosition = (alignment) => {
|
|
1805
|
+
if (!alignment || 'string' !== typeof alignment) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
|
|
1809
|
+
// Special case for handling single values
|
|
1810
|
+
// for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
|
|
1811
|
+
if (horizontalAlignment && !verticalAlignment) {
|
|
1812
|
+
const singleValue = horizontalAlignment;
|
|
1813
|
+
switch (singleValue) {
|
|
1814
|
+
case 'left':
|
|
1815
|
+
horizontalAlignment = 'left';
|
|
1816
|
+
verticalAlignment = 'center';
|
|
1817
|
+
break;
|
|
1818
|
+
case 'right':
|
|
1819
|
+
horizontalAlignment = 'right';
|
|
1820
|
+
verticalAlignment = 'center';
|
|
1821
|
+
break;
|
|
1822
|
+
case 'center':
|
|
1823
|
+
horizontalAlignment = 'center';
|
|
1824
|
+
verticalAlignment = 'center';
|
|
1825
|
+
break;
|
|
1826
|
+
case 'top':
|
|
1827
|
+
horizontalAlignment = 'center';
|
|
1828
|
+
verticalAlignment = 'top';
|
|
1829
|
+
break;
|
|
1830
|
+
case 'bottom':
|
|
1831
|
+
horizontalAlignment = 'center';
|
|
1832
|
+
verticalAlignment = 'bottom';
|
|
1833
|
+
break;
|
|
1834
|
+
// just fall down to the normal validation logic for horiz and vert
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
|
|
1838
|
+
const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
|
|
1839
|
+
horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
|
|
1840
|
+
verticalAlignment = isVerticalValid ? verticalAlignment : 'top';
|
|
1841
|
+
return `${horizontalAlignment} ${verticalAlignment}`;
|
|
1546
1842
|
};
|
|
1547
|
-
|
|
1548
|
-
|
|
1843
|
+
if (!cfBackgroundImageUrl) {
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
let backgroundImage;
|
|
1847
|
+
let backgroundImageSet;
|
|
1848
|
+
if (typeof cfBackgroundImageUrl === 'string') {
|
|
1849
|
+
backgroundImage = `url(${cfBackgroundImageUrl})`;
|
|
1850
|
+
}
|
|
1851
|
+
else {
|
|
1852
|
+
const imgSet = cfBackgroundImageUrl.srcSet?.join(',');
|
|
1853
|
+
backgroundImage = `url(${cfBackgroundImageUrl.url})`;
|
|
1854
|
+
backgroundImageSet = `image-set(${imgSet})`;
|
|
1855
|
+
}
|
|
1549
1856
|
return {
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1857
|
+
backgroundImage,
|
|
1858
|
+
backgroundImage2: backgroundImageSet,
|
|
1859
|
+
backgroundRepeat: cfBackgroundImageOptions?.scaling === 'tile' ? 'repeat' : 'no-repeat',
|
|
1860
|
+
backgroundPosition: matchBackgroundPosition(cfBackgroundImageOptions?.alignment),
|
|
1861
|
+
backgroundSize: matchBackgroundSize(cfBackgroundImageOptions?.scaling),
|
|
1555
1862
|
};
|
|
1556
1863
|
};
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
expected: [issue.expected],
|
|
1564
|
-
};
|
|
1864
|
+
|
|
1865
|
+
const toCSSAttribute = (key) => {
|
|
1866
|
+
let val = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
|
1867
|
+
// Remove the number from the end of the key to allow for overrides on style properties
|
|
1868
|
+
val = val.replace(/\d+$/, '');
|
|
1869
|
+
return val;
|
|
1565
1870
|
};
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1871
|
+
/**
|
|
1872
|
+
* Turns a list of CSSProperties into a joined CSS string that can be
|
|
1873
|
+
* used for <style> tags. Per default it creates a minimized version.
|
|
1874
|
+
* For editor mode, use the `useWhitespaces` flag to create a more readable version.
|
|
1875
|
+
*
|
|
1876
|
+
* @param cssProperties list of CSS properties
|
|
1877
|
+
* @param useWhitespaces adds whitespaces and newlines between each rule
|
|
1878
|
+
* @returns a string of CSS rules
|
|
1879
|
+
*/
|
|
1880
|
+
const stringifyCssProperties = (cssProperties, useWhitespaces = false) => {
|
|
1881
|
+
const rules = Object.entries(cssProperties)
|
|
1882
|
+
.filter(([, value]) => value !== undefined)
|
|
1883
|
+
.map(([key, value]) => useWhitespaces ? `${toCSSAttribute(key)}: ${value};` : `${toCSSAttribute(key)}:${value};`);
|
|
1884
|
+
return rules.join(useWhitespaces ? '\n' : '');
|
|
1573
1885
|
};
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
min: issue.minimum,
|
|
1580
|
-
};
|
|
1886
|
+
const buildStyleTag = ({ styles, nodeId }) => {
|
|
1887
|
+
const generatedStyles = stringifyCssProperties(styles, true);
|
|
1888
|
+
const className = `cfstyles-${nodeId ? nodeId : md5(generatedStyles)}`;
|
|
1889
|
+
const styleRule = `.${className}{ ${generatedStyles} }`;
|
|
1890
|
+
return [className, styleRule];
|
|
1581
1891
|
};
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1892
|
+
/**
|
|
1893
|
+
* Takes plain design values and transforms them into CSS properties. Undefined values will
|
|
1894
|
+
* be filtered out.
|
|
1895
|
+
*
|
|
1896
|
+
* **Example Input**
|
|
1897
|
+
* ```
|
|
1898
|
+
* values = {
|
|
1899
|
+
* cfVisibility: 'visible',
|
|
1900
|
+
* cfMargin: '10px',
|
|
1901
|
+
* cfFlexReverse: true,
|
|
1902
|
+
* cfImageOptions: { objectFit: 'cover' },
|
|
1903
|
+
* // ...
|
|
1904
|
+
* }
|
|
1905
|
+
* ```
|
|
1906
|
+
* **Example Output**
|
|
1907
|
+
* ```
|
|
1908
|
+
* cssProperties = {
|
|
1909
|
+
* margin: '10px',
|
|
1910
|
+
* flexDirection: 'row-reverse',
|
|
1911
|
+
* objectFit: 'cover',
|
|
1912
|
+
* // ...
|
|
1913
|
+
* }
|
|
1914
|
+
* ```
|
|
1915
|
+
*/
|
|
1916
|
+
const buildCfStyles = (values) => {
|
|
1917
|
+
const cssProperties = {
|
|
1918
|
+
boxSizing: 'border-box',
|
|
1919
|
+
...transformVisibility(values.cfVisibility),
|
|
1920
|
+
margin: values.cfMargin,
|
|
1921
|
+
padding: values.cfPadding,
|
|
1922
|
+
backgroundColor: values.cfBackgroundColor,
|
|
1923
|
+
width: transformFill(values.cfWidth || values.cfImageOptions?.width),
|
|
1924
|
+
height: transformFill(values.cfHeight || values.cfImageOptions?.height),
|
|
1925
|
+
maxWidth: values.cfMaxWidth,
|
|
1926
|
+
...transformGridColumn(values.cfColumnSpan),
|
|
1927
|
+
...transformBorderStyle(values.cfBorder),
|
|
1928
|
+
borderRadius: values.cfBorderRadius,
|
|
1929
|
+
gap: values.cfGap,
|
|
1930
|
+
...transformAlignment(values.cfHorizontalAlignment, values.cfVerticalAlignment, values.cfFlexDirection),
|
|
1931
|
+
flexDirection: values.cfFlexReverse && values.cfFlexDirection
|
|
1932
|
+
? `${values.cfFlexDirection}-reverse`
|
|
1933
|
+
: values.cfFlexDirection,
|
|
1934
|
+
flexWrap: values.cfFlexWrap,
|
|
1935
|
+
...transformBackgroundImage(values.cfBackgroundImageUrl, values.cfBackgroundImageOptions),
|
|
1936
|
+
fontSize: values.cfFontSize,
|
|
1937
|
+
fontWeight: values.cfTextBold ? 'bold' : values.cfFontWeight,
|
|
1938
|
+
fontStyle: values.cfTextItalic ? 'italic' : undefined,
|
|
1939
|
+
textDecoration: values.cfTextUnderline ? 'underline' : undefined,
|
|
1940
|
+
lineHeight: values.cfLineHeight,
|
|
1941
|
+
letterSpacing: values.cfLetterSpacing,
|
|
1942
|
+
color: values.cfTextColor,
|
|
1943
|
+
textAlign: values.cfTextAlign,
|
|
1944
|
+
textTransform: values.cfTextTransform,
|
|
1945
|
+
objectFit: values.cfImageOptions?.objectFit,
|
|
1946
|
+
objectPosition: values.cfImageOptions?.objectPosition,
|
|
1587
1947
|
};
|
|
1948
|
+
const cssPropertiesWithoutUndefined = Object.fromEntries(Object.entries(cssProperties).filter(([, value]) => value !== undefined));
|
|
1949
|
+
return cssPropertiesWithoutUndefined;
|
|
1588
1950
|
};
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
case ZodIssueCode.too_small:
|
|
1600
|
-
return convertTooSmall(issue);
|
|
1601
|
-
case ZodIssueCode.too_big:
|
|
1602
|
-
return convertTooBig(issue);
|
|
1603
|
-
case ZodIssueCode.invalid_literal:
|
|
1604
|
-
return convertInvalidLiteral(issue);
|
|
1605
|
-
default:
|
|
1606
|
-
return defaultConversion(issue);
|
|
1607
|
-
}
|
|
1608
|
-
};
|
|
1609
|
-
|
|
1610
|
-
const validateBreakpointsDefinition = (breakpoints) => {
|
|
1611
|
-
const result = z
|
|
1612
|
-
.array(BreakpointSchema)
|
|
1613
|
-
.superRefine(breakpointsRefinement)
|
|
1614
|
-
.safeParse(breakpoints);
|
|
1615
|
-
if (!result.success) {
|
|
1951
|
+
/**
|
|
1952
|
+
* **Only meant to be used in editor mode!**
|
|
1953
|
+
*
|
|
1954
|
+
* If the node is an empty structure component with a relative height (e.g. '100%'),
|
|
1955
|
+
* it might render with a zero height. In this case, add a min height of 80px to ensure
|
|
1956
|
+
* that child nodes can be added via drag & drop.
|
|
1957
|
+
*/
|
|
1958
|
+
const addMinHeightForEmptyStructures = (cssProperties, node) => {
|
|
1959
|
+
if (!node.children.length &&
|
|
1960
|
+
isStructureWithRelativeHeight(node.definitionId, cssProperties.height)) {
|
|
1616
1961
|
return {
|
|
1617
|
-
|
|
1618
|
-
|
|
1962
|
+
...cssProperties,
|
|
1963
|
+
minHeight: EMPTY_CONTAINER_HEIGHT,
|
|
1619
1964
|
};
|
|
1620
1965
|
}
|
|
1621
|
-
return
|
|
1966
|
+
return cssProperties;
|
|
1622
1967
|
};
|
|
1623
|
-
|
|
1624
|
-
let breakpointsRegistry = [];
|
|
1625
1968
|
/**
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1969
|
+
* Container/section default behavior:
|
|
1970
|
+
* Default height => height: EMPTY_CONTAINER_HEIGHT
|
|
1971
|
+
* If a container component has children => height: 'fit-content'
|
|
1629
1972
|
*/
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1973
|
+
const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
|
|
1974
|
+
if (!blockId || !isContentfulStructureComponent(blockId) || value !== 'auto') {
|
|
1975
|
+
return value;
|
|
1976
|
+
}
|
|
1977
|
+
if (children.length) {
|
|
1978
|
+
return '100%';
|
|
1979
|
+
}
|
|
1980
|
+
return EMPTY_CONTAINER_HEIGHT;
|
|
1632
1981
|
};
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1982
|
+
|
|
1983
|
+
function getOptimizedImageUrl(url, width, quality, format) {
|
|
1984
|
+
if (url.startsWith('//')) {
|
|
1985
|
+
url = 'https:' + url;
|
|
1986
|
+
}
|
|
1987
|
+
const params = new URLSearchParams();
|
|
1988
|
+
if (width) {
|
|
1989
|
+
params.append('w', width.toString());
|
|
1990
|
+
}
|
|
1991
|
+
if (quality && quality > 0 && quality < 100) {
|
|
1992
|
+
params.append('q', quality.toString());
|
|
1993
|
+
}
|
|
1994
|
+
if (format) {
|
|
1995
|
+
params.append('fm', format);
|
|
1996
|
+
}
|
|
1997
|
+
const queryString = params.toString();
|
|
1998
|
+
return `${url}${queryString ? '?' + queryString : ''}`;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
function validateParams(file, quality, format) {
|
|
2002
|
+
if (!file.details.image) {
|
|
2003
|
+
throw Error('No image in file asset to transform');
|
|
2004
|
+
}
|
|
2005
|
+
if (quality < 0 || quality > 100) {
|
|
2006
|
+
throw Error('Quality must be between 0 and 100');
|
|
2007
|
+
}
|
|
2008
|
+
if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
|
|
2009
|
+
throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
|
|
2010
|
+
}
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
const MAX_WIDTH_ALLOWED$1 = 2000;
|
|
2015
|
+
const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', format) => {
|
|
2016
|
+
const qualityNumber = Number(quality.replace('%', ''));
|
|
2017
|
+
if (!validateParams(file, qualityNumber, format)) ;
|
|
2018
|
+
if (!validateParams(file, qualityNumber, format)) ;
|
|
2019
|
+
const url = file.url;
|
|
2020
|
+
const { width1x, width2x } = getWidths(widthStyle, file);
|
|
2021
|
+
const imageUrl1x = getOptimizedImageUrl(url, width1x, qualityNumber, format);
|
|
2022
|
+
const imageUrl2x = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
2023
|
+
const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
|
|
2024
|
+
const returnedUrl = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
2025
|
+
const optimizedBackgroundImageAsset = {
|
|
2026
|
+
url: returnedUrl,
|
|
2027
|
+
srcSet,
|
|
2028
|
+
file,
|
|
2029
|
+
};
|
|
2030
|
+
return optimizedBackgroundImageAsset;
|
|
2031
|
+
function getWidths(widthStyle, file) {
|
|
2032
|
+
let width1x = 0;
|
|
2033
|
+
let width2x = 0;
|
|
2034
|
+
const intrinsicImageWidth = file.details.image.width;
|
|
2035
|
+
if (widthStyle.endsWith('px')) {
|
|
2036
|
+
width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
|
|
2037
|
+
}
|
|
2038
|
+
else {
|
|
2039
|
+
width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
|
|
2040
|
+
}
|
|
2041
|
+
width2x = Math.min(width1x * 2, intrinsicImageWidth);
|
|
2042
|
+
return { width1x, width2x };
|
|
1639
2043
|
}
|
|
1640
2044
|
};
|
|
1641
|
-
// Used in the tests to get a breakpoint registration
|
|
1642
|
-
const getBreakpointRegistration = (id) => breakpointsRegistry.find((breakpoint) => breakpoint.id === id);
|
|
1643
|
-
// Used in the tests to reset the registry
|
|
1644
|
-
const resetBreakpointsRegistry = () => {
|
|
1645
|
-
breakpointsRegistry = [];
|
|
1646
|
-
};
|
|
1647
2045
|
|
|
1648
2046
|
const detachExperienceStyles = (experience) => {
|
|
1649
2047
|
const experienceTreeRoot = experience.entityStore?.experienceEntryFields
|
|
@@ -1664,17 +2062,25 @@ const detachExperienceStyles = (experience) => {
|
|
|
1664
2062
|
}), {});
|
|
1665
2063
|
// getting the breakpoint ids
|
|
1666
2064
|
const breakpointIds = Object.keys(mediaQueryDataByBreakpoint);
|
|
1667
|
-
const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, patternWrapper, wrappingPatternIds,
|
|
2065
|
+
const iterateOverTreeAndExtractStyles = ({ componentTree, dataSource, unboundValues, componentSettings, componentVariablesOverwrites, patternWrapper, wrappingPatternIds, parentChainArr = [], }) => {
|
|
1668
2066
|
// traversing the tree
|
|
1669
2067
|
const queue = [];
|
|
1670
|
-
queue.push(...componentTree.children)
|
|
1671
|
-
|
|
2068
|
+
queue.push(...componentTree.children.map((child) => ({
|
|
2069
|
+
node: child,
|
|
2070
|
+
parentChain: [...parentChainArr],
|
|
2071
|
+
})));
|
|
1672
2072
|
// for each tree node
|
|
1673
2073
|
while (queue.length) {
|
|
1674
|
-
|
|
2074
|
+
const queueItem = queue.shift();
|
|
2075
|
+
if (!queueItem) {
|
|
2076
|
+
break;
|
|
2077
|
+
}
|
|
2078
|
+
const { node: currentNode, parentChain } = queueItem;
|
|
1675
2079
|
if (!currentNode) {
|
|
1676
2080
|
break;
|
|
1677
2081
|
}
|
|
2082
|
+
const currentNodeParentChain = [...parentChain, currentNode.id || ''];
|
|
2083
|
+
const currentPatternNodeIdsChain = currentNodeParentChain.join('');
|
|
1678
2084
|
const usedComponents = experience.entityStore?.usedComponents ?? [];
|
|
1679
2085
|
const isPatternNode = checkIsAssemblyNode({
|
|
1680
2086
|
componentId: currentNode.definitionId,
|
|
@@ -1712,7 +2118,7 @@ const detachExperienceStyles = (experience) => {
|
|
|
1712
2118
|
// pass top-level pattern node to store instance-specific child styles for rendering
|
|
1713
2119
|
patternWrapper: currentNode,
|
|
1714
2120
|
wrappingPatternIds: new Set([...wrappingPatternIds, currentNode.definitionId]),
|
|
1715
|
-
|
|
2121
|
+
parentChainArr: currentNodeParentChain,
|
|
1716
2122
|
});
|
|
1717
2123
|
continue;
|
|
1718
2124
|
}
|
|
@@ -1798,13 +2204,12 @@ const detachExperienceStyles = (experience) => {
|
|
|
1798
2204
|
// making sure that we respect the order of breakpoints from
|
|
1799
2205
|
// we can achieve "desktop first" or "mobile first" approach to style over-writes
|
|
1800
2206
|
if (patternWrapper) {
|
|
1801
|
-
currentNode.id = currentNode.id || generateRandomId(5);
|
|
1802
2207
|
// @ts-expect-error -- valueByBreakpoint is not explicitly defined, but it's already defined in the patternWrapper styles
|
|
1803
2208
|
patternWrapper.variables.cfSsrClassName = {
|
|
1804
2209
|
...(patternWrapper.variables.cfSsrClassName ?? {}),
|
|
1805
2210
|
type: 'DesignValue',
|
|
1806
2211
|
// Chain IDs to avoid overwriting styles across multiple instances of the same pattern
|
|
1807
|
-
[
|
|
2212
|
+
[currentPatternNodeIdsChain]: {
|
|
1808
2213
|
valuesByBreakpoint: {
|
|
1809
2214
|
[breakpointIds[0]]: currentNodeClassNames.join(' '),
|
|
1810
2215
|
},
|
|
@@ -1819,7 +2224,10 @@ const detachExperienceStyles = (experience) => {
|
|
|
1819
2224
|
},
|
|
1820
2225
|
};
|
|
1821
2226
|
}
|
|
1822
|
-
queue.push(...currentNode.children)
|
|
2227
|
+
queue.push(...currentNode.children.map((child) => ({
|
|
2228
|
+
node: child,
|
|
2229
|
+
parentChain: currentNodeParentChain,
|
|
2230
|
+
})));
|
|
1823
2231
|
}
|
|
1824
2232
|
};
|
|
1825
2233
|
iterateOverTreeAndExtractStyles({
|
|
@@ -1941,7 +2349,28 @@ const maybePopulateDesignTokenValue = (variableName, variableValue, mapOfDesignV
|
|
|
1941
2349
|
// Not trimming would end up with a trailing space that breaks the check in `calculateNodeDefaultHeight`
|
|
1942
2350
|
return resolvedValue.trim();
|
|
1943
2351
|
};
|
|
1944
|
-
const
|
|
2352
|
+
const transformMedia$1 = (boundAsset, width, options) => {
|
|
2353
|
+
try {
|
|
2354
|
+
const asset = boundAsset;
|
|
2355
|
+
// Target width (px/rem/em) will be applied to the css url if it's lower than the original image width (in px)
|
|
2356
|
+
const assetDetails = asset.fields.file?.details;
|
|
2357
|
+
const assetWidth = assetDetails?.image?.width || 0; // This is always in px
|
|
2358
|
+
if (!options) {
|
|
2359
|
+
return asset.fields.file?.url;
|
|
2360
|
+
}
|
|
2361
|
+
const targetWidthObject = parseCSSValue(options.targetSize); // Contains value and unit (px/rem/em) so convert and then compare to assetWidth
|
|
2362
|
+
const targetValue = targetWidthObject ? getTargetValueInPixels(targetWidthObject) : assetWidth;
|
|
2363
|
+
if (targetValue < assetWidth)
|
|
2364
|
+
width = `${targetValue}px`;
|
|
2365
|
+
const value = getOptimizedBackgroundImageAsset(asset.fields.file, width, options.quality, options.format);
|
|
2366
|
+
return value;
|
|
2367
|
+
}
|
|
2368
|
+
catch (error) {
|
|
2369
|
+
console.error('Error transforming image asset', error);
|
|
2370
|
+
}
|
|
2371
|
+
return boundAsset.fields.file?.url;
|
|
2372
|
+
};
|
|
2373
|
+
const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataSource = {}, unboundValues = {}, componentVariablesOverwrites, componentSettings = { variableDefinitions: {} }, options, width, }) => {
|
|
1945
2374
|
if (variableData.type === 'UnboundValue') {
|
|
1946
2375
|
const uuid = variableData.key;
|
|
1947
2376
|
return unboundValues[uuid]?.value;
|
|
@@ -1966,6 +2395,8 @@ const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataS
|
|
|
1966
2395
|
unboundValues,
|
|
1967
2396
|
componentVariablesOverwrites,
|
|
1968
2397
|
componentSettings,
|
|
2398
|
+
options,
|
|
2399
|
+
width,
|
|
1969
2400
|
});
|
|
1970
2401
|
return resolvedValue || defaultValue;
|
|
1971
2402
|
}
|
|
@@ -1978,7 +2409,7 @@ const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataS
|
|
|
1978
2409
|
return;
|
|
1979
2410
|
}
|
|
1980
2411
|
if (boundEntity.sys.type === 'Asset') {
|
|
1981
|
-
return boundEntity
|
|
2412
|
+
return transformMedia$1(boundEntity, width, options);
|
|
1982
2413
|
}
|
|
1983
2414
|
else {
|
|
1984
2415
|
// '/lUERH7tX7nJTaPX6f0udB/fields/assetReference/~locale/fields/file/~locale'
|
|
@@ -2002,11 +2433,31 @@ const resolveBackgroundImageBinding = ({ variableData, getBoundEntityById, dataS
|
|
|
2002
2433
|
if (!referencedAsset) {
|
|
2003
2434
|
return;
|
|
2004
2435
|
}
|
|
2005
|
-
return referencedAsset
|
|
2436
|
+
return transformMedia$1(referencedAsset, width, options);
|
|
2006
2437
|
}
|
|
2007
2438
|
}
|
|
2008
2439
|
}
|
|
2009
2440
|
};
|
|
2441
|
+
const resolveVariable = ({ variableData, defaultBreakpoint, componentSettings = { variableDefinitions: {} }, componentVariablesOverwrites, }) => {
|
|
2442
|
+
if (variableData?.type === 'DesignValue') {
|
|
2443
|
+
return variableData.valuesByBreakpoint[defaultBreakpoint] || {};
|
|
2444
|
+
}
|
|
2445
|
+
else if (variableData?.type === 'ComponentValue') {
|
|
2446
|
+
const variableDefinitionKey = variableData.key;
|
|
2447
|
+
const variableDefinition = componentSettings.variableDefinitions[variableDefinitionKey];
|
|
2448
|
+
const defaultValue = variableDefinition.defaultValue;
|
|
2449
|
+
const userSetValue = componentVariablesOverwrites?.[variableDefinitionKey];
|
|
2450
|
+
if (!userSetValue || userSetValue.type === 'ComponentValue') {
|
|
2451
|
+
return defaultValue?.valuesByBreakpoint[defaultBreakpoint] || '';
|
|
2452
|
+
}
|
|
2453
|
+
return resolveVariable({
|
|
2454
|
+
variableData: userSetValue,
|
|
2455
|
+
defaultBreakpoint,
|
|
2456
|
+
componentSettings,
|
|
2457
|
+
componentVariablesOverwrites,
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
};
|
|
2010
2461
|
/**
|
|
2011
2462
|
* Takes the initial set of properties, filters only design properties that will be mapped to CSS and
|
|
2012
2463
|
* re-organizes them to be indexed by breakpoint ID ("breakpoint > variable > value"). It will
|
|
@@ -2057,6 +2508,22 @@ const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unbou
|
|
|
2057
2508
|
if (variableName === 'cfBackgroundImageUrl' ||
|
|
2058
2509
|
// TODO: Test this for nested patterns as the name might be just a random hash without the actual name (needs to be validated).
|
|
2059
2510
|
variableName.startsWith('cfBackgroundImageUrl_')) {
|
|
2511
|
+
const width = resolveVariable({
|
|
2512
|
+
variableData: variables['cfWidth'],
|
|
2513
|
+
defaultBreakpoint,
|
|
2514
|
+
componentSettings,
|
|
2515
|
+
componentVariablesOverwrites,
|
|
2516
|
+
});
|
|
2517
|
+
const options = resolveVariable({
|
|
2518
|
+
variableData: variables['cfBackgroundImageOptions'],
|
|
2519
|
+
defaultBreakpoint,
|
|
2520
|
+
componentSettings,
|
|
2521
|
+
componentVariablesOverwrites,
|
|
2522
|
+
});
|
|
2523
|
+
if (!options) {
|
|
2524
|
+
console.error(`Error transforming image asset: Required variable [cfBackgroundImageOptions] missing from component definition`);
|
|
2525
|
+
continue;
|
|
2526
|
+
}
|
|
2060
2527
|
const imageUrl = resolveBackgroundImageBinding({
|
|
2061
2528
|
variableData,
|
|
2062
2529
|
getBoundEntityById,
|
|
@@ -2064,6 +2531,8 @@ const indexByBreakpoint = ({ variables, breakpointIds, getBoundEntityById, unbou
|
|
|
2064
2531
|
dataSource,
|
|
2065
2532
|
componentSettings,
|
|
2066
2533
|
componentVariablesOverwrites,
|
|
2534
|
+
width,
|
|
2535
|
+
options,
|
|
2067
2536
|
});
|
|
2068
2537
|
if (imageUrl) {
|
|
2069
2538
|
variableValuesByBreakpoints[defaultBreakpoint][variableName] = imageUrl;
|
|
@@ -2203,69 +2672,6 @@ const resolveLinks = (node, entityStore) => {
|
|
|
2203
2672
|
}
|
|
2204
2673
|
};
|
|
2205
2674
|
|
|
2206
|
-
function getOptimizedImageUrl(url, width, quality, format) {
|
|
2207
|
-
if (url.startsWith('//')) {
|
|
2208
|
-
url = 'https:' + url;
|
|
2209
|
-
}
|
|
2210
|
-
const params = new URLSearchParams();
|
|
2211
|
-
if (width) {
|
|
2212
|
-
params.append('w', width.toString());
|
|
2213
|
-
}
|
|
2214
|
-
if (quality && quality > 0 && quality < 100) {
|
|
2215
|
-
params.append('q', quality.toString());
|
|
2216
|
-
}
|
|
2217
|
-
if (format) {
|
|
2218
|
-
params.append('fm', format);
|
|
2219
|
-
}
|
|
2220
|
-
const queryString = params.toString();
|
|
2221
|
-
return `${url}${queryString ? '?' + queryString : ''}`;
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
function validateParams(file, quality, format) {
|
|
2225
|
-
if (!file.details.image) {
|
|
2226
|
-
throw Error('No image in file asset to transform');
|
|
2227
|
-
}
|
|
2228
|
-
if (quality < 0 || quality > 100) {
|
|
2229
|
-
throw Error('Quality must be between 0 and 100');
|
|
2230
|
-
}
|
|
2231
|
-
if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
|
|
2232
|
-
throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
|
|
2233
|
-
}
|
|
2234
|
-
return true;
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
const MAX_WIDTH_ALLOWED$1 = 2000;
|
|
2238
|
-
const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = '100%', format) => {
|
|
2239
|
-
const qualityNumber = Number(quality.replace('%', ''));
|
|
2240
|
-
if (!validateParams(file, qualityNumber, format)) ;
|
|
2241
|
-
if (!validateParams(file, qualityNumber, format)) ;
|
|
2242
|
-
const url = file.url;
|
|
2243
|
-
const { width1x, width2x } = getWidths(widthStyle, file);
|
|
2244
|
-
const imageUrl1x = getOptimizedImageUrl(url, width1x, qualityNumber, format);
|
|
2245
|
-
const imageUrl2x = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
2246
|
-
const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
|
|
2247
|
-
const returnedUrl = getOptimizedImageUrl(url, width2x, qualityNumber, format);
|
|
2248
|
-
const optimizedBackgroundImageAsset = {
|
|
2249
|
-
url: returnedUrl,
|
|
2250
|
-
srcSet,
|
|
2251
|
-
file,
|
|
2252
|
-
};
|
|
2253
|
-
return optimizedBackgroundImageAsset;
|
|
2254
|
-
function getWidths(widthStyle, file) {
|
|
2255
|
-
let width1x = 0;
|
|
2256
|
-
let width2x = 0;
|
|
2257
|
-
const intrinsicImageWidth = file.details.image.width;
|
|
2258
|
-
if (widthStyle.endsWith('px')) {
|
|
2259
|
-
width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
|
|
2260
|
-
}
|
|
2261
|
-
else {
|
|
2262
|
-
width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
|
|
2263
|
-
}
|
|
2264
|
-
width2x = Math.min(width1x * 2, intrinsicImageWidth);
|
|
2265
|
-
return { width1x, width2x };
|
|
2266
|
-
}
|
|
2267
|
-
};
|
|
2268
|
-
|
|
2269
2675
|
const MAX_WIDTH_ALLOWED = 4000;
|
|
2270
2676
|
const getOptimizedImageAsset = ({ file, sizes, loading, quality = '100%', format, }) => {
|
|
2271
2677
|
const qualityNumber = Number(quality.replace('%', ''));
|
|
@@ -2428,138 +2834,30 @@ function getArrayValue(entryOrAsset, path, entityStore) {
|
|
|
2428
2834
|
return undefined;
|
|
2429
2835
|
}
|
|
2430
2836
|
});
|
|
2431
|
-
return result;
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
|
-
const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableType, path) => {
|
|
2435
|
-
const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
|
|
2436
|
-
if (!entityOrAsset)
|
|
2437
|
-
return;
|
|
2438
|
-
switch (variableType) {
|
|
2439
|
-
case 'Media':
|
|
2440
|
-
// If we bound a normal entry field to the media variable we just return the bound value
|
|
2441
|
-
if (entityOrAsset.sys.type === 'Entry') {
|
|
2442
|
-
return getBoundValue(entityOrAsset, path);
|
|
2443
|
-
}
|
|
2444
|
-
return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
|
|
2445
|
-
case 'RichText':
|
|
2446
|
-
return transformRichText(entityOrAsset, entityStore, path);
|
|
2447
|
-
case 'Array':
|
|
2448
|
-
return getArrayValue(entityOrAsset, path, entityStore);
|
|
2449
|
-
case 'Link':
|
|
2450
|
-
return getResolvedEntryFromLink(entityOrAsset, path, entityStore);
|
|
2451
|
-
default:
|
|
2452
|
-
return getBoundValue(entityOrAsset, path);
|
|
2453
|
-
}
|
|
2454
|
-
};
|
|
2455
|
-
|
|
2456
|
-
const getDataFromTree = (tree) => {
|
|
2457
|
-
let dataSource = {};
|
|
2458
|
-
let unboundValues = {};
|
|
2459
|
-
const queue = [...tree.root.children];
|
|
2460
|
-
while (queue.length) {
|
|
2461
|
-
const node = queue.shift();
|
|
2462
|
-
if (!node) {
|
|
2463
|
-
continue;
|
|
2464
|
-
}
|
|
2465
|
-
dataSource = { ...dataSource, ...node.data.dataSource };
|
|
2466
|
-
unboundValues = { ...unboundValues, ...node.data.unboundValues };
|
|
2467
|
-
if (node.children.length) {
|
|
2468
|
-
queue.push(...node.children);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
return {
|
|
2472
|
-
dataSource,
|
|
2473
|
-
unboundValues,
|
|
2474
|
-
};
|
|
2475
|
-
};
|
|
2476
|
-
/**
|
|
2477
|
-
* Gets calculates the index to drop the dragged component based on the mouse position
|
|
2478
|
-
* @returns {InsertionData} a object containing a node that will become a parent for dragged component and index at which it must be inserted
|
|
2479
|
-
*/
|
|
2480
|
-
const getInsertionData = ({ dropReceiverParentNode, dropReceiverNode, flexDirection, isMouseAtTopBorder, isMouseAtBottomBorder, isMouseInLeftHalf, isMouseInUpperHalf, isOverTopIndicator, isOverBottomIndicator, }) => {
|
|
2481
|
-
const APPEND_INSIDE = dropReceiverNode.children.length;
|
|
2482
|
-
const PREPEND_INSIDE = 0;
|
|
2483
|
-
if (isMouseAtTopBorder || isMouseAtBottomBorder) {
|
|
2484
|
-
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2485
|
-
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2486
|
-
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
2487
|
-
return {
|
|
2488
|
-
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2489
|
-
node: dropReceiverParentNode,
|
|
2490
|
-
index: isMouseAtBottomBorder ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2491
|
-
};
|
|
2492
|
-
}
|
|
2493
|
-
// if over one of the section indicators
|
|
2494
|
-
if (isOverTopIndicator || isOverBottomIndicator) {
|
|
2495
|
-
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2496
|
-
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2497
|
-
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
2498
|
-
return {
|
|
2499
|
-
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2500
|
-
node: dropReceiverParentNode,
|
|
2501
|
-
index: isOverBottomIndicator ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2502
|
-
};
|
|
2503
|
-
}
|
|
2504
|
-
if (flexDirection === undefined || flexDirection === 'row') {
|
|
2505
|
-
return {
|
|
2506
|
-
node: dropReceiverNode,
|
|
2507
|
-
index: isMouseInLeftHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
2508
|
-
};
|
|
2509
|
-
}
|
|
2510
|
-
else {
|
|
2511
|
-
return {
|
|
2512
|
-
node: dropReceiverNode,
|
|
2513
|
-
index: isMouseInUpperHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
2514
|
-
};
|
|
2515
|
-
}
|
|
2516
|
-
};
|
|
2517
|
-
const generateRandomId = (letterCount) => {
|
|
2518
|
-
const LETTERS = 'abcdefghijklmnopqvwxyzABCDEFGHIJKLMNOPQVWXYZ';
|
|
2519
|
-
const NUMS = '0123456789';
|
|
2520
|
-
const ALNUM = NUMS + LETTERS;
|
|
2521
|
-
const times = (n, callback) => Array.from({ length: n }, callback);
|
|
2522
|
-
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
2523
|
-
return times(letterCount, () => ALNUM[random(0, ALNUM.length - 1)]).join('');
|
|
2524
|
-
};
|
|
2525
|
-
const checkIsAssemblyNode = ({ componentId, usedComponents, }) => {
|
|
2526
|
-
if (!usedComponents?.length)
|
|
2527
|
-
return false;
|
|
2528
|
-
return usedComponents.some((usedComponent) => usedComponent.sys.id === componentId);
|
|
2529
|
-
};
|
|
2530
|
-
/** @deprecated use `checkIsAssemblyNode` instead. Will be removed with SDK v5. */
|
|
2531
|
-
const checkIsAssembly = checkIsAssemblyNode;
|
|
2532
|
-
/**
|
|
2533
|
-
* This check assumes that the entry is already ensured to be an experience, i.e. the
|
|
2534
|
-
* content type of the entry is an experience type with the necessary annotations.
|
|
2535
|
-
**/
|
|
2536
|
-
const checkIsAssemblyEntry = (entry) => {
|
|
2537
|
-
return Boolean(entry.fields?.componentSettings);
|
|
2538
|
-
};
|
|
2539
|
-
const checkIsAssemblyDefinition = (component) => component?.category === ASSEMBLY_DEFAULT_CATEGORY;
|
|
2540
|
-
function parseCSSValue(input) {
|
|
2541
|
-
const regex = /^(\d+(\.\d+)?)(px|em|rem)$/;
|
|
2542
|
-
const match = input.match(regex);
|
|
2543
|
-
if (match) {
|
|
2544
|
-
return {
|
|
2545
|
-
value: parseFloat(match[1]),
|
|
2546
|
-
unit: match[3],
|
|
2547
|
-
};
|
|
2548
|
-
}
|
|
2549
|
-
return null;
|
|
2550
|
-
}
|
|
2551
|
-
function getTargetValueInPixels(targetWidthObject) {
|
|
2552
|
-
switch (targetWidthObject.unit) {
|
|
2553
|
-
case 'px':
|
|
2554
|
-
return targetWidthObject.value;
|
|
2555
|
-
case 'em':
|
|
2556
|
-
return targetWidthObject.value * 16;
|
|
2557
|
-
case 'rem':
|
|
2558
|
-
return targetWidthObject.value * 16;
|
|
2837
|
+
return result;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableType, path) => {
|
|
2841
|
+
const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
|
|
2842
|
+
if (!entityOrAsset)
|
|
2843
|
+
return;
|
|
2844
|
+
switch (variableType) {
|
|
2845
|
+
case 'Media':
|
|
2846
|
+
// If we bound a normal entry field to the media variable we just return the bound value
|
|
2847
|
+
if (entityOrAsset.sys.type === 'Entry') {
|
|
2848
|
+
return getBoundValue(entityOrAsset, path);
|
|
2849
|
+
}
|
|
2850
|
+
return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
|
|
2851
|
+
case 'RichText':
|
|
2852
|
+
return transformRichText(entityOrAsset, entityStore, path);
|
|
2853
|
+
case 'Array':
|
|
2854
|
+
return getArrayValue(entityOrAsset, path, entityStore);
|
|
2855
|
+
case 'Link':
|
|
2856
|
+
return getResolvedEntryFromLink(entityOrAsset, path, entityStore);
|
|
2559
2857
|
default:
|
|
2560
|
-
return
|
|
2858
|
+
return getBoundValue(entityOrAsset, path);
|
|
2561
2859
|
}
|
|
2562
|
-
}
|
|
2860
|
+
};
|
|
2563
2861
|
|
|
2564
2862
|
const isExperienceEntry = (entry) => {
|
|
2565
2863
|
return (entry?.sys?.type === 'Entry' &&
|
|
@@ -2571,368 +2869,177 @@ const isExperienceEntry = (entry) => {
|
|
|
2571
2869
|
typeof entry.fields.componentTree.schemaVersion === 'string');
|
|
2572
2870
|
};
|
|
2573
2871
|
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
if (operator === '<') {
|
|
2583
|
-
const maxScreenWidth = Number(value) - 1;
|
|
2584
|
-
return `(max-width: ${maxScreenWidth}${unit})`;
|
|
2585
|
-
}
|
|
2586
|
-
else if (operator === '>') {
|
|
2587
|
-
const minScreenWidth = Number(value) + 1;
|
|
2588
|
-
return `(min-width: ${minScreenWidth}${unit})`;
|
|
2589
|
-
}
|
|
2590
|
-
return undefined;
|
|
2591
|
-
};
|
|
2592
|
-
// Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
|
|
2593
|
-
const findLast = (array, predicate) => {
|
|
2594
|
-
return array.reverse().find(predicate);
|
|
2595
|
-
};
|
|
2596
|
-
// Initialise media query matchers. This won't include the always matching fallback breakpoint.
|
|
2597
|
-
const mediaQueryMatcher = (breakpoints) => {
|
|
2598
|
-
const mediaQueryMatches = {};
|
|
2599
|
-
const mediaQueryMatchers = breakpoints
|
|
2600
|
-
.map((breakpoint) => {
|
|
2601
|
-
const cssMediaQuery = toCSSMediaQuery(breakpoint);
|
|
2602
|
-
if (!cssMediaQuery)
|
|
2603
|
-
return undefined;
|
|
2604
|
-
if (typeof window === 'undefined')
|
|
2605
|
-
return undefined;
|
|
2606
|
-
const mediaQueryMatcher = window.matchMedia(cssMediaQuery);
|
|
2607
|
-
mediaQueryMatches[breakpoint.id] = mediaQueryMatcher.matches;
|
|
2608
|
-
return { id: breakpoint.id, signal: mediaQueryMatcher };
|
|
2609
|
-
})
|
|
2610
|
-
.filter((matcher) => !!matcher);
|
|
2611
|
-
return [mediaQueryMatchers, mediaQueryMatches];
|
|
2612
|
-
};
|
|
2613
|
-
const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
|
|
2614
|
-
// The breakpoints are ordered (desktop-first: descending by screen width)
|
|
2615
|
-
const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
|
|
2616
|
-
id,
|
|
2617
|
-
index,
|
|
2618
|
-
// The fallback breakpoint with wildcard query will always match
|
|
2619
|
-
isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
|
|
2620
|
-
}));
|
|
2621
|
-
// Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
|
|
2622
|
-
const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
|
|
2623
|
-
return mostSpecificIndex ?? fallbackBreakpointIndex;
|
|
2624
|
-
};
|
|
2625
|
-
const getFallbackBreakpointIndex = (breakpoints) => {
|
|
2626
|
-
// We assume that there will be a single breakpoint which uses the wildcard query.
|
|
2627
|
-
// If there is none, we just take the first one in the list.
|
|
2628
|
-
return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
|
|
2629
|
-
};
|
|
2630
|
-
const builtInStylesWithDesignTokens = [
|
|
2631
|
-
'cfMargin',
|
|
2632
|
-
'cfPadding',
|
|
2633
|
-
'cfGap',
|
|
2634
|
-
'cfWidth',
|
|
2635
|
-
'cfHeight',
|
|
2636
|
-
'cfBackgroundColor',
|
|
2637
|
-
'cfBorder',
|
|
2638
|
-
'cfBorderRadius',
|
|
2639
|
-
'cfFontSize',
|
|
2640
|
-
'cfLineHeight',
|
|
2641
|
-
'cfLetterSpacing',
|
|
2642
|
-
'cfTextColor',
|
|
2643
|
-
'cfMaxWidth',
|
|
2644
|
-
];
|
|
2645
|
-
const isValidBreakpointValue = (value) => {
|
|
2646
|
-
return value !== undefined && value !== null && value !== '';
|
|
2647
|
-
};
|
|
2648
|
-
const getValueForBreakpoint = (valuesByBreakpoint, breakpoints, activeBreakpointIndex, fallbackBreakpointIndex, variableName, resolveDesignTokens = true) => {
|
|
2649
|
-
const eventuallyResolveDesignTokens = (value) => {
|
|
2650
|
-
// For some built-in design properties, we support design tokens
|
|
2651
|
-
if (builtInStylesWithDesignTokens.includes(variableName)) {
|
|
2652
|
-
return getDesignTokenRegistration(value, variableName);
|
|
2653
|
-
}
|
|
2654
|
-
// For all other properties, we just return the breakpoint-specific value
|
|
2655
|
-
return value;
|
|
2656
|
-
};
|
|
2657
|
-
if (valuesByBreakpoint instanceof Object) {
|
|
2658
|
-
// Assume that the values are sorted by media query to apply the cascading CSS logic
|
|
2659
|
-
for (let index = activeBreakpointIndex; index >= 0; index--) {
|
|
2660
|
-
const breakpointId = breakpoints[index]?.id;
|
|
2661
|
-
if (isValidBreakpointValue(valuesByBreakpoint[breakpointId])) {
|
|
2662
|
-
// If the value is defined, we use it and stop the breakpoints cascade
|
|
2663
|
-
if (resolveDesignTokens) {
|
|
2664
|
-
return eventuallyResolveDesignTokens(valuesByBreakpoint[breakpointId]);
|
|
2665
|
-
}
|
|
2666
|
-
return valuesByBreakpoint[breakpointId];
|
|
2667
|
-
}
|
|
2872
|
+
const getDataFromTree = (tree) => {
|
|
2873
|
+
let dataSource = {};
|
|
2874
|
+
let unboundValues = {};
|
|
2875
|
+
const queue = [...tree.root.children];
|
|
2876
|
+
while (queue.length) {
|
|
2877
|
+
const node = queue.shift();
|
|
2878
|
+
if (!node) {
|
|
2879
|
+
continue;
|
|
2668
2880
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
}
|
|
2674
|
-
return valuesByBreakpoint[fallbackBreakpointId];
|
|
2881
|
+
dataSource = { ...dataSource, ...node.data.dataSource };
|
|
2882
|
+
unboundValues = { ...unboundValues, ...node.data.unboundValues };
|
|
2883
|
+
if (node.children.length) {
|
|
2884
|
+
queue.push(...node.children);
|
|
2675
2885
|
}
|
|
2676
2886
|
}
|
|
2677
|
-
else {
|
|
2678
|
-
// Old design properties did not support breakpoints, keep for backward compatibility
|
|
2679
|
-
return valuesByBreakpoint;
|
|
2680
|
-
}
|
|
2681
|
-
};
|
|
2682
|
-
|
|
2683
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2684
|
-
const isLinkToAsset = (variable) => {
|
|
2685
|
-
if (!variable)
|
|
2686
|
-
return false;
|
|
2687
|
-
if (typeof variable !== 'object')
|
|
2688
|
-
return false;
|
|
2689
|
-
return (variable.sys?.linkType === 'Asset' &&
|
|
2690
|
-
typeof variable.sys?.id === 'string' &&
|
|
2691
|
-
!!variable.sys?.id &&
|
|
2692
|
-
variable.sys?.type === 'Link');
|
|
2693
|
-
};
|
|
2694
|
-
|
|
2695
|
-
const isLink = (maybeLink) => {
|
|
2696
|
-
if (maybeLink === null)
|
|
2697
|
-
return false;
|
|
2698
|
-
if (typeof maybeLink !== 'object')
|
|
2699
|
-
return false;
|
|
2700
|
-
const link = maybeLink;
|
|
2701
|
-
return Boolean(link.sys?.id) && link.sys?.type === 'Link';
|
|
2702
|
-
};
|
|
2703
|
-
|
|
2704
|
-
/**
|
|
2705
|
-
* This module encapsulates format of the path to a deep reference.
|
|
2706
|
-
*/
|
|
2707
|
-
const parseDataSourcePathIntoFieldset = (path) => {
|
|
2708
|
-
const parsedPath = parseDeepPath(path);
|
|
2709
|
-
if (null === parsedPath) {
|
|
2710
|
-
throw new Error(`Cannot parse path '${path}' as deep path`);
|
|
2711
|
-
}
|
|
2712
|
-
return parsedPath.fields.map((field) => [null, field, '~locale']);
|
|
2713
|
-
};
|
|
2714
|
-
/**
|
|
2715
|
-
* Parse path into components, supports L1 references (one reference follow) atm.
|
|
2716
|
-
* @param path from data source. eg. `/uuid123/fields/image/~locale/fields/file/~locale`
|
|
2717
|
-
* eg. `/uuid123/fields/file/~locale/fields/title/~locale`
|
|
2718
|
-
* @returns
|
|
2719
|
-
*/
|
|
2720
|
-
const parseDataSourcePathWithL1DeepBindings = (path) => {
|
|
2721
|
-
const parsedPath = parseDeepPath(path);
|
|
2722
|
-
if (null === parsedPath) {
|
|
2723
|
-
throw new Error(`Cannot parse path '${path}' as deep path`);
|
|
2724
|
-
}
|
|
2725
2887
|
return {
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
referentField: parsedPath.fields[1],
|
|
2888
|
+
dataSource,
|
|
2889
|
+
unboundValues,
|
|
2729
2890
|
};
|
|
2730
2891
|
};
|
|
2731
2892
|
/**
|
|
2732
|
-
*
|
|
2733
|
-
*
|
|
2734
|
-
* or regular, like:
|
|
2735
|
-
* - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
|
|
2736
|
-
* @returns
|
|
2893
|
+
* Gets calculates the index to drop the dragged component based on the mouse position
|
|
2894
|
+
* @returns {InsertionData} a object containing a node that will become a parent for dragged component and index at which it must be inserted
|
|
2737
2895
|
*/
|
|
2738
|
-
const
|
|
2739
|
-
const
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
// ['', 'uuid123', 'fields', 'mainStory', '~locale', 'fields', 'cover', '~locale', 'fields', 'title', '~locale']
|
|
2751
|
-
// Then group segments into intermediate represenatation - chunks, where each non-initial chunk starts with 'fields'
|
|
2752
|
-
// [
|
|
2753
|
-
// [ "", "uuid123" ],
|
|
2754
|
-
// [ "fields", "mainStory", "~locale" ],
|
|
2755
|
-
// [ "fields", "cover", "~locale" ],
|
|
2756
|
-
// [ "fields", "title", "~locale" ]
|
|
2757
|
-
// ]
|
|
2758
|
-
// Then check "initial" chunk for corretness
|
|
2759
|
-
// Then check all "field-leading" chunks for correctness
|
|
2760
|
-
const isValidInitialChunk = (initialChunk) => {
|
|
2761
|
-
// must have start with '' and have at least 2 segments, second non-empty
|
|
2762
|
-
// eg. /-_432uuid123123
|
|
2763
|
-
return /^\/([^/^~]+)$/.test(initialChunk.join('/'));
|
|
2764
|
-
};
|
|
2765
|
-
const isValidFieldChunk = (fieldChunk) => {
|
|
2766
|
-
// must start with 'fields' and have at least 3 segments, second non-empty and last segment must be '~locale'
|
|
2767
|
-
// eg. fields/-32234mainStory/~locale
|
|
2768
|
-
return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
|
|
2769
|
-
};
|
|
2770
|
-
const deepPathSegments = deepPathCandidate.split('/');
|
|
2771
|
-
const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
|
|
2772
|
-
if (chunks.length <= 1) {
|
|
2773
|
-
return null; // malformed path, even regular paths have at least 2 chunks
|
|
2896
|
+
const getInsertionData = ({ dropReceiverParentNode, dropReceiverNode, flexDirection, isMouseAtTopBorder, isMouseAtBottomBorder, isMouseInLeftHalf, isMouseInUpperHalf, isOverTopIndicator, isOverBottomIndicator, }) => {
|
|
2897
|
+
const APPEND_INSIDE = dropReceiverNode.children.length;
|
|
2898
|
+
const PREPEND_INSIDE = 0;
|
|
2899
|
+
if (isMouseAtTopBorder || isMouseAtBottomBorder) {
|
|
2900
|
+
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2901
|
+
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2902
|
+
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
2903
|
+
return {
|
|
2904
|
+
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2905
|
+
node: dropReceiverParentNode,
|
|
2906
|
+
index: isMouseAtBottomBorder ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2907
|
+
};
|
|
2774
2908
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2909
|
+
// if over one of the section indicators
|
|
2910
|
+
if (isOverTopIndicator || isOverBottomIndicator) {
|
|
2911
|
+
const indexOfSectionInParentChildren = dropReceiverParentNode.children.findIndex((n) => n.data.id === dropReceiverNode.data.id);
|
|
2912
|
+
const APPEND_OUTSIDE = indexOfSectionInParentChildren + 1;
|
|
2913
|
+
const PREPEND_OUTSIDE = indexOfSectionInParentChildren;
|
|
2914
|
+
return {
|
|
2915
|
+
// when the mouse is around the border we want to drop the new component as a new section onto the root node
|
|
2916
|
+
node: dropReceiverParentNode,
|
|
2917
|
+
index: isOverBottomIndicator ? APPEND_OUTSIDE : PREPEND_OUTSIDE,
|
|
2918
|
+
};
|
|
2777
2919
|
}
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2920
|
+
if (flexDirection === undefined || flexDirection === 'row') {
|
|
2921
|
+
return {
|
|
2922
|
+
node: dropReceiverNode,
|
|
2923
|
+
index: isMouseInLeftHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
2924
|
+
};
|
|
2782
2925
|
}
|
|
2783
|
-
|
|
2784
|
-
return
|
|
2926
|
+
else {
|
|
2927
|
+
return {
|
|
2928
|
+
node: dropReceiverNode,
|
|
2929
|
+
index: isMouseInUpperHalf ? PREPEND_INSIDE : APPEND_INSIDE,
|
|
2930
|
+
};
|
|
2785
2931
|
}
|
|
2786
|
-
return {
|
|
2787
|
-
key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
|
|
2788
|
-
fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
|
|
2789
|
-
};
|
|
2790
2932
|
};
|
|
2791
|
-
const
|
|
2792
|
-
const
|
|
2793
|
-
|
|
2794
|
-
const
|
|
2795
|
-
const
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
const segment = segments[i];
|
|
2799
|
-
if (isInitialElement) {
|
|
2800
|
-
currentChunk = [segment];
|
|
2801
|
-
}
|
|
2802
|
-
else if (isSegmentBeginningOfChunk(segment)) {
|
|
2803
|
-
chunks.push(currentChunk);
|
|
2804
|
-
currentChunk = [segment];
|
|
2805
|
-
}
|
|
2806
|
-
else {
|
|
2807
|
-
currentChunk.push(segment);
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
chunks.push(currentChunk);
|
|
2811
|
-
return chunks.filter(excludeEmptyChunks);
|
|
2933
|
+
const generateRandomId = (letterCount) => {
|
|
2934
|
+
const LETTERS = 'abcdefghijklmnopqvwxyzABCDEFGHIJKLMNOPQVWXYZ';
|
|
2935
|
+
const NUMS = '0123456789';
|
|
2936
|
+
const ALNUM = NUMS + LETTERS;
|
|
2937
|
+
const times = (n, callback) => Array.from({ length: n }, callback);
|
|
2938
|
+
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
2939
|
+
return times(letterCount, () => ALNUM[random(0, ALNUM.length - 1)]).join('');
|
|
2812
2940
|
};
|
|
2813
|
-
const
|
|
2814
|
-
|
|
2815
|
-
// ['', 'key123', 'fields', 'featureImage', '~locale', 'fields', 'file', '~locale']
|
|
2816
|
-
const segments = path.split('/');
|
|
2817
|
-
if (segments.length < 2) {
|
|
2818
|
-
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.`);
|
|
2941
|
+
const checkIsAssemblyNode = ({ componentId, usedComponents, }) => {
|
|
2942
|
+
if (!usedComponents?.length)
|
|
2819
2943
|
return false;
|
|
2820
|
-
|
|
2821
|
-
const secondLast = segments[segments.length - 2]; // skipping trailing '~locale'
|
|
2822
|
-
return secondLast === expectedName;
|
|
2944
|
+
return usedComponents.some((usedComponent) => usedComponent.sys.id === componentId);
|
|
2823
2945
|
};
|
|
2824
|
-
|
|
2825
|
-
const
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
return buildTemplate({ template: pattern, context: variables });
|
|
2946
|
+
/** @deprecated use `checkIsAssemblyNode` instead. Will be removed with SDK v5. */
|
|
2947
|
+
const checkIsAssembly = checkIsAssemblyNode;
|
|
2948
|
+
/**
|
|
2949
|
+
* This check assumes that the entry is already ensured to be an experience, i.e. the
|
|
2950
|
+
* content type of the entry is an experience type with the necessary annotations.
|
|
2951
|
+
**/
|
|
2952
|
+
const checkIsAssemblyEntry = (entry) => {
|
|
2953
|
+
return Boolean(entry.fields?.componentSettings);
|
|
2833
2954
|
};
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
const fieldsIndex = str.indexOf(fieldsIndicator);
|
|
2844
|
-
if (fieldsIndex !== -1) {
|
|
2845
|
-
const dotIndex = str.indexOf('.', fieldsIndex + fieldsIndicator.length + 1); // +1 for '.'
|
|
2846
|
-
if (dotIndex !== -1) {
|
|
2847
|
-
return str.slice(0, dotIndex + 1) + locale + '.' + str.slice(dotIndex + 1);
|
|
2848
|
-
}
|
|
2955
|
+
const checkIsAssemblyDefinition = (component) => component?.category === ASSEMBLY_DEFAULT_CATEGORY;
|
|
2956
|
+
function parseCSSValue(input) {
|
|
2957
|
+
const regex = /^(\d+(\.\d+)?)(px|em|rem)$/;
|
|
2958
|
+
const match = input.match(regex);
|
|
2959
|
+
if (match) {
|
|
2960
|
+
return {
|
|
2961
|
+
value: parseFloat(match[1]),
|
|
2962
|
+
unit: match[3],
|
|
2963
|
+
};
|
|
2849
2964
|
}
|
|
2850
|
-
return
|
|
2851
|
-
}
|
|
2852
|
-
function getTemplateValue(ctx, path) {
|
|
2853
|
-
const pathWithLocale = addLocale(path, ctx.locale);
|
|
2854
|
-
const retrievedValue = getValue(ctx, pathWithLocale);
|
|
2855
|
-
return typeof retrievedValue === 'object' && retrievedValue !== null
|
|
2856
|
-
? retrievedValue[ctx.locale]
|
|
2857
|
-
: retrievedValue;
|
|
2965
|
+
return null;
|
|
2858
2966
|
}
|
|
2859
|
-
function
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
// using _.result didn't gave proper results so we run our own version of it
|
|
2871
|
-
return String(typeof value === 'function' ? value() : value);
|
|
2872
|
-
}));
|
|
2967
|
+
function getTargetValueInPixels(targetWidthObject) {
|
|
2968
|
+
switch (targetWidthObject.unit) {
|
|
2969
|
+
case 'px':
|
|
2970
|
+
return targetWidthObject.value;
|
|
2971
|
+
case 'em':
|
|
2972
|
+
return targetWidthObject.value * 16;
|
|
2973
|
+
case 'rem':
|
|
2974
|
+
return targetWidthObject.value * 16;
|
|
2975
|
+
default:
|
|
2976
|
+
return targetWidthObject.value;
|
|
2977
|
+
}
|
|
2873
2978
|
}
|
|
2874
2979
|
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
const sanitizeNodeProps = (nodeProps) => {
|
|
2879
|
-
return omit(nodeProps, stylesToRemove, propsToRemove);
|
|
2880
|
-
};
|
|
2881
|
-
|
|
2882
|
-
const CF_DEBUG_KEY = 'cf_debug';
|
|
2883
|
-
class DebugLogger {
|
|
2884
|
-
constructor() {
|
|
2885
|
-
// Public methods for logging
|
|
2886
|
-
this.error = this.logger('error');
|
|
2887
|
-
this.warn = this.logger('warn');
|
|
2888
|
-
this.log = this.logger('log');
|
|
2889
|
-
this.debug = this.logger('debug');
|
|
2890
|
-
if (typeof localStorage === 'undefined') {
|
|
2891
|
-
this.enabled = false;
|
|
2892
|
-
return;
|
|
2893
|
-
}
|
|
2894
|
-
// Default to checking localStorage for the debug mode on initialization if in browser
|
|
2895
|
-
this.enabled = localStorage.getItem(CF_DEBUG_KEY) === 'true';
|
|
2980
|
+
class ParseError extends Error {
|
|
2981
|
+
constructor(message) {
|
|
2982
|
+
super(message);
|
|
2896
2983
|
}
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2984
|
+
}
|
|
2985
|
+
const isValidJsonObject = (s) => {
|
|
2986
|
+
try {
|
|
2987
|
+
const result = JSON.parse(s);
|
|
2988
|
+
if ('object' !== typeof result) {
|
|
2989
|
+
return false;
|
|
2900
2990
|
}
|
|
2901
|
-
return
|
|
2991
|
+
return true;
|
|
2902
2992
|
}
|
|
2903
|
-
|
|
2904
|
-
return
|
|
2993
|
+
catch (e) {
|
|
2994
|
+
return false;
|
|
2905
2995
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
localStorage.removeItem(CF_DEBUG_KEY);
|
|
2996
|
+
};
|
|
2997
|
+
const doesMismatchMessageSchema = (event) => {
|
|
2998
|
+
try {
|
|
2999
|
+
tryParseMessage(event);
|
|
3000
|
+
return false;
|
|
3001
|
+
}
|
|
3002
|
+
catch (e) {
|
|
3003
|
+
if (e instanceof ParseError) {
|
|
3004
|
+
return e.message;
|
|
2916
3005
|
}
|
|
3006
|
+
throw e;
|
|
2917
3007
|
}
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
console[level]('[cf-experiences-sdk]', ...args);
|
|
2923
|
-
}
|
|
2924
|
-
};
|
|
3008
|
+
};
|
|
3009
|
+
const tryParseMessage = (event) => {
|
|
3010
|
+
if (!event.data) {
|
|
3011
|
+
throw new ParseError('Field event.data is missing');
|
|
2925
3012
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
3013
|
+
if ('string' !== typeof event.data) {
|
|
3014
|
+
throw new ParseError(`Field event.data must be a string, instead of '${typeof event.data}'`);
|
|
3015
|
+
}
|
|
3016
|
+
if (!isValidJsonObject(event.data)) {
|
|
3017
|
+
throw new ParseError('Field event.data must be a valid JSON object serialized as string');
|
|
3018
|
+
}
|
|
3019
|
+
const eventData = JSON.parse(event.data);
|
|
3020
|
+
if (!eventData.source) {
|
|
3021
|
+
throw new ParseError(`Field eventData.source must be equal to 'composability-app'`);
|
|
3022
|
+
}
|
|
3023
|
+
if ('composability-app' !== eventData.source) {
|
|
3024
|
+
throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
|
|
3025
|
+
}
|
|
3026
|
+
// check eventData.eventType
|
|
3027
|
+
const supportedEventTypes = Object.values(INCOMING_EVENTS);
|
|
3028
|
+
if (!supportedEventTypes.includes(eventData.eventType)) {
|
|
3029
|
+
// Expected message: This message is handled in the EntityStore to store fetched entities
|
|
3030
|
+
if (eventData.eventType !== PostMessageMethods.REQUESTED_ENTITIES) {
|
|
3031
|
+
throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
return eventData;
|
|
2932
3035
|
};
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
|
|
3036
|
+
const validateExperienceBuilderConfig = ({ locale, mode, }) => {
|
|
3037
|
+
if (mode === StudioCanvasMode.EDITOR || mode === StudioCanvasMode.READ_ONLY) {
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
if (!locale) {
|
|
3041
|
+
throw new Error('Parameter "locale" is required for experience builder initialization outside of editor mode');
|
|
3042
|
+
}
|
|
2936
3043
|
};
|
|
2937
3044
|
|
|
2938
3045
|
const sendMessage = (eventType, data) => {
|
|
@@ -3372,7 +3479,7 @@ class EntityStore extends EntityStoreBase {
|
|
|
3372
3479
|
else {
|
|
3373
3480
|
const { experienceEntry, entities, locale } = options;
|
|
3374
3481
|
if (!isExperienceEntry(experienceEntry)) {
|
|
3375
|
-
throw new Error('Provided entry is not experience entry');
|
|
3482
|
+
throw new Error('Provided entry is not an experience entry');
|
|
3376
3483
|
}
|
|
3377
3484
|
super({ entities, locale });
|
|
3378
3485
|
this._experienceEntryFields = experienceEntry.fields;
|
|
@@ -3454,8 +3561,11 @@ function createExperience(options) {
|
|
|
3454
3561
|
}
|
|
3455
3562
|
else {
|
|
3456
3563
|
const { experienceEntry, referencedAssets, referencedEntries, locale } = options;
|
|
3564
|
+
if ([experienceEntry, ...referencedAssets, ...referencedEntries].some(isNotLocalized)) {
|
|
3565
|
+
throw new Error('Some of the provided content is not localized. Please localize every entity before passing it to this function. Note that this is solely determined by `sys.locale` being set respectively.');
|
|
3566
|
+
}
|
|
3457
3567
|
if (!isExperienceEntry(experienceEntry)) {
|
|
3458
|
-
throw new Error('Provided entry is not experience entry');
|
|
3568
|
+
throw new Error('Provided entry is not an experience entry');
|
|
3459
3569
|
}
|
|
3460
3570
|
const entityStore = new EntityStore({
|
|
3461
3571
|
experienceEntry,
|
|
@@ -3467,21 +3577,33 @@ function createExperience(options) {
|
|
|
3467
3577
|
};
|
|
3468
3578
|
}
|
|
3469
3579
|
}
|
|
3580
|
+
// Following the API shape, we check the `sys.locale` property as we can't rely on the shape of
|
|
3581
|
+
// fields to determine whether it's localized or not.
|
|
3582
|
+
// See CDA documentation mentioning it here: https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/common-resource-attributes
|
|
3583
|
+
const isNotLocalized = (entity) => {
|
|
3584
|
+
return !entity.sys.locale;
|
|
3585
|
+
};
|
|
3470
3586
|
|
|
3587
|
+
/**
|
|
3588
|
+
* Fetches an experience entry by its slug or id. Throws an error if there are multiple
|
|
3589
|
+
* entries with the same slug. Additionally, it resolves all nested pattern entries inside `fields.usedComponents`.
|
|
3590
|
+
* @param options.client - Instantiated client from the Contentful SDK. If this is using the `withAllLocales` modifier, you may not provide a specific locale.
|
|
3591
|
+
* @param options.locale - Provide a locale if your experience contains custom localized fields. Otherwise, it will fallback to the default locale.
|
|
3592
|
+
* @param options.experienceTypeId - id of the content type associated with the experience
|
|
3593
|
+
* @param options.identifier - identifying condition to find the correct experience entry
|
|
3594
|
+
*
|
|
3595
|
+
*/
|
|
3471
3596
|
const fetchExperienceEntry = async ({ client, experienceTypeId, locale, identifier, }) => {
|
|
3472
3597
|
if (!client) {
|
|
3473
3598
|
throw new Error('Failed to fetch experience entities. Required "client" parameter was not provided');
|
|
3474
3599
|
}
|
|
3475
|
-
if (!locale) {
|
|
3476
|
-
throw new Error('Failed to fetch experience entities. Required "locale" parameter was not provided');
|
|
3477
|
-
}
|
|
3478
3600
|
if (!experienceTypeId) {
|
|
3479
3601
|
throw new Error('Failed to fetch experience entities. Required "experienceTypeId" parameter was not provided');
|
|
3480
3602
|
}
|
|
3481
|
-
if (!
|
|
3603
|
+
if (!('slug' in identifier) && !('id' in identifier)) {
|
|
3482
3604
|
throw new Error(`Failed to fetch experience entities. At least one identifier must be provided. Received: ${JSON.stringify(identifier)}`);
|
|
3483
3605
|
}
|
|
3484
|
-
const filter =
|
|
3606
|
+
const filter = 'slug' in identifier ? { 'fields.slug': identifier.slug } : { 'sys.id': identifier.id };
|
|
3485
3607
|
const entries = await client.getEntries({
|
|
3486
3608
|
content_type: experienceTypeId,
|
|
3487
3609
|
locale,
|
|
@@ -3746,13 +3868,18 @@ const fetchAllAssets = async ({ client, ids, locale, skip = 0, limit = 100, resp
|
|
|
3746
3868
|
}
|
|
3747
3869
|
};
|
|
3748
3870
|
|
|
3871
|
+
/**
|
|
3872
|
+
* Fetches all entries and assets from the `dataSource` of the given experience entry. This will
|
|
3873
|
+
* also consider deep references that are not listed explicitly but linked through deep binding paths.
|
|
3874
|
+
* @param options.client - Instantiated client from the Contentful SDK. If this is using the `withAllLocales` modifier, you may not provide a specific locale.
|
|
3875
|
+
* @param options.experienceEntry - Localized experience entry. To localize a multi locale entry, use the `localizeEntity` function.
|
|
3876
|
+
* @param options.locale - Retrieve a specific localized version of the referenced entities. Otherwise, it will fallback to the default locale.
|
|
3877
|
+
* @returns object with a list of `entries` and a list of `assets`
|
|
3878
|
+
*/
|
|
3749
3879
|
const fetchReferencedEntities = async ({ client, experienceEntry, locale, }) => {
|
|
3750
3880
|
if (!client) {
|
|
3751
3881
|
throw new Error('Failed to fetch experience entities. Required "client" parameter was not provided');
|
|
3752
3882
|
}
|
|
3753
|
-
if (!locale) {
|
|
3754
|
-
throw new Error('Failed to fetch experience entities. Required "locale" parameter was not provided');
|
|
3755
|
-
}
|
|
3756
3883
|
if (!isExperienceEntry(experienceEntry)) {
|
|
3757
3884
|
throw new Error('Failed to fetch experience entities. Provided "experienceEntry" does not match experience entry schema');
|
|
3758
3885
|
}
|
|
@@ -3864,7 +3991,8 @@ const handleError$1 = (generalMessage, error) => {
|
|
|
3864
3991
|
throw Error(message);
|
|
3865
3992
|
};
|
|
3866
3993
|
/**
|
|
3867
|
-
* Fetches an experience
|
|
3994
|
+
* Fetches an experience entry by its slug and additionally fetches all its references to return
|
|
3995
|
+
* an initilized experience instance.
|
|
3868
3996
|
* @param {FetchBySlugParams} options - options to fetch the experience
|
|
3869
3997
|
*/
|
|
3870
3998
|
async function fetchBySlug({ client, experienceTypeId, slug, localeCode, isEditorMode, }) {
|
|
@@ -3872,6 +4000,9 @@ async function fetchBySlug({ client, experienceTypeId, slug, localeCode, isEdito
|
|
|
3872
4000
|
if (isEditorMode)
|
|
3873
4001
|
return;
|
|
3874
4002
|
let experienceEntry = undefined;
|
|
4003
|
+
if (!localeCode) {
|
|
4004
|
+
throw new Error('Failed to fetch by slug. Required "localeCode" parameter was not provided');
|
|
4005
|
+
}
|
|
3875
4006
|
try {
|
|
3876
4007
|
experienceEntry = await fetchExperienceEntry({
|
|
3877
4008
|
client,
|
|
@@ -3918,7 +4049,8 @@ const handleError = (generalMessage, error) => {
|
|
|
3918
4049
|
throw Error(message);
|
|
3919
4050
|
};
|
|
3920
4051
|
/**
|
|
3921
|
-
* Fetches an experience
|
|
4052
|
+
* Fetches an experience entry by its id and additionally fetches all its references to return
|
|
4053
|
+
* an initilized experience instance.
|
|
3922
4054
|
* @param {FetchByIdParams} options - options to fetch the experience
|
|
3923
4055
|
*/
|
|
3924
4056
|
async function fetchById({ client, experienceTypeId, id, localeCode, isEditorMode, }) {
|
|
@@ -3926,6 +4058,9 @@ async function fetchById({ client, experienceTypeId, id, localeCode, isEditorMod
|
|
|
3926
4058
|
if (isEditorMode)
|
|
3927
4059
|
return;
|
|
3928
4060
|
let experienceEntry = undefined;
|
|
4061
|
+
if (!localeCode) {
|
|
4062
|
+
throw new Error('Failed to fetch by id. Required "localeCode" parameter was not provided');
|
|
4063
|
+
}
|
|
3929
4064
|
try {
|
|
3930
4065
|
experienceEntry = await fetchExperienceEntry({
|
|
3931
4066
|
client,
|
|
@@ -3963,5 +4098,5 @@ async function fetchById({ client, experienceTypeId, id, localeCode, isEditorMod
|
|
|
3963
4098
|
}
|
|
3964
4099
|
}
|
|
3965
4100
|
|
|
3966
|
-
export { DebugLogger, DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, addMinHeightForEmptyStructures, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, containerBuiltInStyles, createExperience, debug, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, disableDebug, dividerBuiltInStyles, doesMismatchMessageSchema, enableDebug, fetchAllAssets, fetchAllEntries, fetchById, fetchBySlug, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getInsertionData, getTargetValueInPixels, getTemplateValue, getValueForBreakpoint, indexByBreakpoint, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isDeepPath, isExperienceEntry, isLink, isLinkToAsset, isPatternComponent, isStructureWithRelativeHeight, isValidBreakpointValue, lastPathNamedSegmentEq, maybePopulateDesignTokenValue, mediaQueryMatcher, optionalBuiltInStyles, parseCSSValue, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sanitizeNodeProps, sectionBuiltInStyles, sendMessage, singleColumnBuiltInStyles, stringifyCssProperties, toCSSAttribute, toMediaQuery, transformBoundContentValue, tryParseMessage, validateExperienceBuilderConfig };
|
|
4101
|
+
export { DebugLogger, DeepReference, EditorModeEntityStore, EntityStore, EntityStoreBase, MEDIA_QUERY_REGEXP, VisualEditorMode, addLocale, addMinHeightForEmptyStructures, breakpointsRegistry, buildCfStyles, buildStyleTag, buildTemplate, builtInStyles, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyDefinition, checkIsAssemblyEntry, checkIsAssemblyNode, columnsBuiltInStyles, containerBuiltInStyles, createExperience, debug, defineBreakpoints, defineDesignTokens, designTokensRegistry, detachExperienceStyles, disableDebug, dividerBuiltInStyles, doesMismatchMessageSchema, enableDebug, fetchAllAssets, fetchAllEntries, fetchById, fetchBySlug, fetchExperienceEntry, fetchReferencedEntities, findOutermostCoordinates, flattenDesignTokenRegistry, gatherDeepReferencesFromExperienceEntry, gatherDeepReferencesFromTree, generateRandomId, getActiveBreakpointIndex, getBreakpointRegistration, getDataFromTree, getDesignTokenRegistration, getElementCoordinates, getFallbackBreakpointIndex, getInsertionData, getTargetValueInPixels, getTemplateValue, getValueForBreakpoint, indexByBreakpoint, isCfStyleAttribute, isComponentAllowedOnRoot, isContentfulComponent, isContentfulStructureComponent, isDeepPath, isExperienceEntry, isLink, isLinkToAsset, isPatternComponent, isStructureWithRelativeHeight, isValidBreakpointValue, lastPathNamedSegmentEq, localizeEntity, maybePopulateDesignTokenValue, mediaQueryMatcher, optionalBuiltInStyles, parseCSSValue, parseDataSourcePathIntoFieldset, parseDataSourcePathWithL1DeepBindings, resetBreakpointsRegistry, resetDesignTokenRegistry, resolveBackgroundImageBinding, resolveHyperlinkPattern, runBreakpointsValidation, sanitizeNodeProps, sectionBuiltInStyles, sendMessage, singleColumnBuiltInStyles, stringifyCssProperties, toCSSAttribute, toMediaQuery, transformBoundContentValue, tryParseMessage, validateExperienceBuilderConfig };
|
|
3967
4102
|
//# sourceMappingURL=index.js.map
|