@brandocms/jupiter 5.0.0-beta.1 → 5.0.0-beta.11
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 +63 -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": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.11",
|
|
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.2"
|
|
50
48
|
},
|
|
51
49
|
"devDependencies": {
|
|
52
|
-
"@playwright/test": "^1.
|
|
53
|
-
"@types/node": "^22
|
|
54
|
-
"typescript": "^5.
|
|
55
|
-
"vite": "^
|
|
50
|
+
"@playwright/test": "^1.58.2",
|
|
51
|
+
"@types/node": "^22",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vite": "^7.3.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,
|
|
@@ -202,6 +202,12 @@ export default class FixedHeader {
|
|
|
202
202
|
*/
|
|
203
203
|
constructor(app, opts = {}) {
|
|
204
204
|
this.app = app
|
|
205
|
+
// Preserve raw section configs before _defaultsDeep mutates them
|
|
206
|
+
this._rawSections = opts.sections
|
|
207
|
+
? Object.fromEntries(
|
|
208
|
+
Object.entries(opts.sections).map(([k, v]) => [k, { ...v }])
|
|
209
|
+
)
|
|
210
|
+
: {}
|
|
205
211
|
this.mainOpts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
206
212
|
|
|
207
213
|
if (typeof this.mainOpts.el === 'string') {
|
|
@@ -704,6 +710,59 @@ export default class FixedHeader {
|
|
|
704
710
|
)
|
|
705
711
|
}
|
|
706
712
|
|
|
713
|
+
/**
|
|
714
|
+
* Reconfigure the header for a new section/page.
|
|
715
|
+
* Call this after a view transition or SPA navigation
|
|
716
|
+
* to re-resolve section options and reset scroll state.
|
|
717
|
+
*/
|
|
718
|
+
reconfigure() {
|
|
719
|
+
const section = document.body.getAttribute('data-script')
|
|
720
|
+
|
|
721
|
+
// Build a fresh opts object from raw sections so function
|
|
722
|
+
// offsets that were previously resolved to numbers are restored
|
|
723
|
+
const freshOpts = {
|
|
724
|
+
...this.mainOpts,
|
|
725
|
+
sections: Object.fromEntries(
|
|
726
|
+
Object.entries(this._rawSections).map(([k, v]) => [k, { ...v }])
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
this.opts = this._getOptionsForSection(section, freshOpts)
|
|
730
|
+
|
|
731
|
+
// Re-resolve dynamic offsets
|
|
732
|
+
if (typeof this.opts.offsetBg === 'string') {
|
|
733
|
+
const offsetBgElm = document.querySelector(this.opts.offsetBg)
|
|
734
|
+
this.opts.offsetBg = offsetBgElm ? offsetBgElm.offsetTop : 200
|
|
735
|
+
} else if (typeof this.opts.offsetBg === 'function') {
|
|
736
|
+
this.opts.offsetBg = this.opts.offsetBg(this) - 1
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (typeof this.opts.offset === 'string') {
|
|
740
|
+
const offsetElm = document.querySelector(this.opts.offset)
|
|
741
|
+
this.opts.offset = offsetElm ? offsetElm.offsetTop - 1 : 0
|
|
742
|
+
} else if (typeof this.opts.offset === 'function') {
|
|
743
|
+
this.opts.offset = this.opts.offset(this) - 1
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (typeof this.opts.offsetSmall === 'string') {
|
|
747
|
+
const offsetSmallElm = document.querySelector(this.opts.offsetSmall)
|
|
748
|
+
this.opts.offsetSmall = offsetSmallElm ? offsetSmallElm.offsetTop - 1 : 50
|
|
749
|
+
} else if (typeof this.opts.offsetSmall === 'function') {
|
|
750
|
+
this.opts.offsetSmall = this.opts.offsetSmall(this) - 1
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Reset scroll tracking to prevent the scroll-height-change
|
|
754
|
+
// guard from bailing out after content swap
|
|
755
|
+
this.lastKnownScrollY = this.getScrollY()
|
|
756
|
+
this.lastKnownScrollHeight = document.body.scrollHeight
|
|
757
|
+
this.currentScrollY = this.lastKnownScrollY
|
|
758
|
+
this.currentScrollHeight = this.lastKnownScrollHeight
|
|
759
|
+
|
|
760
|
+
// Re-check current state
|
|
761
|
+
this.checkSize(true)
|
|
762
|
+
this.checkBg(true)
|
|
763
|
+
this.checkTop(true)
|
|
764
|
+
}
|
|
765
|
+
|
|
707
766
|
_getOptionsForSection(section, opts) {
|
|
708
767
|
// if section is not a key in opts, return default opts
|
|
709
768
|
if (
|
|
@@ -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() {
|