@magento/pagebuilder 7.1.0 → 7.2.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.
Files changed (48) hide show
  1. package/lib/ContentTypes/Banner/__tests__/__snapshots__/banner.shimmer.spec.js.snap +0 -1
  2. package/lib/ContentTypes/Banner/__tests__/__snapshots__/banner.spec.js.snap +0 -1
  3. package/lib/ContentTypes/Banner/banner.js +23 -4
  4. package/lib/ContentTypes/Banner/banner.shimmer.js +13 -2
  5. package/lib/ContentTypes/Banner/configAggregator.js +4 -2
  6. package/lib/ContentTypes/Column/column.js +13 -2
  7. package/lib/ContentTypes/Column/configAggregator.js +3 -1
  8. package/lib/ContentTypes/DynamicBlock/__tests__/__snapshots__/dynamicBlock.ce.spec.js.snap +3 -0
  9. package/lib/ContentTypes/DynamicBlock/__tests__/__snapshots__/dynamicBlock.ee.spec.js.snap +93 -0
  10. package/lib/ContentTypes/DynamicBlock/__tests__/__snapshots__/dynamicBlock.shimmer.spec.js.snap +39 -0
  11. package/lib/ContentTypes/DynamicBlock/__tests__/configAggregator.spec.js +49 -0
  12. package/lib/ContentTypes/DynamicBlock/__tests__/dynamicBlock.ce.spec.js +17 -0
  13. package/lib/ContentTypes/DynamicBlock/__tests__/dynamicBlock.ee.spec.js +70 -0
  14. package/lib/ContentTypes/DynamicBlock/__tests__/dynamicBlock.shimmer.spec.js +20 -0
  15. package/lib/ContentTypes/DynamicBlock/configAggregator.js +25 -0
  16. package/lib/ContentTypes/DynamicBlock/dynamicBlock.ce.js +5 -0
  17. package/lib/ContentTypes/DynamicBlock/dynamicBlock.ee.js +134 -0
  18. package/lib/ContentTypes/DynamicBlock/dynamicBlock.module.css +8 -0
  19. package/lib/ContentTypes/DynamicBlock/dynamicBlock.shimmer.js +134 -0
  20. package/lib/ContentTypes/DynamicBlock/dynamicBlock.shimmer.module.css +26 -0
  21. package/lib/ContentTypes/DynamicBlock/index.js +2 -0
  22. package/lib/ContentTypes/Image/__tests__/__snapshots__/image.shimmer.spec.js.snap +139 -0
  23. package/lib/ContentTypes/Image/__tests__/configAggregator.spec.js +28 -10
  24. package/lib/ContentTypes/Image/__tests__/image.shimmer.spec.js +122 -0
  25. package/lib/ContentTypes/Image/__tests__/image.spec.js +5 -5
  26. package/lib/ContentTypes/Image/configAggregator.js +51 -18
  27. package/lib/ContentTypes/Image/image.js +25 -9
  28. package/lib/ContentTypes/Image/image.module.css +2 -0
  29. package/lib/ContentTypes/Image/image.shimmer.js +191 -0
  30. package/lib/ContentTypes/Image/image.shimmer.module.css +5 -0
  31. package/lib/ContentTypes/Image/index.js +1 -0
  32. package/lib/ContentTypes/Row/__tests__/configAggregator.spec.js +18 -0
  33. package/lib/ContentTypes/Row/__tests__/row.spec.js +2 -1
  34. package/lib/ContentTypes/Row/configAggregator.js +13 -3
  35. package/lib/ContentTypes/Row/row.js +26 -3
  36. package/lib/ContentTypes/Slider/configAggregator.js +3 -2
  37. package/lib/ContentTypes/Slider/slider.js +21 -2
  38. package/lib/ContentTypes/Slider/slider.shimmer.js +13 -2
  39. package/lib/ContentTypes/TabItem/configAggregator.js +4 -2
  40. package/lib/ContentTypes/TabItem/tabItem.js +13 -2
  41. package/lib/ContentTypes/Tabs/configAggregator.js +4 -2
  42. package/lib/ContentTypes/Tabs/tabs.js +19 -2
  43. package/lib/__tests__/parseStorageHtml.spec.js +35 -0
  44. package/lib/__tests__/utils.spec.js +21 -1
  45. package/lib/config.js +10 -2
  46. package/lib/parseStorageHtml.js +32 -0
  47. package/lib/utils.js +49 -0
  48. package/package.json +7 -7
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
- import { arrayOf, bool, shape, string } from 'prop-types';
2
+ import { arrayOf, bool, shape, string, object } from 'prop-types';
3
3
  import defaultClasses from './slider.shimmer.module.css';
