@brandocms/jupiter 4.0.0-beta.2 → 5.0.0-beta.10
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/package.json +7 -9
- package/src/modules/Cookies/index.js +190 -8
- package/src/modules/DoubleHeader/index.js +4 -4
- package/src/modules/FixedHeader/index.js +4 -4
- package/src/modules/HeroSlider/index.js +22 -12
- package/src/modules/Lazyload/index.js +67 -8
- package/src/modules/Looper/index.js +192 -192
- package/src/modules/StickyHeader/index.js +4 -4
- package/src/utils/motion-helpers.js +2 -0
- package/types/index.d.ts +1 -1
- package/types/modules/Cookies/index.d.ts +22 -0
- package/types/modules/DoubleHeader/index.d.ts +1 -0
- package/types/modules/Dropdown/index.d.ts +1 -1
- package/types/modules/FixedHeader/index.d.ts +1 -0
- package/types/modules/HeroSlider/index.d.ts +10 -0
- package/types/modules/Lazyload/index.d.ts +7 -0
- package/types/modules/Lightbox/index.d.ts +3 -2
- package/types/modules/Looper/index.d.ts +10 -123
- package/types/modules/Marquee/index.d.ts +2 -0
- package/types/modules/Moonwalk/index.d.ts +33 -3
- package/types/modules/Popover/index.d.ts +1 -1
- package/types/modules/StickyHeader/index.d.ts +1 -0
- package/types/modules/Toggler/index.d.ts +16 -0
- package/types/utils/motion-helpers.d.ts +91 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandocms/jupiter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.10",
|
|
4
4
|
"description": "Frontend helpers.",
|
|
5
5
|
"author": "Univers/Twined",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -36,23 +36,21 @@
|
|
|
36
36
|
"playwright:dataloader": "playwright test e2e/dataloader.spec.js --project=chromium --reporter line",
|
|
37
37
|
"playwright:dataloader-url-sync": "playwright test e2e/dataloader-url-sync.spec.js --project=chromium --reporter line",
|
|
38
38
|
"playwright:parallax": "playwright test e2e/parallax.spec.js --project=chromium --reporter line",
|
|
39
|
+
"playwright:looper": "playwright test e2e/looper.spec.js --project=chromium --reporter line",
|
|
39
40
|
"vite": "vite",
|
|
40
41
|
"vite:build": "vite build",
|
|
41
42
|
"vite:preview": "vite preview"
|
|
42
43
|
},
|
|
43
44
|
"types": "types/index.d.ts",
|
|
44
45
|
"dependencies": {
|
|
45
|
-
"body-scroll-lock": "^4.0.0-beta.0",
|
|
46
|
-
"gsap": "^3.13.0",
|
|
47
46
|
"lodash.defaultsdeep": "^4.6.1",
|
|
48
|
-
"motion": "^12.
|
|
49
|
-
"virtual-scroll": "^2.2.1"
|
|
47
|
+
"motion": "^12.35.1"
|
|
50
48
|
},
|
|
51
49
|
"devDependencies": {
|
|
52
|
-
"@playwright/test": "^1.
|
|
53
|
-
"@types/node": "^22.
|
|
54
|
-
"typescript": "^5.
|
|
55
|
-
"vite": "^6.
|
|
50
|
+
"@playwright/test": "^1.57.0",
|
|
51
|
+
"@types/node": "^22.19.3",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vite": "^6.4.1"
|
|
56
54
|
},
|
|
57
55
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
58
56
|
}
|
|
@@ -3,6 +3,91 @@ import _defaultsDeep from 'lodash.defaultsdeep'
|
|
|
3
3
|
import * as Events from '../../events'
|
|
4
4
|
import { set } from '../../utils/motion-helpers'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @module Cookies
|
|
8
|
+
*
|
|
9
|
+
* Cookie consent module with optional dialog and toggle button support.
|
|
10
|
+
*
|
|
11
|
+
* ## Consent dialog
|
|
12
|
+
*
|
|
13
|
+
* The traditional consent banner uses a fixed container at the bottom of the page:
|
|
14
|
+
*
|
|
15
|
+
* ```html
|
|
16
|
+
* <div class="cookie-container">
|
|
17
|
+
* <div class="cookie-container-inner">
|
|
18
|
+
* <div class="cookie-law-text">
|
|
19
|
+
* <p>We use cookies...</p>
|
|
20
|
+
* </div>
|
|
21
|
+
* <div class="cookie-law-buttons">
|
|
22
|
+
* <button class="dismiss-cookielaw">Accept</button>
|
|
23
|
+
* <button class="refuse-cookielaw">Decline</button>
|
|
24
|
+
* </div>
|
|
25
|
+
* </div>
|
|
26
|
+
* </div>
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ## Consent toggle button
|
|
30
|
+
*
|
|
31
|
+
* A toggle button can be placed anywhere on the page to let users change their
|
|
32
|
+
* consent at any time. It can also serve as the sole consent mechanism (no
|
|
33
|
+
* dialog required).
|
|
34
|
+
*
|
|
35
|
+
* ```html
|
|
36
|
+
* <button data-cookie-consent
|
|
37
|
+
* data-cookie-consent-accept="Accept cookies"
|
|
38
|
+
* data-cookie-consent-refuse="Refuse cookies">
|
|
39
|
+
* </button>
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* ### Data attributes
|
|
43
|
+
*
|
|
44
|
+
* | Attribute | Description |
|
|
45
|
+
* |---|---|
|
|
46
|
+
* | `data-cookie-consent` | Marks the element as a consent toggle |
|
|
47
|
+
* | `data-cookie-consent-accept` | Label shown when user can accept (currently refused/unset) |
|
|
48
|
+
* | `data-cookie-consent-refuse` | Label shown when user can retract (currently accepted) |
|
|
49
|
+
* | `data-cookie-consent-status` | Set by the module: `"accepted"` or `"refused"` |
|
|
50
|
+
* | `data-cookie-consent-icon` | Set on the injected icon `<span>` |
|
|
51
|
+
* | `data-cookie-consent-label` | Set on the injected label `<span>` |
|
|
52
|
+
*
|
|
53
|
+
* ### CSS styling
|
|
54
|
+
*
|
|
55
|
+
* ```css
|
|
56
|
+
* [data-cookie-consent-status="accepted"] [data-cookie-consent-icon] { color: green; }
|
|
57
|
+
* [data-cookie-consent-status="refused"] [data-cookie-consent-icon] { color: red; }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* ### Gettext / translation
|
|
61
|
+
*
|
|
62
|
+
* ```html
|
|
63
|
+
* <button data-cookie-consent
|
|
64
|
+
* data-cookie-consent-accept="{{ _('Accept cookies') }}"
|
|
65
|
+
* data-cookie-consent-refuse="{{ _('Refuse cookies') }}">
|
|
66
|
+
* </button>
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* ## Usage examples
|
|
70
|
+
*
|
|
71
|
+
* Dialog only (default):
|
|
72
|
+
* ```js
|
|
73
|
+
* new Cookies(app)
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* Toggle only (no dialog HTML needed):
|
|
77
|
+
* ```js
|
|
78
|
+
* new Cookies(app, { setCookies: (c) => { ... } })
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* Both dialog and toggle:
|
|
82
|
+
* ```js
|
|
83
|
+
* new Cookies(app, {
|
|
84
|
+
* onConsentChanged: (c) => {
|
|
85
|
+
* console.log(c.getCookie('COOKIES_CONSENT_STATUS'))
|
|
86
|
+
* }
|
|
87
|
+
* })
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
|
|
6
91
|
/**
|
|
7
92
|
* @typedef {Object} CookiesOptions
|
|
8
93
|
* @property {Function} [onAccept] - Called when cookies are accepted
|
|
@@ -11,6 +96,7 @@ import { set } from '../../utils/motion-helpers'
|
|
|
11
96
|
* @property {Function} [alreadyRefused] - Called if user has already refused cookies
|
|
12
97
|
* @property {Function} [setCookies] - Custom function to set cookies
|
|
13
98
|
* @property {Function} [showCC] - Custom function to display cookie consent dialog
|
|
99
|
+
* @property {Function} [onConsentChanged] - Called after consent is toggled via the toggle button
|
|
14
100
|
*/
|
|
15
101
|
|
|
16
102
|
/** @type {CookiesOptions} */
|
|
@@ -21,6 +107,7 @@ const DEFAULT_OPTIONS = {
|
|
|
21
107
|
|
|
22
108
|
c.setCookie('COOKIES_CONSENT_STATUS', 1, oneYearFromNow, '/')
|
|
23
109
|
c.opts.setCookies(c)
|
|
110
|
+
c.updateConsentToggles()
|
|
24
111
|
|
|
25
112
|
const timeline = [
|
|
26
113
|
[c.cc, { y: '120%' }, { duration: 0.35, ease: 'easeIn', at: 0 }],
|
|
@@ -37,6 +124,7 @@ const DEFAULT_OPTIONS = {
|
|
|
37
124
|
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1)
|
|
38
125
|
|
|
39
126
|
c.setCookie('COOKIES_CONSENT_STATUS', 0, oneYearFromNow, '/')
|
|
127
|
+
c.updateConsentToggles()
|
|
40
128
|
|
|
41
129
|
const timeline = [
|
|
42
130
|
[c.cc, { y: '120%' }, { duration: 0.35, ease: 'easeIn', at: 0 }],
|
|
@@ -58,6 +146,8 @@ const DEFAULT_OPTIONS = {
|
|
|
58
146
|
|
|
59
147
|
setCookies: (c) => {},
|
|
60
148
|
|
|
149
|
+
onConsentChanged: (c) => {},
|
|
150
|
+
|
|
61
151
|
showCC: (c) => {
|
|
62
152
|
if (c.hasCookie('COOKIES_CONSENT_STATUS')) {
|
|
63
153
|
if (c.getCookie('COOKIES_CONSENT_STATUS') === '1') {
|
|
@@ -107,22 +197,114 @@ export default class Cookies {
|
|
|
107
197
|
this.btn = document.querySelector('.dismiss-cookielaw')
|
|
108
198
|
this.btnRefuse = document.querySelector('.refuse-cookielaw')
|
|
109
199
|
|
|
110
|
-
|
|
200
|
+
this.setupConsentToggles()
|
|
201
|
+
|
|
202
|
+
if (!this.btn && this.consentToggles.length === 0) {
|
|
111
203
|
return
|
|
112
204
|
}
|
|
113
205
|
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
206
|
+
if (this.btn) {
|
|
207
|
+
this.app.registerCallback(Events.APPLICATION_REVEALED, () => {
|
|
208
|
+
this.opts.showCC(this)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
this.btn.addEventListener('click', () => {
|
|
212
|
+
this.opts.onAccept(this)
|
|
213
|
+
})
|
|
214
|
+
if (this.btnRefuse) {
|
|
215
|
+
this.btnRefuse.addEventListener('click', () => {
|
|
216
|
+
this.opts.onRefuse(this)
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Find all `[data-cookie-consent]` elements and wire them up.
|
|
224
|
+
*/
|
|
225
|
+
setupConsentToggles() {
|
|
226
|
+
this.consentToggles = [...document.querySelectorAll('[data-cookie-consent]')]
|
|
227
|
+
|
|
228
|
+
this.consentToggles.forEach(el => {
|
|
229
|
+
const icon = document.createElement('span')
|
|
230
|
+
icon.setAttribute('data-cookie-consent-icon', '')
|
|
231
|
+
|
|
232
|
+
const label = document.createElement('span')
|
|
233
|
+
label.setAttribute('data-cookie-consent-label', '')
|
|
234
|
+
|
|
235
|
+
el.appendChild(icon)
|
|
236
|
+
el.appendChild(label)
|
|
237
|
+
|
|
238
|
+
this.updateConsentToggle(el)
|
|
239
|
+
|
|
240
|
+
el.addEventListener('click', () => {
|
|
241
|
+
this.handleConsentToggle()
|
|
242
|
+
})
|
|
116
243
|
})
|
|
244
|
+
}
|
|
117
245
|
|
|
118
|
-
|
|
119
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Update a single consent toggle element to reflect current cookie state.
|
|
248
|
+
* @param {Element} el - The toggle element
|
|
249
|
+
*/
|
|
250
|
+
updateConsentToggle(el) {
|
|
251
|
+
const accepted = this.getCookie('COOKIES_CONSENT_STATUS') === '1'
|
|
252
|
+
const acceptText = el.getAttribute('data-cookie-consent-accept') || 'Accept cookies'
|
|
253
|
+
const refuseText = el.getAttribute('data-cookie-consent-refuse') || 'Refuse cookies'
|
|
254
|
+
|
|
255
|
+
const icon = el.querySelector('[data-cookie-consent-icon]')
|
|
256
|
+
const label = el.querySelector('[data-cookie-consent-label]')
|
|
257
|
+
|
|
258
|
+
if (accepted) {
|
|
259
|
+
el.setAttribute('data-cookie-consent-status', 'accepted')
|
|
260
|
+
icon.textContent = '\u2713'
|
|
261
|
+
label.textContent = refuseText
|
|
262
|
+
} else {
|
|
263
|
+
el.setAttribute('data-cookie-consent-status', 'refused')
|
|
264
|
+
icon.textContent = '\u2715'
|
|
265
|
+
label.textContent = acceptText
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Update all consent toggle elements.
|
|
271
|
+
*/
|
|
272
|
+
updateConsentToggles() {
|
|
273
|
+
this.consentToggles.forEach(el => {
|
|
274
|
+
this.updateConsentToggle(el)
|
|
120
275
|
})
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Handle a click on a consent toggle button.
|
|
280
|
+
*/
|
|
281
|
+
handleConsentToggle() {
|
|
282
|
+
const oneYearFromNow = new Date()
|
|
283
|
+
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1)
|
|
284
|
+
|
|
285
|
+
const accepted = this.getCookie('COOKIES_CONSENT_STATUS') === '1'
|
|
286
|
+
|
|
287
|
+
if (accepted) {
|
|
288
|
+
this.setCookie('COOKIES_CONSENT_STATUS', 0, oneYearFromNow, '/')
|
|
289
|
+
} else {
|
|
290
|
+
this.setCookie('COOKIES_CONSENT_STATUS', 1, oneYearFromNow, '/')
|
|
291
|
+
this.opts.setCookies(this)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.updateConsentToggles()
|
|
295
|
+
|
|
296
|
+
if (this.cc && this.cc.style.display !== 'none') {
|
|
297
|
+
const timeline = [
|
|
298
|
+
[this.cc, { y: '120%' }, { duration: 0.35, ease: 'easeIn', at: 0 }],
|
|
299
|
+
[this.inner, { opacity: 0 }, { duration: 0.3, ease: 'easeIn', at: 0 }]
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
animate(timeline).finished.then(() => {
|
|
303
|
+
this.cc.style.display = 'none'
|
|
124
304
|
})
|
|
125
305
|
}
|
|
306
|
+
|
|
307
|
+
this.opts.onConsentChanged(this)
|
|
126
308
|
}
|
|
127
309
|
|
|
128
310
|
/**
|
|
@@ -50,7 +50,7 @@ const DEFAULT_EVENTS = {
|
|
|
50
50
|
|
|
51
51
|
onPin: (h) => {
|
|
52
52
|
animate(h.auxEl, {
|
|
53
|
-
|
|
53
|
+
y: '0%'
|
|
54
54
|
}, {
|
|
55
55
|
duration: 0.35,
|
|
56
56
|
ease: 'easeOut'
|
|
@@ -60,7 +60,7 @@ const DEFAULT_EVENTS = {
|
|
|
60
60
|
onUnpin: (h) => {
|
|
61
61
|
h._hiding = true
|
|
62
62
|
animate(h.auxEl, {
|
|
63
|
-
|
|
63
|
+
y: '-100%'
|
|
64
64
|
}, {
|
|
65
65
|
duration: 0.25,
|
|
66
66
|
ease: 'easeIn'
|
|
@@ -86,12 +86,12 @@ const DEFAULT_OPTIONS = {
|
|
|
86
86
|
},
|
|
87
87
|
enter: (h) => {
|
|
88
88
|
// Set initial states
|
|
89
|
-
set(h.auxEl, {
|
|
89
|
+
set(h.auxEl, { y: '-100%' })
|
|
90
90
|
set(h.lis, { opacity: 0 })
|
|
91
91
|
|
|
92
92
|
// Auxiliary header slides down
|
|
93
93
|
animate(h.auxEl, {
|
|
94
|
-
|
|
94
|
+
y: '0%'
|
|
95
95
|
}, {
|
|
96
96
|
duration: 1,
|
|
97
97
|
delay: h.opts.enterDelay,
|
|
@@ -79,7 +79,7 @@ import { set } from '../../utils/motion-helpers'
|
|
|
79
79
|
const DEFAULT_EVENTS = {
|
|
80
80
|
onPin: (h) => {
|
|
81
81
|
animate(h.el, {
|
|
82
|
-
|
|
82
|
+
y: '0%'
|
|
83
83
|
}, {
|
|
84
84
|
duration: 0.35,
|
|
85
85
|
ease: 'easeOut'
|
|
@@ -89,7 +89,7 @@ const DEFAULT_EVENTS = {
|
|
|
89
89
|
onUnpin: (h) => {
|
|
90
90
|
h._hiding = true
|
|
91
91
|
animate(h.el, {
|
|
92
|
-
|
|
92
|
+
y: '-100%'
|
|
93
93
|
}, {
|
|
94
94
|
duration: 0.25,
|
|
95
95
|
ease: 'easeIn'
|
|
@@ -156,14 +156,14 @@ const DEFAULT_OPTIONS = {
|
|
|
156
156
|
canvas: window,
|
|
157
157
|
intersects: null,
|
|
158
158
|
beforeEnter: (h) => {
|
|
159
|
-
set(h.el, {
|
|
159
|
+
set(h.el, { y: '-100%' })
|
|
160
160
|
set(h.lis, { opacity: 0 })
|
|
161
161
|
},
|
|
162
162
|
|
|
163
163
|
enter: (h) => {
|
|
164
164
|
// Header slides down
|
|
165
165
|
animate(h.el, {
|
|
166
|
-
|
|
166
|
+
y: '0%'
|
|
167
167
|
}, {
|
|
168
168
|
duration: 1,
|
|
169
169
|
delay: h.opts.enterDelay,
|
|
@@ -306,20 +306,30 @@ export default class HeroSlider {
|
|
|
306
306
|
this._currentAnimation = animate(sequence)
|
|
307
307
|
|
|
308
308
|
this._currentAnimation.finished.then(() => {
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
309
|
+
// Reset ALL slides using instant animations to ensure Motion.js state is clean
|
|
310
|
+
Array.from(this.slides).forEach((slide) => {
|
|
311
|
+
if (slide === this._currentSlide) return
|
|
312
|
+
const img = slide.querySelector('.hero-slide-img')
|
|
313
|
+
if (img) {
|
|
314
|
+
// Use animate with duration 0 to reset Motion.js internal state
|
|
315
|
+
animate(img, { scale: 1 }, { duration: 0 })
|
|
316
|
+
}
|
|
317
|
+
const isNext = slide === this._nextSlide
|
|
318
|
+
// Reset slide using animate with duration 0
|
|
319
|
+
animate(slide, {
|
|
320
|
+
width: '100%',
|
|
321
|
+
opacity: isNext ? 1 : 0,
|
|
322
|
+
}, { duration: 0 })
|
|
323
|
+
slide.style.overflow = ''
|
|
324
|
+
slide.style.zIndex = isNext ? this.opts.zIndex.next : this.opts.zIndex.regular
|
|
315
325
|
})
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
|
|
327
|
+
animate(this._currentSlide, {
|
|
318
328
|
width: '100%',
|
|
319
|
-
opacity:
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
|
|
329
|
+
opacity: 1,
|
|
330
|
+
}, { duration: 0 })
|
|
331
|
+
this._currentSlide.style.zIndex = this.opts.zIndex.visible
|
|
332
|
+
|
|
323
333
|
this.next()
|
|
324
334
|
})
|
|
325
335
|
}
|
|
@@ -83,6 +83,65 @@ export default class Lazyload {
|
|
|
83
83
|
this.initObserver(this.revealObserver, false)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Observe new lazyload elements within a container
|
|
88
|
+
* Handles both [data-ll-image] and [data-ll-srcset] elements
|
|
89
|
+
* Useful for dynamically added content (e.g., Looper clones)
|
|
90
|
+
* @param {HTMLElement|HTMLElement[]|NodeList} elements - Container element(s) or lazyload element(s) to observe
|
|
91
|
+
*/
|
|
92
|
+
observe(elements) {
|
|
93
|
+
// Handle NodeList, array, or single element
|
|
94
|
+
const els = elements instanceof NodeList ? Array.from(elements) :
|
|
95
|
+
Array.isArray(elements) ? elements : [elements]
|
|
96
|
+
|
|
97
|
+
let imgIdx = this.lazyImages?.length || 0
|
|
98
|
+
let picIdx = this.lazyPictures?.length || 0
|
|
99
|
+
|
|
100
|
+
els.forEach(el => {
|
|
101
|
+
// Handle [data-ll-image] elements
|
|
102
|
+
if (this.imageObserver) {
|
|
103
|
+
const images = el.matches?.('[data-ll-image]')
|
|
104
|
+
? [el]
|
|
105
|
+
: el.querySelectorAll?.('[data-ll-image]') || []
|
|
106
|
+
|
|
107
|
+
images.forEach(img => {
|
|
108
|
+
// Skip if already observed or loaded
|
|
109
|
+
if (img.hasAttribute('data-ll-idx') || img.hasAttribute('data-ll-loaded')) return
|
|
110
|
+
|
|
111
|
+
img.setAttribute('data-ll-blurred', '')
|
|
112
|
+
img.setAttribute('data-ll-idx', imgIdx)
|
|
113
|
+
img.style.setProperty('--ll-idx', imgIdx)
|
|
114
|
+
this.imageObserver.observe(img)
|
|
115
|
+
imgIdx++
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Handle [data-ll-srcset] picture elements
|
|
120
|
+
if (this.loadObserver) {
|
|
121
|
+
const pictures = el.matches?.('[data-ll-srcset]')
|
|
122
|
+
? [el]
|
|
123
|
+
: el.querySelectorAll?.('[data-ll-srcset]') || []
|
|
124
|
+
|
|
125
|
+
pictures.forEach(picture => {
|
|
126
|
+
// Skip if already loaded
|
|
127
|
+
if (picture.hasAttribute('data-ll-srcset-ready')) return
|
|
128
|
+
|
|
129
|
+
picture.setAttribute('data-ll-srcset-initialized', '')
|
|
130
|
+
picture.querySelectorAll('img:not([data-ll-loaded])').forEach(img => {
|
|
131
|
+
img.removeAttribute('data-ll-idx') // Clear cloned idx
|
|
132
|
+
img.setAttribute('data-ll-blurred', '')
|
|
133
|
+
img.setAttribute('data-ll-idx', picIdx)
|
|
134
|
+
img.style.setProperty('--ll-idx', picIdx)
|
|
135
|
+
})
|
|
136
|
+
// Add to both observers like initObserver does
|
|
137
|
+
this.loadObserver.observe(picture)
|
|
138
|
+
this.revealObserver?.observe(picture)
|
|
139
|
+
picIdx++
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
86
145
|
initialize() {
|
|
87
146
|
// initialize ResizeObserver for images with data-sizes="auto"
|
|
88
147
|
this.initializeResizeObserver()
|
|
@@ -128,12 +187,7 @@ export default class Lazyload {
|
|
|
128
187
|
)
|
|
129
188
|
|
|
130
189
|
this.lazyImages = this.target.querySelectorAll('[data-ll-image]')
|
|
131
|
-
this.lazyImages
|
|
132
|
-
img.setAttribute('data-ll-blurred', '')
|
|
133
|
-
img.setAttribute('data-ll-idx', idx)
|
|
134
|
-
img.style.setProperty('--ll-idx', idx)
|
|
135
|
-
this.imageObserver.observe(img)
|
|
136
|
-
})
|
|
190
|
+
this.observe(this.lazyImages)
|
|
137
191
|
}
|
|
138
192
|
|
|
139
193
|
initObserver(observer, setAttrs = true) {
|
|
@@ -150,12 +204,17 @@ export default class Lazyload {
|
|
|
150
204
|
})
|
|
151
205
|
}
|
|
152
206
|
|
|
153
|
-
forceLoad($container = document.body) {
|
|
207
|
+
forceLoad($container = document.body, { reveal = true } = {}) {
|
|
154
208
|
const images = Dom.all($container, '[data-ll-image]')
|
|
155
209
|
images.forEach(img => this.swapImage(img))
|
|
156
210
|
|
|
157
211
|
const pictures = Dom.all($container, '[data-ll-srcset]')
|
|
158
|
-
pictures.forEach(picture =>
|
|
212
|
+
pictures.forEach(picture => {
|
|
213
|
+
this.loadPicture(picture)
|
|
214
|
+
if (reveal) {
|
|
215
|
+
this.revealPicture(picture)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
159
218
|
}
|
|
160
219
|
|
|
161
220
|
initializeResizeObserver() {
|