@brandocms/jupiter 4.0.0-beta.1 → 5.0.0-beta.1
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/README.md +191 -2
- package/package.json +20 -18
- package/src/index.js +10 -10
- package/src/modules/Application/index.js +203 -157
- package/src/modules/Cookies/index.js +34 -55
- package/src/modules/CoverOverlay/index.js +20 -13
- package/src/modules/Dataloader/index.js +71 -24
- package/src/modules/Dataloader/url-sync.js +238 -0
- package/src/modules/Dom/index.js +18 -0
- package/src/modules/DoubleHeader/index.js +571 -0
- package/src/modules/Dropdown/index.js +101 -75
- package/src/modules/EqualHeightElements/index.js +5 -7
- package/src/modules/EqualHeightImages/index.js +7 -2
- package/src/modules/FixedHeader/index.js +60 -30
- package/src/modules/FooterReveal/index.js +3 -3
- package/src/modules/HeroSlider/index.js +207 -91
- package/src/modules/HeroVideo/index.js +15 -27
- package/src/modules/Lazyload/index.js +101 -80
- package/src/modules/Lightbox/index.js +17 -55
- package/src/modules/Links/index.js +54 -49
- package/src/modules/Looper/index.js +1737 -0
- package/src/modules/Marquee/index.js +106 -37
- package/src/modules/MobileMenu/index.js +70 -124
- package/src/modules/Moonwalk/index.js +349 -150
- package/src/modules/Popover/index.js +186 -28
- package/src/modules/Popup/index.js +27 -34
- package/src/modules/StackedBoxes/index.js +3 -3
- package/src/modules/StickyHeader/index.js +364 -155
- package/src/modules/Toggler/index.js +184 -27
- package/src/utils/motion-helpers.js +330 -0
- package/types/index.d.ts +1 -30
- package/types/modules/Application/index.d.ts +6 -6
- package/types/modules/Breakpoints/index.d.ts +2 -0
- package/types/modules/Dataloader/index.d.ts +5 -2
- package/types/modules/Dataloader/url-sync.d.ts +36 -0
- package/types/modules/Dom/index.d.ts +7 -0
- package/types/modules/DoubleHeader/index.d.ts +63 -0
- package/types/modules/Dropdown/index.d.ts +7 -30
- package/types/modules/EqualHeightImages/index.d.ts +1 -1
- package/types/modules/FixedHeader/index.d.ts +1 -1
- package/types/modules/Lazyload/index.d.ts +9 -9
- package/types/modules/Lightbox/index.d.ts +0 -5
- package/types/modules/Looper/index.d.ts +127 -0
- package/types/modules/Moonwalk/index.d.ts +6 -15
- package/types/modules/Parallax/index.d.ts +10 -32
- package/types/modules/Popover/index.d.ts +12 -0
- package/types/modules/Popup/index.d.ts +6 -19
- package/types/modules/ScrollSpy/index.d.ts +1 -1
- package/types/modules/StickyHeader/index.d.ts +171 -14
- package/types/modules/Toggler/index.d.ts +24 -2
|
@@ -37,6 +37,7 @@ const DEFAULT_OPTIONS = {
|
|
|
37
37
|
minSize: 40,
|
|
38
38
|
updateSizes: true,
|
|
39
39
|
registerCallback: true,
|
|
40
|
+
target: null,
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -51,6 +52,20 @@ export default class Lazyload {
|
|
|
51
52
|
constructor(app, opts = {}) {
|
|
52
53
|
this.app = app
|
|
53
54
|
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
55
|
+
this.target = this.opts.target || document.body
|
|
56
|
+
this.resizePending = new Map()
|
|
57
|
+
this.rafId = null
|
|
58
|
+
|
|
59
|
+
// Create reusable MutationObserver for reveal handling
|
|
60
|
+
this.srcsetReadyObserver = new MutationObserver(mutations => {
|
|
61
|
+
mutations.forEach(record => {
|
|
62
|
+
if (record.type === 'attributes' && record.attributeName === 'data-ll-srcset-ready') {
|
|
63
|
+
this.revealPicture(record.target)
|
|
64
|
+
this.revealObserver.unobserve(record.target)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
54
69
|
this.initialize()
|
|
55
70
|
|
|
56
71
|
if (this.opts.registerCallback) {
|
|
@@ -69,35 +84,30 @@ export default class Lazyload {
|
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
initialize() {
|
|
72
|
-
// initialize
|
|
73
|
-
this.
|
|
87
|
+
// initialize ResizeObserver for images with data-sizes="auto"
|
|
88
|
+
this.initializeResizeObserver()
|
|
74
89
|
// look for lazyload sections. if we find, add an observer that triggers
|
|
75
90
|
// lazyload for all images within.
|
|
76
91
|
this.initializeSections()
|
|
77
92
|
|
|
78
93
|
// if we have native lazyload, use it.
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
) {
|
|
83
|
-
const lazyImages = document.querySelectorAll('[data-ll-image]')
|
|
84
|
-
lazyImages.forEach((img) => {
|
|
94
|
+
if ('loading' in HTMLImageElement.prototype && this.opts.useNativeLazyloadIfAvailable) {
|
|
95
|
+
const lazyImages = this.target.querySelectorAll('[data-ll-image]')
|
|
96
|
+
lazyImages.forEach(img => {
|
|
85
97
|
img.setAttribute('loading', 'lazy')
|
|
86
98
|
this.swapImage(img)
|
|
87
99
|
})
|
|
88
100
|
|
|
89
|
-
const lazyPictures =
|
|
90
|
-
lazyPictures.forEach(
|
|
91
|
-
picture
|
|
92
|
-
.querySelectorAll('img')
|
|
93
|
-
.forEach((img) => img.setAttribute('loading', 'lazy'))
|
|
101
|
+
const lazyPictures = this.target.querySelectorAll('[data-ll-srcset]')
|
|
102
|
+
lazyPictures.forEach(picture => {
|
|
103
|
+
picture.querySelectorAll('img').forEach(img => img.setAttribute('loading', 'lazy'))
|
|
94
104
|
this.swapPicture(picture)
|
|
95
105
|
})
|
|
96
106
|
|
|
97
107
|
return
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
this.lazyPictures =
|
|
110
|
+
this.lazyPictures = this.target.querySelectorAll('[data-ll-srcset]')
|
|
101
111
|
|
|
102
112
|
this.loadObserver = new IntersectionObserver(
|
|
103
113
|
this.handleLoadEntries.bind(this),
|
|
@@ -117,7 +127,7 @@ export default class Lazyload {
|
|
|
117
127
|
this.opts.intersectionObserverConfig
|
|
118
128
|
)
|
|
119
129
|
|
|
120
|
-
this.lazyImages =
|
|
130
|
+
this.lazyImages = this.target.querySelectorAll('[data-ll-image]')
|
|
121
131
|
this.lazyImages.forEach((img, idx) => {
|
|
122
132
|
img.setAttribute('data-ll-blurred', '')
|
|
123
133
|
img.setAttribute('data-ll-idx', idx)
|
|
@@ -130,7 +140,7 @@ export default class Lazyload {
|
|
|
130
140
|
this.lazyPictures.forEach((picture, idx) => {
|
|
131
141
|
if (setAttrs) {
|
|
132
142
|
picture.setAttribute('data-ll-srcset-initialized', '')
|
|
133
|
-
picture.querySelectorAll('img:not([data-ll-loaded])').forEach(
|
|
143
|
+
picture.querySelectorAll('img:not([data-ll-loaded])').forEach(img => {
|
|
134
144
|
img.setAttribute('data-ll-blurred', '')
|
|
135
145
|
img.setAttribute('data-ll-idx', idx)
|
|
136
146
|
img.style.setProperty('--ll-idx', idx)
|
|
@@ -142,45 +152,82 @@ export default class Lazyload {
|
|
|
142
152
|
|
|
143
153
|
forceLoad($container = document.body) {
|
|
144
154
|
const images = Dom.all($container, '[data-ll-image]')
|
|
145
|
-
images.forEach(
|
|
155
|
+
images.forEach(img => this.swapImage(img))
|
|
146
156
|
|
|
147
157
|
const pictures = Dom.all($container, '[data-ll-srcset]')
|
|
148
|
-
pictures.forEach(
|
|
158
|
+
pictures.forEach(picture => this.revealPicture(picture))
|
|
149
159
|
}
|
|
150
160
|
|
|
151
|
-
|
|
152
|
-
if (this.opts.updateSizes) {
|
|
153
|
-
|
|
154
|
-
this.autoSizes()
|
|
155
|
-
window.addEventListener(Events.APPLICATION_RESIZE, () => this.autoSizes())
|
|
161
|
+
initializeResizeObserver() {
|
|
162
|
+
if (!this.opts.updateSizes) {
|
|
163
|
+
return
|
|
156
164
|
}
|
|
157
|
-
}
|
|
158
165
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
// Use ResizeObserver to watch images with data-sizes="auto"
|
|
167
|
+
// This eliminates layout thrashing from repeated offsetWidth reads
|
|
168
|
+
this.sizeObserver = new ResizeObserver(entries => {
|
|
169
|
+
entries.forEach(entry => {
|
|
170
|
+
const img = entry.target
|
|
171
|
+
// Use contentBoxSize for better performance (avoids layout queries)
|
|
172
|
+
let width = entry.borderBoxSize?.[0]?.inlineSize || entry.contentRect.width
|
|
173
|
+
|
|
174
|
+
// Round to prevent decimal fluctuations causing loops
|
|
175
|
+
width = Math.round(width)
|
|
176
|
+
|
|
177
|
+
// Fallback to minSize if element is too small
|
|
178
|
+
if (width < this.opts.minSize) {
|
|
179
|
+
width = this.opts.minSize
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Only queue update if width actually changed from current sizes attribute
|
|
183
|
+
const currentSizes = img.getAttribute('sizes')
|
|
184
|
+
const expectedSizes = `${width}px`
|
|
185
|
+
|
|
186
|
+
if (currentSizes !== expectedSizes) {
|
|
187
|
+
// Batch updates using RAF to avoid layout thrashing
|
|
188
|
+
this.resizePending.set(img, width)
|
|
189
|
+
|
|
190
|
+
if (!this.rafId) {
|
|
191
|
+
this.rafId = requestAnimationFrame(() => {
|
|
192
|
+
this.flushSizeUpdates()
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Observe all images with data-sizes="auto" within the target container
|
|
200
|
+
const autoSizesImages = Dom.all(this.target, '[data-sizes="auto"]')
|
|
201
|
+
|
|
202
|
+
// Deduplicate in case of multiple Lazyload instances
|
|
203
|
+
const uniqueImages = new Set(autoSizesImages)
|
|
204
|
+
|
|
205
|
+
uniqueImages.forEach(img => {
|
|
206
|
+
this.sizeObserver.observe(img)
|
|
171
207
|
})
|
|
172
208
|
}
|
|
173
209
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
flushSizeUpdates() {
|
|
211
|
+
// Batch all size updates together to minimize reflows
|
|
212
|
+
this.resizePending.forEach((width, img) => {
|
|
213
|
+
const currentSizes = img.getAttribute('sizes')
|
|
214
|
+
const newSizes = `${Math.round(width)}px`
|
|
177
215
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
216
|
+
// Only update if value actually changed to prevent resize loops
|
|
217
|
+
if (currentSizes !== newSizes) {
|
|
218
|
+
img.setAttribute('sizes', newSizes)
|
|
219
|
+
if (img.parentNode) {
|
|
220
|
+
Array.from(Dom.all(img.parentNode, 'source')).forEach(source => {
|
|
221
|
+
if (source.getAttribute('sizes') !== newSizes) {
|
|
222
|
+
source.setAttribute('sizes', newSizes)
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
})
|
|
182
228
|
|
|
183
|
-
|
|
229
|
+
this.resizePending.clear()
|
|
230
|
+
this.rafId = null
|
|
184
231
|
}
|
|
185
232
|
|
|
186
233
|
initializeSections() {
|
|
@@ -189,12 +236,12 @@ export default class Lazyload {
|
|
|
189
236
|
const sectionObserver = (section, children) => {
|
|
190
237
|
const imagesInSection = Dom.all(section, 'img')
|
|
191
238
|
return new IntersectionObserver((entries, self) => {
|
|
192
|
-
entries.forEach(
|
|
239
|
+
entries.forEach(entry => {
|
|
193
240
|
if (entry.isIntersecting || entry.intersectionRatio > 0) {
|
|
194
241
|
imagesAreLoaded(imagesInSection, true).then(() => {
|
|
195
242
|
dispatchElementEvent(section, Events.SECTION_LAZYLOADED)
|
|
196
243
|
})
|
|
197
|
-
children.forEach(
|
|
244
|
+
children.forEach(picture => {
|
|
198
245
|
this.loadPicture(picture)
|
|
199
246
|
this.loadObserver.unobserve(picture)
|
|
200
247
|
})
|
|
@@ -204,7 +251,7 @@ export default class Lazyload {
|
|
|
204
251
|
}, this.opts.intersectionObserverConfig)
|
|
205
252
|
}
|
|
206
253
|
|
|
207
|
-
sections.forEach(
|
|
254
|
+
sections.forEach(section => {
|
|
208
255
|
const children = section.querySelectorAll('picture')
|
|
209
256
|
const obs = sectionObserver(section, children)
|
|
210
257
|
obs.observe(section)
|
|
@@ -214,7 +261,7 @@ export default class Lazyload {
|
|
|
214
261
|
|
|
215
262
|
// we load the picture a ways before it enters the viewport
|
|
216
263
|
handleLoadEntries(elements) {
|
|
217
|
-
elements.forEach(
|
|
264
|
+
elements.forEach(item => {
|
|
218
265
|
if (item.isIntersecting || item.intersectionRatio > 0) {
|
|
219
266
|
const picture = item.target
|
|
220
267
|
this.loadPicture(picture)
|
|
@@ -225,26 +272,15 @@ export default class Lazyload {
|
|
|
225
272
|
|
|
226
273
|
// we reveal the picture when it enters the viewport
|
|
227
274
|
handleRevealEntries(elements) {
|
|
228
|
-
|
|
229
|
-
mutations.forEach((record) => {
|
|
230
|
-
if (
|
|
231
|
-
record.type === 'attributes' &&
|
|
232
|
-
record.attributeName === 'data-ll-srcset-ready'
|
|
233
|
-
) {
|
|
234
|
-
this.revealPicture(record.target)
|
|
235
|
-
this.revealObserver.unobserve(record.target)
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
elements.forEach((item) => {
|
|
275
|
+
elements.forEach(item => {
|
|
241
276
|
if (item.isIntersecting || item.intersectionRatio > 0) {
|
|
242
277
|
const picture = item.target
|
|
243
278
|
const ready = item.target.hasAttribute('data-ll-srcset-ready')
|
|
244
279
|
if (!ready) {
|
|
245
280
|
// element is not loaded, observe the picture and wait for
|
|
246
281
|
// `data-ll-srcset-ready` before revealing
|
|
247
|
-
|
|
282
|
+
// Use reusable MutationObserver to prevent memory leaks
|
|
283
|
+
this.srcsetReadyObserver.observe(picture, { attributes: true })
|
|
248
284
|
} else {
|
|
249
285
|
this.revealPicture(picture)
|
|
250
286
|
this.revealObserver.unobserve(item.target)
|
|
@@ -276,23 +312,8 @@ export default class Lazyload {
|
|
|
276
312
|
const img = picture.querySelector('img')
|
|
277
313
|
|
|
278
314
|
const onload = () => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.app.browser === 'firefox'
|
|
282
|
-
) {
|
|
283
|
-
// set sizes attribute on load again,
|
|
284
|
-
// since firefox sometimes is a bit slow to
|
|
285
|
-
// get the actual image width
|
|
286
|
-
const width = this.getWidth(img)
|
|
287
|
-
|
|
288
|
-
img.setAttribute('sizes', `${width}px`)
|
|
289
|
-
if (img.parentNode) {
|
|
290
|
-
Array.from(Dom.all(img.parentNode, 'source')).forEach((source) =>
|
|
291
|
-
source.setAttribute('sizes', `${width}px`)
|
|
292
|
-
)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
315
|
+
// ResizeObserver now handles size updates automatically,
|
|
316
|
+
// including Firefox's delayed dimension calculation
|
|
296
317
|
img.removeAttribute('data-ll-placeholder')
|
|
297
318
|
img.removeAttribute('data-ll-blurred')
|
|
298
319
|
img.removeAttribute('data-ll-loading')
|
|
@@ -336,7 +357,7 @@ export default class Lazyload {
|
|
|
336
357
|
}
|
|
337
358
|
|
|
338
359
|
lazyloadImages(elements) {
|
|
339
|
-
elements.forEach(
|
|
360
|
+
elements.forEach(item => {
|
|
340
361
|
if (item.isIntersecting || item.intersectionRatio > 0) {
|
|
341
362
|
const image = item.target
|
|
342
363
|
this.swapImage(image)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { gsap } from 'gsap/all'
|
|
1
|
+
import { animate } from 'motion'
|
|
3
2
|
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
4
3
|
import imageIsLoaded from '../../utils/imageIsLoaded'
|
|
5
4
|
import Dom from '../Dom'
|
|
5
|
+
import { set, PausedTimeline } from '../../utils/motion-helpers'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {Object} LightboxElements
|
|
@@ -16,7 +16,6 @@ import Dom from '../Dom'
|
|
|
16
16
|
* @typedef {Object} LightboxOptions
|
|
17
17
|
* @property {boolean} [captions=false] - Enable captions
|
|
18
18
|
* @property {boolean} [numbers=false] - Enable index numbers
|
|
19
|
-
* @property {boolean} [swipe=true] - Enable swipe - this breaks native zoom
|
|
20
19
|
* @property {string|boolean} [trigger=false] - Selector for trigger element to open the lightbox
|
|
21
20
|
* @property {LightboxElements} [elements] - Custom elements configuration
|
|
22
21
|
* @property {Function} [onClick] - Click handler for lightbox
|
|
@@ -41,9 +40,6 @@ const DEFAULT_OPTIONS = {
|
|
|
41
40
|
/* enable index numbers */
|
|
42
41
|
numbers: false,
|
|
43
42
|
|
|
44
|
-
/* enable swipe — this breaks native zoom! */
|
|
45
|
-
swipe: true,
|
|
46
|
-
|
|
47
43
|
/* set to a selector if you want a specific trigger element to open the box */
|
|
48
44
|
trigger: false,
|
|
49
45
|
|
|
@@ -128,23 +124,17 @@ const DEFAULT_OPTIONS = {
|
|
|
128
124
|
onOpen: (h) => {
|
|
129
125
|
h.app.scrollLock()
|
|
130
126
|
|
|
131
|
-
|
|
132
|
-
duration: 0.5,
|
|
133
|
-
opacity: 1,
|
|
134
|
-
})
|
|
127
|
+
animate(h.elements.wrapper, { opacity: 1 }, { duration: 0.5 })
|
|
135
128
|
},
|
|
136
129
|
|
|
137
130
|
onAfterClose: () => {},
|
|
138
131
|
|
|
139
132
|
onClose: (h) => {
|
|
140
133
|
if (h.opts.captions) {
|
|
141
|
-
|
|
142
|
-
duration: 0.45,
|
|
143
|
-
opacity: 0,
|
|
144
|
-
})
|
|
134
|
+
animate(h.elements.caption, { opacity: 0 }, { duration: 0.45 })
|
|
145
135
|
}
|
|
146
136
|
|
|
147
|
-
|
|
137
|
+
animate(
|
|
148
138
|
[
|
|
149
139
|
h.elements.imgWrapper,
|
|
150
140
|
h.elements.nextArrow,
|
|
@@ -152,21 +142,14 @@ const DEFAULT_OPTIONS = {
|
|
|
152
142
|
h.elements.close,
|
|
153
143
|
h.elements.dots,
|
|
154
144
|
],
|
|
155
|
-
{
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
h.app.scrollRelease()
|
|
164
|
-
h.destroy()
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
},
|
|
168
|
-
}
|
|
169
|
-
)
|
|
145
|
+
{ opacity: 0 },
|
|
146
|
+
{ duration: 0.5 }
|
|
147
|
+
).finished.then(() => {
|
|
148
|
+
animate(h.elements.wrapper, { opacity: 0 }, { duration: 0.45 }).finished.then(() => {
|
|
149
|
+
h.app.scrollRelease()
|
|
150
|
+
h.destroy()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
170
153
|
},
|
|
171
154
|
}
|
|
172
155
|
|
|
@@ -191,8 +174,8 @@ export default class Lightbox {
|
|
|
191
174
|
this.firstTransition = true
|
|
192
175
|
this.previousCaption = null
|
|
193
176
|
this.timelines = {
|
|
194
|
-
caption:
|
|
195
|
-
image:
|
|
177
|
+
caption: new PausedTimeline(),
|
|
178
|
+
image: new PausedTimeline(),
|
|
196
179
|
}
|
|
197
180
|
|
|
198
181
|
this.lightboxes.forEach((lightbox) => {
|
|
@@ -294,7 +277,8 @@ export default class Lightbox {
|
|
|
294
277
|
|
|
295
278
|
this.sections[section].forEach((img, x) => {
|
|
296
279
|
const imgElement = document.createElement('img')
|
|
297
|
-
|
|
280
|
+
set(imgElement, { opacity: 0 })
|
|
281
|
+
imgElement.style.visibility = 'hidden'
|
|
298
282
|
imgElement.classList.add('lightbox-image', 'm-lg')
|
|
299
283
|
imgElement.setAttribute('data-idx', x)
|
|
300
284
|
this.elements.imgWrapper.appendChild(imgElement)
|
|
@@ -342,9 +326,6 @@ export default class Lightbox {
|
|
|
342
326
|
document.body.appendChild(this.elements.wrapper)
|
|
343
327
|
|
|
344
328
|
this.setImg(section, index, this.getPrevIdx(section))
|
|
345
|
-
if (this.opts.swipe) {
|
|
346
|
-
this.attachSwiper(section, this.elements.content, index)
|
|
347
|
-
}
|
|
348
329
|
|
|
349
330
|
this.opts.onOpen(this)
|
|
350
331
|
|
|
@@ -501,23 +482,4 @@ export default class Lightbox {
|
|
|
501
482
|
this.opts.onPointerRight(this)
|
|
502
483
|
}
|
|
503
484
|
}
|
|
504
|
-
|
|
505
|
-
attachSwiper(section, el, initialIdx) {
|
|
506
|
-
const hammerManager = new Manager(el)
|
|
507
|
-
const swipeHandler = new Swipe()
|
|
508
|
-
|
|
509
|
-
this.elements.content.setAttribute('data-current-idx', initialIdx)
|
|
510
|
-
|
|
511
|
-
hammerManager.add(swipeHandler)
|
|
512
|
-
|
|
513
|
-
hammerManager.on('swipeleft', () => {
|
|
514
|
-
const index = this.getNextIdx(section)
|
|
515
|
-
this.setImg(section, index)
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
hammerManager.on('swiperight', () => {
|
|
519
|
-
const index = this.getPrevIdx(section)
|
|
520
|
-
this.setImg(section, index)
|
|
521
|
-
})
|
|
522
|
-
}
|
|
523
485
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { animate } from 'motion'
|
|
2
2
|
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
3
|
-
|
|
4
|
-
gsap.registerPlugin(ScrollToPlugin)
|
|
3
|
+
import { set } from '../../utils/motion-helpers'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* @typedef {Object} LinksOptions
|
|
@@ -23,8 +22,7 @@ const DEFAULT_OPTIONS = {
|
|
|
23
22
|
scrollOffsetNav: false,
|
|
24
23
|
mobileMenuDelay: 800,
|
|
25
24
|
openExternalInWindow: true,
|
|
26
|
-
linkQuery:
|
|
27
|
-
'a:not([href^="#"]):not([target="_blank"]):not([data-lightbox]):not(.noanim)',
|
|
25
|
+
linkQuery: 'a:not([href^="#"]):not([target="_blank"]):not([data-lightbox]):not(.noanim)',
|
|
28
26
|
anchorQuery: 'a[href^="#"]:not(.noanim)',
|
|
29
27
|
|
|
30
28
|
onAnchor: (target, links) => {
|
|
@@ -33,11 +31,7 @@ const DEFAULT_OPTIONS = {
|
|
|
33
31
|
const headerHeight = header ? header.clientHeight : 0
|
|
34
32
|
target = { y: target, offsetY: headerHeight }
|
|
35
33
|
}
|
|
36
|
-
links.app.scrollTo(
|
|
37
|
-
target,
|
|
38
|
-
links.opts.scrollDuration,
|
|
39
|
-
links.opts.triggerEvents
|
|
40
|
-
)
|
|
34
|
+
links.app.scrollTo(target, links.opts.scrollDuration, links.opts.triggerEvents)
|
|
41
35
|
},
|
|
42
36
|
|
|
43
37
|
onTransition: (href, app) => {
|
|
@@ -47,49 +41,40 @@ const DEFAULT_OPTIONS = {
|
|
|
47
41
|
const fader = document.querySelector('#fader')
|
|
48
42
|
|
|
49
43
|
if (fader) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
y: 25,
|
|
54
|
-
|
|
55
|
-
}
|
|
44
|
+
set(fader, { display: 'block', opacity: 0 })
|
|
45
|
+
|
|
46
|
+
if (main) {
|
|
47
|
+
animate(main, { y: 25 }, { duration: 0.8, ease: 'easeOut' })
|
|
48
|
+
animate(main, { opacity: 0 }, { duration: 0.2 })
|
|
49
|
+
}
|
|
56
50
|
|
|
57
51
|
if (header) {
|
|
58
|
-
|
|
52
|
+
animate(header, { opacity: 0 }, { duration: 0.2 })
|
|
59
53
|
}
|
|
60
54
|
|
|
61
55
|
if (footer) {
|
|
62
|
-
|
|
56
|
+
animate(footer, { opacity: 0 }, { duration: 0.2 })
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
opacity: 1,
|
|
68
|
-
onComplete: () => {
|
|
69
|
-
window.location = href
|
|
70
|
-
},
|
|
59
|
+
animate(fader, { opacity: 1 }, { duration: 0.2 }).finished.then(() => {
|
|
60
|
+
window.location = href
|
|
71
61
|
})
|
|
72
62
|
} else {
|
|
73
|
-
|
|
74
|
-
duration: 0.8,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
63
|
+
if (main) {
|
|
64
|
+
animate(main, { y: 25 }, { duration: 0.8, ease: 'easeOut' })
|
|
65
|
+
animate(main, { opacity: 0 }, { duration: 0.2 })
|
|
66
|
+
}
|
|
78
67
|
|
|
79
68
|
if (header) {
|
|
80
|
-
|
|
69
|
+
animate(header, { opacity: 0 }, { duration: 0.2 })
|
|
81
70
|
}
|
|
82
71
|
|
|
83
72
|
if (footer) {
|
|
84
|
-
|
|
73
|
+
animate(footer, { opacity: 0 }, { duration: 0.2 })
|
|
85
74
|
}
|
|
86
75
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
opacity: 0,
|
|
90
|
-
onComplete: () => {
|
|
91
|
-
window.location = href
|
|
92
|
-
},
|
|
76
|
+
animate(main, { opacity: 0 }, { duration: 0.2 }).finished.then(() => {
|
|
77
|
+
window.location = href
|
|
93
78
|
})
|
|
94
79
|
}
|
|
95
80
|
},
|
|
@@ -119,7 +104,7 @@ export default class Links {
|
|
|
119
104
|
bindHeroLink() {
|
|
120
105
|
const el = document.querySelector('[data-link-to-content]')
|
|
121
106
|
if (el) {
|
|
122
|
-
el.addEventListener('click',
|
|
107
|
+
el.addEventListener('click', e => {
|
|
123
108
|
const dataTarget = document.querySelector('main')
|
|
124
109
|
e.preventDefault()
|
|
125
110
|
if (dataTarget) {
|
|
@@ -131,8 +116,8 @@ export default class Links {
|
|
|
131
116
|
|
|
132
117
|
bindAnchors(anchors) {
|
|
133
118
|
let wait = false
|
|
134
|
-
Array.from(anchors).forEach(
|
|
135
|
-
anchor.addEventListener('click',
|
|
119
|
+
Array.from(anchors).forEach(anchor => {
|
|
120
|
+
anchor.addEventListener('click', e => {
|
|
136
121
|
e.preventDefault()
|
|
137
122
|
const href = anchor.getAttribute('href')
|
|
138
123
|
if (href === '#') {
|
|
@@ -187,21 +172,21 @@ export default class Links {
|
|
|
187
172
|
bindLinks(links) {
|
|
188
173
|
const loadingContainer = document.querySelector('.loading-container')
|
|
189
174
|
|
|
190
|
-
Array.from(links).forEach(
|
|
175
|
+
Array.from(links).forEach(link => {
|
|
191
176
|
const href = link.getAttribute('href')
|
|
192
177
|
if (!href || href === '#' || href.startsWith('javascript:')) {
|
|
193
178
|
return // Skip empty, anchor, or JS-based links
|
|
194
179
|
}
|
|
195
180
|
|
|
196
181
|
// Determine the normalized hostname of the current document.
|
|
197
|
-
const normalizedCurrentHost = this.normalizeHostname(
|
|
198
|
-
document.location.hostname
|
|
199
|
-
)
|
|
182
|
+
const normalizedCurrentHost = this.normalizeHostname(document.location.hostname)
|
|
200
183
|
|
|
201
184
|
// For absolute URLs, use the URL constructor.
|
|
202
185
|
let linkHostname
|
|
186
|
+
let linkUrl
|
|
203
187
|
try {
|
|
204
|
-
|
|
188
|
+
linkUrl = new URL(href, document.location.href)
|
|
189
|
+
linkHostname = linkUrl.hostname
|
|
205
190
|
} catch (error) {
|
|
206
191
|
// If URL construction fails, assume it's not internal.
|
|
207
192
|
console.warn(`Failed to parse URL for href "${href}":`, error) // Log errors for debugging
|
|
@@ -216,7 +201,7 @@ export default class Links {
|
|
|
216
201
|
link.setAttribute('target', '_blank')
|
|
217
202
|
}
|
|
218
203
|
|
|
219
|
-
link.addEventListener('click',
|
|
204
|
+
link.addEventListener('click', e => {
|
|
220
205
|
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
|
221
206
|
return
|
|
222
207
|
}
|
|
@@ -225,9 +210,29 @@ export default class Links {
|
|
|
225
210
|
loadingContainer.style.display = 'none'
|
|
226
211
|
}
|
|
227
212
|
|
|
228
|
-
if (internalLink) {
|
|
229
|
-
|
|
230
|
-
|
|
213
|
+
if (internalLink && linkUrl) {
|
|
214
|
+
// Check if we're navigating to the same page with just a different hash
|
|
215
|
+
const currentUrl = new URL(window.location.href)
|
|
216
|
+
const isSamePage = linkUrl.pathname === currentUrl.pathname &&
|
|
217
|
+
linkUrl.search === currentUrl.search
|
|
218
|
+
|
|
219
|
+
if (isSamePage && linkUrl.hash) {
|
|
220
|
+
// Same page, just different hash - treat as anchor navigation
|
|
221
|
+
e.preventDefault()
|
|
222
|
+
const target = linkUrl.hash
|
|
223
|
+
const element = document.querySelector(target)
|
|
224
|
+
if (element) {
|
|
225
|
+
this.opts.onAnchor(element, this)
|
|
226
|
+
history.pushState({}, '', href)
|
|
227
|
+
}
|
|
228
|
+
} else if (isSamePage && !linkUrl.hash && !currentUrl.hash) {
|
|
229
|
+
// Same exact page without hash - do nothing
|
|
230
|
+
e.preventDefault()
|
|
231
|
+
} else {
|
|
232
|
+
// Different page or same page with/without hash change - do transition
|
|
233
|
+
e.preventDefault()
|
|
234
|
+
this.opts.onTransition(href, this.app)
|
|
235
|
+
}
|
|
231
236
|
}
|
|
232
237
|
})
|
|
233
238
|
})
|