4
4
  import { useStyle } from '@magento/venia-ui/lib/classify';
5
5
  import Shimmer from '@magento/venia-ui/lib/components/Shimmer';
6
+ import { useMediaQuery } from '@magento/peregrine/lib/hooks/useMediaQuery';
6
7
 
7
8
  /**
8
9
  * Page Builder Slider Shimmer component.
@@ -25,6 +26,7 @@ const SliderShimmer = props => {
25
26
  marginRight = 0,
26
27
  marginBottom = 0,
27
28
  marginLeft = 0,
29
+ mediaQueries,
28
30
  paddingTop,
29
31
  paddingRight,
30
32
  paddingBottom,
@@ -32,8 +34,10 @@ const SliderShimmer = props => {
32
34
  cssClasses = []
33
35
  } = props;
34
36
 
37
+ const { styles: mediaQueryStyles } = useMediaQuery({ mediaQueries });
38
+
35
39
  const dynamicStyles = {
36
- minHeight,
40
+ minHeight: mediaQueryStyles?.minHeight || minHeight,
37
41
  border,
38
42
  borderWidth,
39
43
  marginTop,
@@ -86,6 +90,7 @@ const SliderShimmer = props => {
86
90
  * @property {String} marginRight CSS margin right property
87
91
  * @property {String} marginBottom CSS margin bottom property
88
92
  * @property {String} marginLeft CSS margin left property
93
+ * @property {Array} mediaQueries List of media query rules to be applied to the component
89
94
  * @property {String} paddingTop CSS padding top property
90
95
  * @property {String} paddingRight CSS padding right property
91
96
  * @property {String} paddingBottom CSS padding bottom property
@@ -105,6 +110,12 @@ SliderShimmer.propTypes = {
105
110
  marginRight: string,
106
111
  marginBottom: string,
107
112
  marginLeft: string,
113
+ mediaQueries: arrayOf(
114
+ shape({
115
+ media: string,
116
+ style: object
117
+ })
118
+ ),
108
119
  paddingTop: string,
109
120
  paddingRight: string,
110
121
  paddingBottom: string,
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  getAdvanced,
3
3
  getBackgroundImages,
4
- getVerticalAlignment
4
+ getVerticalAlignment,
5
+ getMediaQueries
5
6
  } from '../../utils';
6
7
 
7
8
  export default node => {
@@ -11,6 +12,7 @@ export default node => {
11
12
  ...getVerticalAlignment(node),
12
13
  backgroundColor: node.style.backgroundColor,
13
14
  ...getBackgroundImages(node),
14
- ...getAdvanced(node)
15
+ ...getAdvanced(node),
16
+ ...getMediaQueries(node)
15
17
  };
16
18
  };
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
  import { verticalAlignmentToFlex } from '../../utils';
3
3
  import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
4
+ import { useMediaQuery } from '@magento/peregrine/lib/hooks/useMediaQuery';
4
5
  import { useStyle } from '@magento/venia-ui/lib/classify';
5
6
  import defaultClasses from './tabItem.module.css';
6
- import { arrayOf, oneOf, shape, string } from 'prop-types';
7
+ import { arrayOf, oneOf, shape, string, object } from 'prop-types';
7
8
 
8
9
  const { matchMedia } = globalThis;
9
10
 
@@ -40,6 +41,7 @@ const TabItem = props => {
40
41
  marginRight,
41
42
  marginBottom,
42
43
  marginLeft,
44
+ mediaQueries,
43
45
  paddingTop,
44
46
  paddingRight,
45
47
  paddingBottom,
@@ -48,6 +50,8 @@ const TabItem = props => {
48
50
  cssClasses = []
49
51
  } = props;
50
52
 
53
+ const { styles: mediaQueryStyles } = useMediaQuery({ mediaQueries });
54
+
51
55
  let image = desktopImage;
52
56
  if (mobileImage && matchMedia && matchMedia('(max-width: 768px)').matches) {
53
57
  image = mobileImage;
@@ -94,7 +98,7 @@ const TabItem = props => {
94
98
 
95
99
  return (
96
100
  <div
97
- style={dynamicStyles}
101
+ style={{ ...dynamicStyles, ...mediaQueryStyles }}
98
102
  className={[cssClasses, classes.root].join(' ')}
99
103
  >
100
104
  {children}
@@ -128,6 +132,7 @@ const TabItem = props => {
128
132
  * @property {String} marginRight CSS margin right property
129
133
  * @property {String} marginBottom CSS margin bottom property
130
134
  * @property {String} marginLeft CSS margin left property
135
+ * @property {Array} mediaQueries List of media query rules to be applied to the component
131
136
  * @property {String} paddingTop CSS padding top property
132
137
  * @property {String} paddingRight CSS padding right property
133
138
  * @property {String} paddingBottom CSS padding bottom property
@@ -157,6 +162,12 @@ TabItem.propTypes = {
157
162
  marginRight: string,
158
163
  marginBottom: string,
159
164
  marginLeft: string,
165
+ mediaQueries: arrayOf(
166
+ shape({
167
+ media: string,
168
+ style: object
169
+ })
170
+ ),
160
171
  paddingTop: string,
161
172
  paddingRight: string,
162
173
  paddingBottom: string,
@@ -5,7 +5,8 @@ import {
5
5
  getMargin,
6
6
  getPadding,
7
7
  getTextAlign,
8
- getVerticalAlignment
8
+ getVerticalAlignment,
9
+ getMediaQueries
9
10
  } from '../../utils';
10
11
 
11
12
  export default node => {
@@ -33,6 +34,7 @@ export default node => {
33
34
  ...getPadding(node),
34
35
  ...getBorder(contentEl),
35
36
  ...getIsHidden(node),
36
- ...getCssClasses(node)
37
+ ...getCssClasses(node),
38
+ ...getMediaQueries(contentEl)
37
39
  };
38
40
  };
@@ -13,7 +13,8 @@ import {
13
13
  } from 'react-tabs';
14
14
  import defaultClasses from './tabs.module.css';
15
15
  import { useStyle } from '@magento/venia-ui/lib/classify';
16
- import { arrayOf, number, oneOf, shape, string } from 'prop-types';
16
+ import { useMediaQuery } from '@magento/peregrine/lib/hooks/useMediaQuery';
17
+ import { arrayOf, number, oneOf, shape, string, object } from 'prop-types';
17
18
 
18
19
  /**
19
20
  * Upper case the first letter of a string
@@ -62,6 +63,7 @@ const Tabs = props => {
62
63
  marginRight,
63
64
  marginBottom,
64
65
  marginLeft,
66
+ mediaQueries,
65
67
  paddingTop,
66
68
  paddingRight,
67
69
  paddingBottom,
@@ -70,6 +72,8 @@ const Tabs = props => {
70
72
  children
71
73
  } = props;
72
74
 
75
+ const { styles: mediaQueryStyles } = useMediaQuery({ mediaQueries });
76
+
73
77
  const handleMouseDown = useCallback(event => {
74
78
  isScrolling.current = true;
75
79
  clientX.current = event.clientX;
@@ -94,6 +98,9 @@ const Tabs = props => {
94
98
  const handleScroll = useCallback(
95
99
  event => {
96
100
  const navScrollElement = event.target;
101
+ // Sync scrollLeft
102
+ scrollX.current = event.target.scrollLeft;
103
+
97
104
  if (navScrollElement.scrollLeft > 0) {
98
105
  // If we've scrolled to the end of the scrollable element we only display a left gradient
99
106
  if (
@@ -220,7 +227,10 @@ const Tabs = props => {
220
227
  {tabHeaders}
221
228
  </TabList>
222
229
  </div>
223
- <div className={contentClass} style={contentStyles}>
230
+ <div
231
+ className={contentClass}
232
+ style={{ ...contentStyles, ...mediaQueryStyles }}
233
+ >
224
234
  {tabPanels}
225
235
  </div>
226
236
  </TabWrapper>
@@ -261,6 +271,7 @@ const Tabs = props => {
261
271
  * @property {String} marginRight CSS margin right property
262
272
  * @property {String} marginBottom CSS margin bottom property
263
273
  * @property {String} marginLeft CSS margin left property
274
+ * @property {Array} mediaQueries List of media query rules to be applied to the component
264
275
  * @property {String} paddingTop CSS padding top property
265
276
  * @property {String} paddingRight CSS padding right property
266
277
  * @property {String} paddingBottom CSS padding bottom property
@@ -298,6 +309,12 @@ Tabs.propTypes = {
298
309
  marginRight: string,
299
310
  marginBottom: string,
300
311
  marginLeft: string,
312
+ mediaQueries: arrayOf(
313
+ shape({
314
+ media: string,
315
+ style: object
316
+ })
317
+ ),
301
318
  paddingTop: string,
302
319
  paddingRight: string,
303
320
  paddingBottom: string,
@@ -75,3 +75,38 @@ test('convert to inline styles', () => {
75
75
  getElementsByTagNameSpy.mockRestore();
76
76
  setAttributeSpy.mockRestore();
77
77
  });
78
+
79
+ test('saves media query styles into data attributes', () => {
80
+ const styleSheet = new CSSStyleSheet();
81
+ styleSheet.insertRule(
82
+ '#html-body [data-pb-style=D119W07] { color: transparent }',
83
+ 0
84
+ );
85
+ styleSheet.insertRule(
86
+ '@media only screen and (max-width: 768px) { #html-body [data-pb-style=D119W07] { min-height: 100px }}',
87
+ 1
88
+ );
89
+ const getElementsByTagNameSpy = jest
90
+ .spyOn(Document.prototype, 'getElementsByTagName')
91
+ .mockReturnValueOnce([
92
+ {
93
+ sheet: styleSheet
94
+ }
95
+ ]);
96
+ const setAttributeSpy = jest
97
+ .spyOn(Element.prototype, 'setAttribute')
98
+ .mockImplementation();
99
+ parseStorageHtml(testMasterFormat);
100
+ expect(setAttributeSpy).toHaveBeenNthCalledWith(
101
+ 1,
102
+ 'data-media-0',
103
+ 'only screen and (max-width: 768px)'
104
+ );
105
+ expect(setAttributeSpy).toHaveBeenNthCalledWith(
106
+ 2,
107
+ 'data-media-style-0',
108
+ 'min-height: 100px;'
109
+ );
110
+ getElementsByTagNameSpy.mockRestore();
111
+ setAttributeSpy.mockRestore();
112
+ });
@@ -6,7 +6,8 @@ import {
6
6
  getPadding,
7
7
  getTextAlign,
8
8
  getVerticalAlignment,
9
- verticalAlignmentToFlex
9
+ verticalAlignmentToFlex,
10
+ getMediaQueries
10
11
  } from '../utils';
11
12
 
12
13
  test('can retrieve background image from node', () => {
@@ -124,3 +125,22 @@ test('can retrieve CSS classes', () => {
124
125
  node.innerHTML = '<div></div>';
125
126
  expect(getCssClasses(node.childNodes[0]).cssClasses).toEqual([]);
126
127
  });
128
+
129
+ test('can retrieve mediaQueries', () => {
130
+ const node = document.createElement('div');
131
+ node.setAttribute('data-media-0', 'only screen and (min-width: 768px)');
132
+ node.setAttribute(
133
+ 'data-media-style-0',
134
+ 'display: flex; min-height: 300px;'
135
+ );
136
+ const { mediaQueries } = getMediaQueries(node);
137
+ expect(mediaQueries).toEqual([
138
+ {
139
+ media: 'only screen and (min-width: 768px)',
140
+ style: {
141
+ display: 'flex',
142
+ minHeight: '300px'
143
+ }
144
+ }
145
+ ]);
146
+ });
package/lib/config.js CHANGED
@@ -6,7 +6,7 @@ import Column from './ContentTypes/Column';
6
6
  import columnGroupConfigAggregator from './ContentTypes/ColumnGroup/configAggregator';
7
7
  import ColumnGroup from './ContentTypes/ColumnGroup';
8
8
  import imageConfigAggregator from './ContentTypes/Image/configAggregator';
9
- import Image from './ContentTypes/Image';
9
+ import { ImageShimmer } from './ContentTypes/Image';
10
10
  import headingConfigAggregator from './ContentTypes/Heading/configAggregator';
11
11
  import Heading from './ContentTypes/Heading';
12
12
  import textConfigAggregator from './ContentTypes/Text/configAggregator';
@@ -14,6 +14,7 @@ import Text from './ContentTypes/Text';
14
14
  import tabsConfigAggregator from './ContentTypes/Tabs/configAggregator';
15
15
  import tabItemConfigAggregator from './ContentTypes/TabItem/configAggregator';
16
16
  import blockConfigAggregator from './ContentTypes/Block/configAggregator';
17
+ import dynamicBlockConfigAggregator from './ContentTypes/DynamicBlock/configAggregator';
17
18
  import productsConfigAggregator from './ContentTypes/Products/configAggregator';
18
19
  import buttonsConfigAggregator from './ContentTypes/Buttons/configAggregator';
19
20
  import buttonItemConfigAggregator from './ContentTypes/ButtonItem/configAggregator';
@@ -26,6 +27,7 @@ import { BannerShimmer } from './ContentTypes/Banner';
26
27
  import ButtonItem from './ContentTypes/ButtonItem';
27
28
  import sliderConfigAggregator from './ContentTypes/Slider/configAggregator';
28
29
  import { SliderShimmer } from './ContentTypes/Slider';
30
+ import { DynamicBlockShimmer } from './ContentTypes/DynamicBlock';
29
31
 
30
32
  /* istanbul ignore next */
