@brandocms/jupiter 5.0.0-beta.12 → 5.0.0-beta.14

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.
@@ -14,12 +14,11 @@ import * as Events from '../../events'
14
14
  * @typedef {Object} LazyloadOptions
15
15
  * @property {IntersectionObserverConfig} [revealIntersectionObserverConfig] - Configuration for the reveal intersection observer
16
16
  * @property {IntersectionObserverConfig} [loadIntersectionObserverConfig] - Configuration for the load intersection observer
17
- * @property {IntersectionObserverConfig} [intersectionObserverConfig] - Configuration for general intersection observers
18
17
  * @property {boolean} [useNativeLazyloadIfAvailable=true] - Whether to use native lazyloading if available
19
- * @property {string} [mode='default'] - Lazyload mode
20
18
  * @property {number} [minSize=40] - Minimum size for auto sizing
21
19
  * @property {boolean} [updateSizes=true] - Whether to update sizes attribute
22
20
  * @property {boolean} [registerCallback=true] - Whether to register a callback for APPLICATION_REVEALED event
21
+ * @property {HTMLElement|null} [target=null] - Container element to scope lazyloading to. Defaults to document.body
23
22
  */
24
23
 
25
24
  /** @type {LazyloadOptions} */
@@ -33,7 +32,6 @@ const DEFAULT_OPTIONS = {
33
32
  threshold: 0.0,
34
33
  },
35
34
  useNativeLazyloadIfAvailable: true,
36
- mode: 'default',
37
35
  minSize: 40,
38
36
  updateSizes: true,
39
37
  registerCallback: true,
@@ -145,9 +143,6 @@ export default class Lazyload {
145
143
  initialize() {
146
144
  // initialize ResizeObserver for images with data-sizes="auto"
147
145
  this.initializeResizeObserver()
148
- // look for lazyload sections. if we find, add an observer that triggers
149
- // lazyload for all images within.
150
- this.initializeSections()
151
146
 
152
147
  // if we have native lazyload, use it.
153
148
  if ('loading' in HTMLImageElement.prototype && this.opts.useNativeLazyloadIfAvailable) {
@@ -180,10 +175,14 @@ export default class Lazyload {
180
175
 
181
176
  this.initObserver(this.loadObserver)
182
177
 
178
+ // look for lazyload sections. if we find, add an observer that triggers
179
+ // lazyload for all images within.
180
+ this.initializeSections()
181
+
183
182
  // Deprecate data-ll-image sometime
184
183
  this.imageObserver = new IntersectionObserver(
185
184
  this.lazyloadImages.bind(this),
186
- this.opts.intersectionObserverConfig
185
+ this.opts.loadIntersectionObserverConfig
187
186
  )
188
187
 
189
188
  this.lazyImages = this.target.querySelectorAll('[data-ll-image]')
@@ -204,6 +203,13 @@ export default class Lazyload {
204
203
  })
205
204
  }
206
205
 
206
+ /**
207
+ * Force load all lazyload elements within a container, bypassing intersection observers.
208
+ * Used by modules like Looper when dynamically adding content that needs immediate loading.
209
+ * @param {HTMLElement} [$container=document.body] - Container to search for lazyload elements
210
+ * @param {Object} [options]
211
+ * @param {boolean} [options.reveal=true] - Whether to also reveal (set data-ll-loaded) after loading
212
+ */
207
213
  forceLoad($container = document.body, { reveal = true } = {}) {
208
214
  const images = Dom.all($container, '[data-ll-image]')
209
215
  images.forEach(img => this.swapImage(img))
@@ -215,6 +221,29 @@ export default class Lazyload {
215
221
  this.revealPicture(picture)
216
222
  }
217
223
  })
224
+
225
+ // Set sizes on dynamically added images with data-sizes="auto"
226
+ // and register them with the ResizeObserver for future updates
227
+ if (this.opts.updateSizes && this.sizeObserver) {
228
+ const autoSizesImages = Dom.all($container, '[data-sizes="auto"]')
229
+ autoSizesImages.forEach(img => {
230
+ let width = Math.round(img.offsetWidth)
231
+ if (width < this.opts.minSize) {
232
+ width = this.opts.minSize
233
+ }
234
+
235
+ const sizes = `${width}px`
236
+ img.setAttribute('sizes', sizes)
237
+
238
+ if (img.parentNode) {
239
+ Dom.all(img.parentNode, 'source').forEach(source => {
240
+ source.setAttribute('sizes', sizes)
241
+ })
242
+ }
243
+
244
+ this.sizeObserver.observe(img)
245
+ })
246
+ }
218
247
  }
