@salesforce/retail-react-app 7.0.0-preview.0 → 7.0.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/CHANGELOG.md +9 -8
- package/app/components/dynamic-image/index.jsx +91 -16
- package/app/components/dynamic-image/index.test.js +214 -30
- package/app/components/image/index.jsx +5 -13
- package/app/components/image/index.test.js +6 -3
- package/app/components/island/README.md +15 -10
- package/app/components/island/index.jsx +12 -5
- package/app/components/island/index.test.js +35 -0
- package/app/components/passwordless-login/index.jsx +4 -5
- package/app/components/passwordless-login/index.test.js +2 -4
- package/app/components/product-tile/index.jsx +1 -1
- package/app/components/product-view-modal/bundle.jsx +12 -2
- package/app/components/social-login/index.jsx +1 -0
- package/app/components/standard-login/index.jsx +4 -1
- package/app/constants.js +3 -0
- package/app/hooks/use-auth-modal.js +68 -67
- package/app/hooks/use-auth-modal.test.js +93 -23
- package/app/hooks/use-datacloud.js +169 -192
- package/app/hooks/use-datacloud.test.js +273 -17
- package/app/pages/cart/index.jsx +2 -1
- package/app/pages/cart/partials/cart-secondary-button-group.jsx +8 -10
- package/app/pages/cart/partials/cart-secondary-button-group.test.js +2 -3
- package/app/pages/checkout/partials/contact-info.jsx +9 -8
- package/app/pages/checkout/partials/contact-info.test.js +41 -4
- package/app/pages/checkout/partials/login-state.jsx +3 -3
- package/app/pages/home/index.test.js +2 -1
- package/app/pages/login/index.jsx +37 -37
- package/app/pages/login/index.test.js +42 -0
- package/app/pages/product-detail/index.jsx +64 -73
- package/app/pages/product-list/index.jsx +19 -9
- package/app/pages/product-list/index.test.js +153 -19
- package/app/utils/image.js +29 -0
- package/app/utils/image.test.js +141 -1
- package/app/utils/responsive-image.js +197 -115
- package/app/utils/responsive-image.test.js +483 -133
- package/config/default.js +2 -2
- package/config/mocks/default.js +2 -2
- package/package.json +7 -7
package/app/utils/image.test.js
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/* eslint-disable jest/no-conditional-expect */
|
|
8
8
|
import {version} from 'react'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getImageAttributes,
|
|
11
|
+
getImageLinkAttributes
|
|
12
|
+
} from '@salesforce/retail-react-app/app/utils/image'
|
|
10
13
|
|
|
11
14
|
const [majorStr] = version.split('.', 1)
|
|
12
15
|
const major = parseInt(majorStr, 10)
|
|
@@ -124,3 +127,140 @@ describe('getImageAttributes()', () => {
|
|
|
124
127
|
})
|
|
125
128
|
})
|
|
126
129
|
})
|
|
130
|
+
|
|
131
|
+
describe('getImageLinkAttributes()', () => {
|
|
132
|
+
test('empty image properties', () => {
|
|
133
|
+
expect(getImageLinkAttributes({})).toBeUndefined()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('image properties without fetch priority and loading strategy', () => {
|
|
137
|
+
expect(getImageLinkAttributes(imageProps)).toBeUndefined()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('image properties with fetch priority, without loading strategy, sizes and srcSet', () => {
|
|
141
|
+
expect(getImageLinkAttributes({...imageProps, fetchPriority: 'high'})).toStrictEqual({
|
|
142
|
+
rel: 'preload',
|
|
143
|
+
as: 'image',
|
|
144
|
+
href: imageProps.src,
|
|
145
|
+
fetchPriority: 'high'
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('image properties with fetch priority, sizes and srcSet, without loading strategy', () => {
|
|
150
|
+
expect(
|
|
151
|
+
getImageLinkAttributes({
|
|
152
|
+
...imageProps,
|
|
153
|
+
fetchPriority: 'high',
|
|
154
|
+
sizes: '100vw',
|
|
155
|
+
srcSet: `${imageProps.src} 240w`
|
|
156
|
+
})
|
|
157
|
+
).toStrictEqual({
|
|
158
|
+
rel: 'preload',
|
|
159
|
+
as: 'image',
|
|
160
|
+
href: imageProps.src,
|
|
161
|
+
fetchPriority: 'high',
|
|
162
|
+
imageSizes: '100vw',
|
|
163
|
+
imageSrcSet: `${imageProps.src} 240w`
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('image properties with fetch priority, type and media, without loading strategy, sizes and srcSet', () => {
|
|
168
|
+
expect(
|
|
169
|
+
getImageLinkAttributes({
|
|
170
|
+
...imageProps,
|
|
171
|
+
fetchPriority: 'high',
|
|
172
|
+
type: 'image/jpeg',
|
|
173
|
+
media: '(min-width: 80em)'
|
|
174
|
+
})
|
|
175
|
+
).toStrictEqual({
|
|
176
|
+
rel: 'preload',
|
|
177
|
+
as: 'image',
|
|
178
|
+
href: imageProps.src,
|
|
179
|
+
fetchPriority: 'high',
|
|
180
|
+
type: 'image/jpeg',
|
|
181
|
+
media: '(min-width: 80em)'
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('image properties with fetch priority, type, media, sizes and srcSet, without loading strategy', () => {
|
|
186
|
+
expect(
|
|
187
|
+
getImageLinkAttributes({
|
|
188
|
+
...imageProps,
|
|
189
|
+
fetchPriority: 'high',
|
|
190
|
+
type: 'image/jpeg',
|
|
191
|
+
media: '(min-width: 80em)',
|
|
192
|
+
sizes: '100vw',
|
|
193
|
+
srcSet: `${imageProps.src} 240w`
|
|
194
|
+
})
|
|
195
|
+
).toStrictEqual({
|
|
196
|
+
rel: 'preload',
|
|
197
|
+
as: 'image',
|
|
198
|
+
href: imageProps.src,
|
|
199
|
+
fetchPriority: 'high',
|
|
200
|
+
type: 'image/jpeg',
|
|
201
|
+
media: '(min-width: 80em)',
|
|
202
|
+
imageSizes: '100vw',
|
|
203
|
+
imageSrcSet: `${imageProps.src} 240w`
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('loading="eager"', () => {
|
|
208
|
+
test('image properties without fetch priority', () => {
|
|
209
|
+
expect(getImageLinkAttributes({...imageProps, loading: 'eager'})).toBeUndefined()
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('image properties with fetch priority, without loading strategy, sizes and srcSet', () => {
|
|
213
|
+
expect(
|
|
214
|
+
getImageLinkAttributes({...imageProps, loading: 'eager', fetchPriority: 'high'})
|
|
215
|
+
).toStrictEqual({
|
|
216
|
+
rel: 'preload',
|
|
217
|
+
as: 'image',
|
|
218
|
+
href: imageProps.src,
|
|
219
|
+
fetchPriority: 'high'
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('image properties with fetch priority, sizes and srcSet', () => {
|
|
224
|
+
expect(
|
|
225
|
+
getImageLinkAttributes({
|
|
226
|
+
...imageProps,
|
|
227
|
+
loading: 'eager',
|
|
228
|
+
fetchPriority: 'high',
|
|
229
|
+
sizes: '100vw',
|
|
230
|
+
srcSet: `${imageProps.src} 240w`
|
|
231
|
+
})
|
|
232
|
+
).toStrictEqual({
|
|
233
|
+
rel: 'preload',
|
|
234
|
+
as: 'image',
|
|
235
|
+
href: imageProps.src,
|
|
236
|
+
fetchPriority: 'high',
|
|
237
|
+
imageSizes: '100vw',
|
|
238
|
+
imageSrcSet: `${imageProps.src} 240w`
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
describe('loading="lazy"', () => {
|
|
244
|
+
test('image properties without fetch priority', () => {
|
|
245
|
+
expect(getImageLinkAttributes({...imageProps, loading: 'lazy'})).toBeUndefined()
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('image properties with fetch priority', () => {
|
|
249
|
+
expect(
|
|
250
|
+
getImageLinkAttributes({...imageProps, loading: 'lazy', fetchPriority: 'high'})
|
|
251
|
+
).toBeUndefined()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test('image properties with fetch priority, sizes and srcSet', () => {
|
|
255
|
+
expect(
|
|
256
|
+
getImageLinkAttributes({
|
|
257
|
+
...imageProps,
|
|
258
|
+
loading: 'eager',
|
|
259
|
+
fetchPriority: 'lazy',
|
|
260
|
+
sizes: '100vw',
|
|
261
|
+
srcSet: `${imageProps.src} 240w`
|
|
262
|
+
})
|
|
263
|
+
).toBeUndefined()
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
})
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
3
|
* All rights reserved.
|
|
4
4
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
7
|
import theme from '@salesforce/retail-react-app/app/components/shared/theme'
|
|
9
8
|
import logger from '@salesforce/retail-react-app/app/utils/logger-instance'
|
|
10
9
|
|
|
@@ -17,119 +16,45 @@ const getBreakpointLabels = (breakpoints) =>
|
|
|
17
16
|
.sort((a, b) => parseFloat(a[1]) - parseFloat(b[1]))
|
|
18
17
|
.map(([key]) => key)
|
|
19
18
|
|
|
19
|
+
const vwValue = /^\d+vw$/
|
|
20
|
+
const pxValue = /^\d+px$/
|
|
21
|
+
const emValue = /^\d+em$/
|
|
22
|
+
|
|
20
23
|
const {breakpoints: defaultBreakpoints} = theme
|
|
21
24
|
let themeBreakpoints = defaultBreakpoints
|
|
22
25
|
let breakpointLabels = getBreakpointLabels(themeBreakpoints)
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
|
-
*
|
|
26
|
-
* @param {
|
|
27
|
-
* @
|
|
28
|
-
* @
|
|
29
|
-
* @return {Object} src, sizes, and srcSet props for your image component
|
|
28
|
+
* Helper to create very specific `media` attributes for responsive preload purposes.
|
|
29
|
+
* @param {number} breakpointIndex
|
|
30
|
+
* @return {({min?: string, max?: string} | undefined)}
|
|
31
|
+
* @see {@link https://web.dev/articles/preload-responsive-images#picture}
|
|
30
32
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const obtainImageLinkMedia = (breakpointIndex) => {
|
|
34
|
+
const toMediaValue = (bp, type) => {
|
|
35
|
+
const val = themeBreakpoints[bp]
|
|
36
|
+
if (emValue.test(val)) {
|
|
37
|
+
// em value
|
|
38
|
+
const parsed = parseFloat(val)
|
|
39
|
+
return {[type]: type === 'max' ? `${parsed - 0.01}em` : `${parsed}em`}
|
|
35
40
|
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
themeBreakpoints = breakpoints
|
|
39
|
-
breakpointLabels = getBreakpointLabels(themeBreakpoints)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// See https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2223
|
|
44
|
-
return {
|
|
45
|
-
sizes: mapWidthsToSizes(widths),
|
|
46
|
-
srcSet: mapWidthsToSrcSet(widths, src),
|
|
47
|
-
src: getSrcWithoutOptionalParams(src)
|
|
42
|
+
const parsed = parseInt(val, 10)
|
|
43
|
+
return {[type]: type === 'max' ? `${parsed - 1}px` : `${parsed}px`}
|
|
48
44
|
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @param {(number[]|string[]|Object)} widths
|
|
53
|
-
* @return {string}
|
|
54
|
-
*/
|
|
55
|
-
const mapWidthsToSizes = (widths) => {
|
|
56
|
-
const _widths = withUnit(Array.isArray(widths) ? widths : widthsAsArray(widths))
|
|
57
45
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* @param {(number[]|string[]|Object)} widths
|
|
69
|
-
* @return {string}
|
|
70
|
-
*/
|
|
71
|
-
const mapWidthsToSrcSet = (widths, dynamicSrc) => {
|
|
72
|
-
let _widths = isObject(widths) ? widthsAsArray(widths) : widths.slice(0)
|
|
73
|
-
|
|
74
|
-
if (_widths.length < breakpointLabels.length) {
|
|
75
|
-
const lastWidth = _widths[_widths.length - 1]
|
|
76
|
-
const amountToPad = breakpointLabels.length - _widths.length
|
|
77
|
-
|
|
78
|
-
_widths = [..._widths, ...Array(amountToPad).fill(lastWidth)]
|
|
46
|
+
const nextBp = breakpointLabels.at(breakpointIndex + 1)
|
|
47
|
+
const currentBp = breakpointLabels.at(breakpointIndex)
|
|
48
|
+
if (breakpointIndex === 0) {
|
|
49
|
+
// first
|
|
50
|
+
return toMediaValue(nextBp, 'max')
|
|
51
|
+
} else if (breakpointIndex === breakpointLabels.length - 1) {
|
|
52
|
+
// last
|
|
53
|
+
return toMediaValue(currentBp, 'min')
|
|
79
54
|
}
|
|
80
|
-
|
|
81
|
-
_widths = uniqueArray(convertToPxNumbers(_widths)).sort()
|
|
82
|
-
|
|
83
|
-
const srcSet = []
|
|
84
|
-
_widths.forEach((width) => {
|
|
85
|
-
srcSet.push(width)
|
|
86
|
-
srcSet.push(width * 2) // for devices with higher pixel density
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
return srcSet.map((imageWidth) => `${getSrc(dynamicSrc, imageWidth)} ${imageWidth}w`).join(', ')
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const vwValue = /^\d+vw$/
|
|
93
|
-
const pxValue = /^\d+px$/
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* @param {string[]|number[]} widths
|
|
97
|
-
* @return {number[]}
|
|
98
|
-
*/
|
|
99
|
-
const convertToPxNumbers = (widths) => {
|
|
100
|
-
return widths
|
|
101
|
-
.map((width, i) => {
|
|
102
|
-
if (typeof width === 'number') {
|
|
103
|
-
return width
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (vwValue.test(width)) {
|
|
107
|
-
const vw = parseFloat(width)
|
|
108
|
-
const currentBp = breakpointLabels[i]
|
|
109
|
-
// We imagine the biggest image for the current breakpoint
|
|
110
|
-
// to be when the viewport is closely approaching the _next breakpoint_.
|
|
111
|
-
const nextBp = breakpointLabels[i + 1]
|
|
112
|
-
|
|
113
|
-
if (nextBp) {
|
|
114
|
-
return vwToPx(vw, nextBp)
|
|
115
|
-
} else {
|
|
116
|
-
// We're already at the last breakpoint
|
|
117
|
-
return widths[i] !== widths[i - 1] ? vwToPx(vw, currentBp) : undefined
|
|
118
|
-
}
|
|
119
|
-
} else if (pxValue.test(width)) {
|
|
120
|
-
return parseInt(width)
|
|
121
|
-
} else {
|
|
122
|
-
logger.error('Expecting to see values with vw or px unit only', {
|
|
123
|
-
namespace: 'utils.convertToPxNumbers'
|
|
124
|
-
})
|
|
125
|
-
return 0
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
.filter((width) => width !== undefined)
|
|
55
|
+
return {...toMediaValue(currentBp, 'min'), ...toMediaValue(nextBp, 'max')}
|
|
129
56
|
}
|
|
130
57
|
|
|
131
|
-
const uniqueArray = (array) => [...new Set(array)]
|
|
132
|
-
|
|
133
58
|
/**
|
|
134
59
|
* @param {(number[]|string[])} widths
|
|
135
60
|
*/
|
|
@@ -147,24 +72,28 @@ const isObject = (o) => o?.constructor === Object
|
|
|
147
72
|
*/
|
|
148
73
|
const widthsAsArray = (widths) => {
|
|
149
74
|
const biggestBreakpoint = breakpointLabels.filter((bp) => Boolean(widths[bp])).pop()
|
|
150
|
-
|
|
151
75
|
let mostRecent
|
|
152
76
|
return breakpointLabels.slice(0, breakpointLabels.indexOf(biggestBreakpoint) + 1).map((bp) => {
|
|
153
77
|
if (widths[bp]) {
|
|
154
78
|
mostRecent = widths[bp]
|
|
155
79
|
return widths[bp]
|
|
156
|
-
} else {
|
|
157
|
-
return mostRecent
|
|
158
80
|
}
|
|
81
|
+
return mostRecent
|
|
159
82
|
})
|
|
160
83
|
}
|
|
161
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @param {number} em
|
|
87
|
+
* @param {number} [browserDefaultFontSize]
|
|
88
|
+
*/
|
|
89
|
+
const emToPx = (em, browserDefaultFontSize = 16) => Math.round(em * browserDefaultFontSize)
|
|
90
|
+
|
|
162
91
|
/**
|
|
163
92
|
* @param {number} vw
|
|
164
93
|
* @param {string} breakpoint
|
|
165
94
|
*/
|
|
166
95
|
const vwToPx = (vw, breakpoint) => {
|
|
167
|
-
|
|
96
|
+
const result = (vw / 100) * parseFloat(themeBreakpoints[breakpoint])
|
|
168
97
|
const breakpointsDefinedInPx = Object.values(themeBreakpoints).some((val) => pxValue.test(val))
|
|
169
98
|
|
|
170
99
|
// Assumes theme's breakpoints are defined in either em or px
|
|
@@ -172,12 +101,6 @@ const vwToPx = (vw, breakpoint) => {
|
|
|
172
101
|
return breakpointsDefinedInPx ? result : emToPx(result)
|
|
173
102
|
}
|
|
174
103
|
|
|
175
|
-
/**
|
|
176
|
-
* @param {number} em
|
|
177
|
-
* @param {number} [browserDefaultFontSize]
|
|
178
|
-
*/
|
|
179
|
-
const emToPx = (em, browserDefaultFontSize = 16) => Math.round(em * browserDefaultFontSize)
|
|
180
|
-
|
|
181
104
|
/**
|
|
182
105
|
* @param {string} dynamicSrc
|
|
183
106
|
* @param {number} imageWidth
|
|
@@ -198,7 +121,166 @@ export const getSrc = (dynamicSrc, imageWidth) => {
|
|
|
198
121
|
* // Returns 'https://example.com/image.jpg'
|
|
199
122
|
* getSrcWithoutOptionalParams('https://example.com/image.jpg[?sw={width}]')
|
|
200
123
|
*/
|
|
201
|
-
const getSrcWithoutOptionalParams = (dynamicSrc) =>
|
|
202
|
-
|
|
203
|
-
|
|
124
|
+
const getSrcWithoutOptionalParams = (dynamicSrc) => dynamicSrc.replace(/\[[^\]]+\]/g, '')
|
|
125
|
+
|
|
126
|
+
const padArray = (arr) => {
|
|
127
|
+
const l1 = arr.length
|
|
128
|
+
const l2 = breakpointLabels.length
|
|
129
|
+
if (l1 < l2) {
|
|
130
|
+
const lastEntry = arr.at(-1)
|
|
131
|
+
const amountToPad = l2 - l1
|
|
132
|
+
return [...arr, ...Array(amountToPad).fill(lastEntry)]
|
|
133
|
+
}
|
|
134
|
+
return arr
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {string[]|number[]} widths
|
|
139
|
+
* @return {number[]}
|
|
140
|
+
*/
|
|
141
|
+
const convertToPxNumbers = (widths) =>
|
|
142
|
+
widths
|
|
143
|
+
.map((width, i) => {
|
|
144
|
+
if (typeof width === 'number') {
|
|
145
|
+
return width
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (vwValue.test(width)) {
|
|
149
|
+
const vw = parseFloat(width)
|
|
150
|
+
const currentBp = breakpointLabels[i]
|
|
151
|
+
// We imagine the biggest image for the current breakpoint
|
|
152
|
+
// to be when the viewport is closely approaching the _next breakpoint_.
|
|
153
|
+
const nextBp = breakpointLabels[i + 1]
|
|
154
|
+
|
|
155
|
+
if (nextBp) {
|
|
156
|
+
return vwToPx(vw, nextBp)
|
|
157
|
+
}
|
|
158
|
+
// We're already at the last breakpoint
|
|
159
|
+
return widths[i] !== widths[i - 1] ? vwToPx(vw, currentBp) : undefined
|
|
160
|
+
} else if (pxValue.test(width)) {
|
|
161
|
+
return parseInt(width, 10)
|
|
162
|
+
} else {
|
|
163
|
+
logger.error('Expecting to see values with vw or px unit only', {
|
|
164
|
+
namespace: 'utils.convertToPxNumbers'
|
|
165
|
+
})
|
|
166
|
+
return 0
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
.filter((width) => width !== undefined)
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Transforms an array of preload link objects by converting the raw `media`
|
|
173
|
+
* property of each entry (with `min` and/or `max` values) into actual media
|
|
174
|
+
* queries using `(min-width)` and/or `(max-width)`.
|
|
175
|
+
* @param {{srcSet: string, sizes: string, media: {min?: string, max?: string}}[]} links
|
|
176
|
+
* @return {{srcSet: string, sizes: string, media: string}[]}
|
|
177
|
+
*/
|
|
178
|
+
const convertImageLinksMedia = (links) =>
|
|
179
|
+
links.map((link) => {
|
|
180
|
+
const {
|
|
181
|
+
media: {min, max}
|
|
182
|
+
} = link
|
|
183
|
+
const acc = []
|
|
184
|
+
if (min) {
|
|
185
|
+
acc.push(`(min-width: ${min})`)
|
|
186
|
+
}
|
|
187
|
+
if (max) {
|
|
188
|
+
acc.push(`(max-width: ${max})`)
|
|
189
|
+
}
|
|
190
|
+
return {...link, media: acc.join(' and ')}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Determines the data required for the responsive `<source>` and `<link rel="preload">
|
|
195
|
+
* portions/elements.
|
|
196
|
+
* @param {string} src
|
|
197
|
+
* @param {(number[]|string[])} widths
|
|
198
|
+
* @returns {{sources: {srcSet: string, sizes: string, media: string}[], links: {srcSet: string, sizes: string, media: string}[]}}
|
|
199
|
+
*/
|
|
200
|
+
const getResponsiveSourcesAndLinks = (src, widths) => {
|
|
201
|
+
const sizesWidths = withUnit(widths)
|
|
202
|
+
const l = sizesWidths.length
|
|
203
|
+
const _sizes = breakpointLabels.map((bp, i) => {
|
|
204
|
+
return i === 0
|
|
205
|
+
? {
|
|
206
|
+
media: undefined,
|
|
207
|
+
mediaLink: obtainImageLinkMedia(i),
|
|
208
|
+
sizes: sizesWidths[i]
|
|
209
|
+
}
|
|
210
|
+
: {
|
|
211
|
+
media: `(min-width: ${themeBreakpoints[bp]})`,
|
|
212
|
+
mediaLink: obtainImageLinkMedia(i),
|
|
213
|
+
sizes: sizesWidths.at(i >= l ? l - 1 : i)
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const sourcesWidths = convertToPxNumbers(padArray(widths))
|
|
218
|
+
const sourcesLength = sourcesWidths.length
|
|
219
|
+
const {sources, links} = breakpointLabels.reduce(
|
|
220
|
+
(acc, bp, idx) => {
|
|
221
|
+
// To support higher-density devices, request all images in 1x and 2x widths
|
|
222
|
+
const width = sourcesWidths.at(idx >= sourcesLength ? sourcesLength - 1 : idx)
|
|
223
|
+
const {sizes, media, mediaLink} = _sizes.at(idx)
|
|
224
|
+
const lastSource = acc.sources.at(-1)
|
|
225
|
+
const lastLink = acc.links.at(-1)
|
|
226
|
+
const srcSet = [1, 2]
|
|
227
|
+
.map((factor) => {
|
|
228
|
+
const effectiveWidth = Math.round(width * factor)
|
|
229
|
+
const effectiveSize = Math.round(width * factor)
|
|
230
|
+
return `${getSrc(src, effectiveSize)} ${effectiveWidth}w`
|
|
231
|
+
})
|
|
232
|
+
.join(', ')
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
idx < sourcesLength &&
|
|
236
|
+
(lastSource?.sizes !== sizes || srcSet !== lastSource?.srcSet)
|
|
237
|
+
) {
|
|
238
|
+
// Only store new `<source>` if we haven't already stored those values
|
|
239
|
+
acc.sources.push({srcSet, sizes, media})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (lastLink?.sizes !== sizes || srcSet !== lastLink?.srcSet) {
|
|
243
|
+
// Only store new `<link>` if we haven't already stored those values
|
|
244
|
+
acc.links.push({srcSet, sizes, media: mediaLink})
|
|
245
|
+
} else {
|
|
246
|
+
// If we have already stored those values, update the `max` portion of the related `<link>` data
|
|
247
|
+
lastLink.media.max = mediaLink.max
|
|
248
|
+
}
|
|
249
|
+
return acc
|
|
250
|
+
},
|
|
251
|
+
{sources: [], links: []}
|
|
252
|
+
)
|
|
253
|
+
return {sources: sources.reverse(), links: convertImageLinksMedia(links)}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Resolve the attributes required to create a DIS-optimized `<picture>` component.
|
|
258
|
+
* @param {Object} props
|
|
259
|
+
* @param {string} props.src - Dynamic src having an optional param that can vary with widths. For example: `image[_{width}].jpg` or `image.jpg[?sw={width}&q=60]`
|
|
260
|
+
* @param {(number[] |string[] |Object)} [props.widths] - Image widths relative to the breakpoints, whose units can either be px or vw or unit-less. They will be mapped to the corresponding `sizes` and `srcSet`.
|
|
261
|
+
* @param {Object} [props.breakpoints] - The current theme's breakpoints. If not given, Chakra's default breakpoints will be used.
|
|
262
|
+
* @return {Object} src, sizes, srcSet, media props for your image component
|
|
263
|
+
* @see {@link DynamicImage}
|
|
264
|
+
*/
|
|
265
|
+
export const getResponsivePictureAttributes = ({src, widths, breakpoints = defaultBreakpoints}) => {
|
|
266
|
+
if (!widths) {
|
|
267
|
+
return {
|
|
268
|
+
sources: [],
|
|
269
|
+
links: [],
|
|
270
|
+
src: getSrcWithoutOptionalParams(src)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (breakpoints !== themeBreakpoints) {
|
|
275
|
+
themeBreakpoints = breakpoints
|
|
276
|
+
breakpointLabels = getBreakpointLabels(themeBreakpoints)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const _widths = isObject(widths) ? widthsAsArray(widths) : widths.slice(0)
|
|
280
|
+
const {sources, links} = getResponsiveSourcesAndLinks(src, _widths)
|
|
281
|
+
return {
|
|
282
|
+
sources,
|
|
283
|
+
links,
|
|
284
|
+
src: getSrcWithoutOptionalParams(src)
|
|
285
|
+
}
|
|
204
286
|
}
|