31
33
  const contentTypesConfig = {
@@ -43,7 +45,8 @@ const contentTypesConfig = {
43
45
  },
44
46
  image: {
45
47
  configAggregator: imageConfigAggregator,
46
- component: Image
48
+ component: React.lazy(() => import('./ContentTypes/Image')),
49
+ componentShimmer: ImageShimmer
47
50
  },
48
51
  heading: {
49
52
  configAggregator: headingConfigAggregator,
@@ -73,6 +76,11 @@ const contentTypesConfig = {
73
76
  configAggregator: blockConfigAggregator,
74
77
  component: React.lazy(() => import('./ContentTypes/Block'))
75
78
  },
79
+ dynamic_block: {
80
+ configAggregator: dynamicBlockConfigAggregator,
81
+ component: React.lazy(() => import('./ContentTypes/DynamicBlock')),
82
+ componentShimmer: DynamicBlockShimmer
83
+ },
76
84
  products: {
77
85
  configAggregator: productsConfigAggregator,
78
86
  component: React.lazy(() => import('./ContentTypes/Products'))
@@ -84,6 +84,7 @@ const bodyId = 'html-body';
84
84
  const convertToInlineStyles = document => {
85
85
  const styleBlocks = document.getElementsByTagName('style');
86
86
  const styles = {};
87
+ const mediaStyles = {};
87
88
 
88
89
  if (styleBlocks.length > 0) {
89
90
  Array.from(styleBlocks).forEach(styleBlock => {
@@ -100,11 +101,42 @@ const convertToInlineStyles = document => {
100
101
  }
101
102
  styles[selector].push(rule.style);
102
103
  });
104
+ } else if (rule instanceof CSSMediaRule) {
105
+ Array.from(rule.media).forEach(media => {
106
+ const styles = Array.from(rule.cssRules).map(rule => {
107
+ return {
108
+ selectors: rule.selectorText
109
+ .split(',')
110
+ .map(selector => selector.trim()),
111
+ css: rule.style.cssText
112
+ };
113
+ });
114
+ mediaStyles[media] = styles;
115
+ });
103
116
  }
104
117
  });
105
118
  });