219
248
 
220
249
  initializeResizeObserver() {
@@ -276,7 +305,7 @@ export default class Lazyload {
276
305
  if (currentSizes !== newSizes) {
277
306
  img.setAttribute('sizes', newSizes)
278
307
  if (img.parentNode) {
279
- Array.from(Dom.all(img.parentNode, 'source')).forEach(source => {
308
+ Dom.all(img.parentNode, 'source').forEach(source => {
280
309
  if (source.getAttribute('sizes') !== newSizes) {
281
310
  source.setAttribute('sizes', newSizes)
282
311
  }
@@ -290,37 +319,35 @@ export default class Lazyload {
290
319
  }
291
320
 
292
321
  initializeSections() {
293
- const sections = document.querySelectorAll('[data-lazyload-section]')
294
- if (sections) {
295
- const sectionObserver = (section, children) => {
296
- const imagesInSection = Dom.all(section, 'img')
297
- return new IntersectionObserver((entries, self) => {
298
- entries.forEach(entry => {
299
- if (entry.isIntersecting || entry.intersectionRatio > 0) {
300
- imagesAreLoaded(imagesInSection, true).then(() => {
301
- dispatchElementEvent(section, Events.SECTION_LAZYLOADED)
302
- })
303
- children.forEach(picture => {
304
- this.loadPicture(picture)
305
- this.loadObserver.unobserve(picture)
306
- })
307
- self.unobserve(section)
308
- }
309
- })
310
- }, this.opts.intersectionObserverConfig)
311
- }
312
-
313
- sections.forEach(section => {
314
- const children = section.querySelectorAll('picture')
315
- const obs = sectionObserver(section, children)
316
- obs.observe(section)
317
- })
322
+ const sections = this.target.querySelectorAll('[data-lazyload-section]')
323
+
324
+ const sectionObserver = (section, children) => {
325
+ const imagesInSection = Dom.all(section, 'img')
326
+ return new IntersectionObserver((entries, self) => {
327
+ entries.forEach(entry => {
328
+ if (entry.isIntersecting || entry.intersectionRatio > 0) {
329
+ imagesAreLoaded(imagesInSection, true).then(() => {
330
+ dispatchElementEvent(section, Events.SECTION_LAZYLOADED)
331
+ })
332
+ children.forEach(picture => {
333
+ this.loadPicture(picture)
334
+ this.loadObserver.unobserve(picture)
335
+ })
336
+ self.unobserve(section)
337
+ }
338
+ })
339
+ }, this.opts.loadIntersectionObserverConfig)
318
340
  }
341
+
342
+ sections.forEach(section => {
343
+ const children = section.querySelectorAll('picture')
344
+ const obs = sectionObserver(section, children)
345
+ obs.observe(section)
346
+ })
319
347
  }
320
348
 
321
- // we load the picture a ways before it enters the viewport
322
- handleLoadEntries(elements) {
323
- elements.forEach(item => {
349
+ handleLoadEntries(entries) {
350
+ entries.forEach(item => {
324
351
  if (item.isIntersecting || item.intersectionRatio > 0) {
325
352
  const picture = item.target
326
353
  this.loadPicture(picture)
@@ -329,9 +356,8 @@ export default class Lazyload {
329
356
  })
330
357
  }
331
358
 
332
- // we reveal the picture when it enters the viewport
333
- handleRevealEntries(elements) {
334
- elements.forEach(item => {
359
+ handleRevealEntries(entries) {
360
+ entries.forEach(item => {
335
361
  if (item.isIntersecting || item.intersectionRatio > 0) {
336
362
  const picture = item.target
337
363
  const ready = item.target.hasAttribute('data-ll-srcset-ready')
@@ -380,7 +406,7 @@ export default class Lazyload {
380
406
  picture.setAttribute('data-ll-srcset-ready', '')
381
407
  }
382
408
 
383
- img.addEventListener('load', onload, false)
409
+ img.addEventListener('load', onload, { once: true })
384
410
  img.setAttribute('data-ll-loading', '')
385
411
 
386
412
  if (img.dataset.src) {
@@ -391,12 +417,6 @@ export default class Lazyload {
391
417
  img.setAttribute('srcset', img.dataset.srcset)
392
418
  }
393
419
 
394
- if (this.app.featureTests.results.ie11) {
395
- if (window.picturefill) {
396
- window.picturefill({ reevaluate: true })
397
- }
398
- }
399
-
400
420
  // safari sometimes caches, so force load
401
421
  if (img.complete) {
402
422
  onload()
@@ -405,7 +425,10 @@ export default class Lazyload {
405
425
  dispatchElementEvent(img, Events.IMAGE_LAZYLOADED)
406
426
  }
407
427
 
408
- /* reveal by just setting `data-ll-loaded` */
428
+ /**
429
+ * Reveal a picture element by setting `data-ll-loaded` on its img child.
430
+ * @param {HTMLElement} picture - The picture element to reveal
431
+ */
409
432
  revealPicture(picture) {
410
433
  const img = picture.querySelector('img')
411
434
  if (img.hasAttribute('data-ll-loaded')) {
@@ -415,8 +438,33 @@ export default class Lazyload {
415
438
  dispatchElementEvent(img, Events.IMAGE_REVEALED)
416
439
  }
417
440
 
418
- lazyloadImages(elements) {
419
- elements.forEach(item => {
441
+ /**
442
+ * Swap source attributes on a picture element for the native lazyload path.
443
+ * Copies data-srcset to srcset on all sources and the img element.
444
+ * @param {HTMLElement} picture - The picture element to swap
445
+ */
446
+ swapPicture(picture) {
447
+ const sources = picture.querySelectorAll('source')
448
+ sources.forEach(source => {
449
+ if (source.hasAttribute('data-srcset')) {
450
+ source.setAttribute('srcset', source.dataset.srcset)
451
+ }
452
+ })
453
+
454
+ const img = picture.querySelector('img')
455
+ if (img) {
456
+ if (img.dataset.src) {
457
+ img.setAttribute('src', img.dataset.src)
458
+ }
459
+ if (img.dataset.srcset) {
460
+ img.setAttribute('srcset', img.dataset.srcset)
461
+ }
462
+ img.setAttribute('data-ll-loaded', '')
463
+ }
464
+ }
465
+
466
+ lazyloadImages(entries) {
467
+ entries.forEach(item => {
420
468
  if (item.isIntersecting || item.intersectionRatio > 0) {
421
469
  const image = item.target
422
470
  this.swapImage(image)
@@ -429,4 +477,22 @@ export default class Lazyload {
429
477
  image.src = image.dataset.src
430
478
  image.setAttribute('data-ll-loaded', '')
431
479
  }
480
+
481
+ /**
482
+ * Destroy the Lazyload instance, disconnecting all observers and freeing resources.
483
+ */
484
+ destroy() {
485
+ this.srcsetReadyObserver?.disconnect()
486
+ this.loadObserver?.disconnect()
487
+ this.revealObserver?.disconnect()
488
+ this.imageObserver?.disconnect()
489
+ this.sizeObserver?.disconnect()
490
+
491
+ if (this.rafId) {
492
+ cancelAnimationFrame(this.rafId)
493
+ this.rafId = null
494
+ }
495
+
496
+ this.resizePending.clear()
497
+ }
432
498
  }
@@ -29,7 +29,7 @@ const DEFAULT_OPTIONS = {
29
29
  if (links.opts.scrollOffsetNav) {
30
30
  const header = document.querySelector('header[data-nav]')
31
31
  const headerHeight = header ? header.clientHeight : 0
32
- target = { y: target, offsetY: headerHeight }
32
+ target = { y: target, offsetY: -headerHeight }
33
33
  }
34
34
  links.app.scrollTo(target, links.opts.scrollDuration, links.opts.triggerEvents)
35
35
  },