106
119
  }
107
120
 
121
+ Object.keys(mediaStyles).map((media, i) => {
122
+ mediaStyles[media].forEach(style => {
123
+ style.selectors.forEach(selector => {
124
+ const element = document.querySelector(selector);
125
+ if (element) {
126
+ element.setAttribute(`data-media-${i}`, media);
127
+ const savedStyles = element.getAttribute(
128
+ `data-media-style-${i}`
129
+ );
130
+ // avoids overwriting previously saved styles
131
+ element.setAttribute(
132
+ `data-media-style-${i}`,
133
+ `${savedStyles ? `${savedStyles} ` : ''}${style.css}`
134
+ );
135
+ }
136
+ });
137
+ });
138
+ });
139
+
108
140
  Object.keys(styles).map(selector => {
109
141
  const element = document.querySelector(selector);
110
142
  if (!element) {
package/lib/utils.js CHANGED
@@ -173,3 +173,52 @@ export function getIsHidden(node) {
173
173
  isHidden: node.style.display === 'none'
174
174
  };
175
175
  }
176
+
177
+ /**
178
+ * Converts a CSS string style into a JSX object inline style
179
+ *
180
+ * @param {String} style
181
+ * @returns {Object}
182
+ */
183
+ export function cssToJSXStyle(style) {
184
+ const toCamelCase = str => str.replace(/-(.)/g, (_, p) => p.toUpperCase());
185
+ const result = {};
186
+ style.split(';').forEach(el => {
187
+ const [prop, value] = el.split(':');
188
+ if (prop) {
189
+ result[toCamelCase(prop.trim())] = value.trim();
190
+ }
191
+ });
192
+
193
+ return result;
194
+ }
195
+
196
+ /**
197
+ * Retrieve media queries from a master format node
198
+ *
199
+ * @param node
200
+ * @param {Array} mediaQueries
201
+ *
202
+ * @returns {{mediaQueries: {media: string, style: string}}}
203
+ */
204
+ export function getMediaQueries(node) {
205
+ const response = [];
206
+ const dataset = Object.keys(node.dataset);
207
+
208
+ const medias = dataset
209
+ .filter(key => key.match(/media-/))
210
+ .map(key => node.dataset[key]);
211
+
212
+ const styles = dataset
213
+ .filter(key => key.match(/mediaStyle/))
214
+ .map(key => node.dataset[key]);
215
+
216
+ medias.forEach((media, i) => {
217
+ response.push({
218
+ media,
219
+ style: cssToJSXStyle(styles[i])
220
+ });
221
+ });
222
+
223
+ return { mediaQueries: response };
224
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magento/pagebuilder",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -34,9 +34,9 @@
34
34
  "homepage": "https://github.com/magento/pwa-studio/tree/main/packages/pagebuilder#readme",
35
35
  "dependencies": {},
36
36
  "devDependencies": {
37
- "@magento/peregrine": "~12.2.0",
38
- "@magento/pwa-buildpack": "~11.1.0",
39
- "@magento/venia-ui": "~9.2.0",
37
+ "@magento/peregrine": "~12.3.0",
38
+ "@magento/pwa-buildpack": "~11.2.0",
39
+ "@magento/venia-ui": "~9.3.0",
40
40
  "@storybook/react": "~6.3.7",
41
41
  "jarallax": "~1.11.1",
42
42
  "load-google-maps-api": "~2.0.1",
@@ -50,9 +50,9 @@
50
50
  "peerDependencies": {
51
51
  "@apollo/client": "~3.4.0",
52
52
  "@magento/babel-preset-peregrine": "~1.2.0",
53
- "@magento/peregrine": "~12.2.0",
54
- "@magento/pwa-buildpack": "~11.1.0",
55
- "@magento/venia-ui": "~9.2.0",
53
+ "@magento/peregrine": "~12.3.0",
54
+ "@magento/pwa-buildpack": "~11.2.0",
55
+ "@magento/venia-ui": "~9.3.0",
56
56
  "jarallax": "~1.11.1",
57
57
  "load-google-maps-api": "~2.0.1",
58
58
  "lodash.escape": "~4.0.1",