@brandocms/jupiter 3.55.0 → 4.0.0-beta.2

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.
Files changed (76) hide show
  1. package/README.md +509 -54
  2. package/package.json +30 -18
  3. package/src/index.js +15 -10
  4. package/src/modules/Application/index.js +236 -158
  5. package/src/modules/Breakpoints/index.js +116 -36
  6. package/src/modules/Cookies/index.js +95 -64
  7. package/src/modules/CoverOverlay/index.js +21 -14
  8. package/src/modules/Dataloader/index.js +71 -24
  9. package/src/modules/Dataloader/url-sync.js +238 -0
  10. package/src/modules/Dom/index.js +24 -0
  11. package/src/modules/DoubleHeader/index.js +571 -0
  12. package/src/modules/Dropdown/index.js +108 -73
  13. package/src/modules/EqualHeightElements/index.js +8 -8
  14. package/src/modules/EqualHeightImages/index.js +15 -7
  15. package/src/modules/FixedHeader/index.js +116 -30
  16. package/src/modules/FooterReveal/index.js +5 -5
  17. package/src/modules/HeroSlider/index.js +231 -106
  18. package/src/modules/HeroVideo/index.js +72 -44
  19. package/src/modules/Lazyload/index.js +128 -80
  20. package/src/modules/Lightbox/index.js +101 -80
  21. package/src/modules/Links/index.js +77 -51
  22. package/src/modules/Looper/index.js +1737 -0
  23. package/src/modules/Marquee/index.js +106 -37
  24. package/src/modules/MobileMenu/index.js +105 -130
  25. package/src/modules/Moonwalk/index.js +479 -153
  26. package/src/modules/Parallax/index.js +280 -57
  27. package/src/modules/Popover/index.js +187 -17
  28. package/src/modules/Popup/index.js +172 -53
  29. package/src/modules/ScrollSpy/index.js +21 -0
  30. package/src/modules/StackedBoxes/index.js +8 -6
  31. package/src/modules/StickyHeader/index.js +394 -164
  32. package/src/modules/Toggler/index.js +207 -11
  33. package/src/modules/Typography/index.js +33 -20
  34. package/src/utils/motion-helpers.js +330 -0
  35. package/types/README.md +159 -0
  36. package/types/events/index.d.ts +20 -0
  37. package/types/index.d.ts +6 -0
  38. package/types/modules/Application/index.d.ts +168 -0
  39. package/types/modules/Breakpoints/index.d.ts +40 -0
  40. package/types/modules/Cookies/index.d.ts +81 -0
  41. package/types/modules/CoverOverlay/index.d.ts +6 -0
  42. package/types/modules/Dataloader/index.d.ts +38 -0
  43. package/types/modules/Dataloader/url-sync.d.ts +36 -0
  44. package/types/modules/Dom/index.d.ts +47 -0
  45. package/types/modules/DoubleHeader/index.d.ts +63 -0
  46. package/types/modules/Dropdown/index.d.ts +15 -0
  47. package/types/modules/EqualHeightElements/index.d.ts +8 -0
  48. package/types/modules/EqualHeightImages/index.d.ts +11 -0
  49. package/types/modules/FeatureTests/index.d.ts +27 -0
  50. package/types/modules/FixedHeader/index.d.ts +219 -0
  51. package/types/modules/Fontloader/index.d.ts +5 -0
  52. package/types/modules/FooterReveal/index.d.ts +5 -0
  53. package/types/modules/HeroSlider/index.d.ts +28 -0
  54. package/types/modules/HeroVideo/index.d.ts +83 -0
  55. package/types/modules/Lazyload/index.d.ts +80 -0
  56. package/types/modules/Lightbox/index.d.ts +123 -0
  57. package/types/modules/Links/index.d.ts +55 -0
  58. package/types/modules/Looper/index.d.ts +127 -0
  59. package/types/modules/Marquee/index.d.ts +23 -0
  60. package/types/modules/MobileMenu/index.d.ts +63 -0
  61. package/types/modules/Moonwalk/index.d.ts +322 -0
  62. package/types/modules/Parallax/index.d.ts +71 -0
  63. package/types/modules/Popover/index.d.ts +29 -0
  64. package/types/modules/Popup/index.d.ts +76 -0
  65. package/types/modules/ScrollSpy/index.d.ts +29 -0
  66. package/types/modules/StackedBoxes/index.d.ts +9 -0
  67. package/types/modules/StickyHeader/index.d.ts +220 -0
  68. package/types/modules/Toggler/index.d.ts +48 -0
  69. package/types/modules/Typography/index.d.ts +77 -0
  70. package/types/utils/dispatchElementEvent.d.ts +1 -0
  71. package/types/utils/imageIsLoaded.d.ts +1 -0
  72. package/types/utils/imagesAreLoaded.d.ts +1 -0
  73. package/types/utils/loadScript.d.ts +2 -0
  74. package/types/utils/prefersReducedMotion.d.ts +4 -0
  75. package/types/utils/rafCallback.d.ts +2 -0
  76. package/types/utils/zoom.d.ts +4 -0
@@ -4,6 +4,25 @@ import imagesAreLoaded from '../../utils/imagesAreLoaded'
4
4
  import Dom from '../Dom'
5
5
  import * as Events from '../../events'
6
6
 
7
+ /**
8
+ * @typedef {Object} IntersectionObserverConfig
9
+ * @property {string} [rootMargin] - Margin around the root
10
+ * @property {number} [threshold] - Threshold for intersection
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} LazyloadOptions
15
+ * @property {IntersectionObserverConfig} [revealIntersectionObserverConfig] - Configuration for the reveal intersection observer
16
+ * @property {IntersectionObserverConfig} [loadIntersectionObserverConfig] - Configuration for the load intersection observer
17
+ * @property {IntersectionObserverConfig} [intersectionObserverConfig] - Configuration for general intersection observers
18
+ * @property {boolean} [useNativeLazyloadIfAvailable=true] - Whether to use native lazyloading if available
19
+ * @property {string} [mode='default'] - Lazyload mode
20
+ * @property {number} [minSize=40] - Minimum size for auto sizing
21
+ * @property {boolean} [updateSizes=true] - Whether to update sizes attribute
22
+ * @property {boolean} [registerCallback=true] - Whether to register a callback for APPLICATION_REVEALED event
23
+ */
24
+
25
+ /** @type {LazyloadOptions} */
7
26
  const DEFAULT_OPTIONS = {
8
27
  revealIntersectionObserverConfig: {
9
28
  rootMargin: '0px 100px 0px 100px',
@@ -18,12 +37,35 @@ const DEFAULT_OPTIONS = {
18
37
  minSize: 40,
19
38
  updateSizes: true,
20
39
  registerCallback: true,
40
+ target: null,
21
41
  }
22
42
 
43
+ /**
44
+ * Lazyload class for handling image lazy loading
45
+ */
23
46
  export default class Lazyload {
47
+ /**
48
+ * Create a new Lazyload instance
49
+ * @param {Object} app - Application instance
50
+ * @param {LazyloadOptions} [opts={}] - Lazyload options
51
+ */
24
52
  constructor(app, opts = {}) {
25
53
  this.app = app
26
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
+
27
69
  this.initialize()
28
70
 
29
71
  if (this.opts.registerCallback) {
@@ -42,35 +84,30 @@ export default class Lazyload {
42
84
  }
43
85
 
44
86
  initialize() {
45
- // initialize all images that have data-sizes="auto" and set sizes="<actual width>px"
46
- this.initializeAutoSizes()
87
+ // initialize ResizeObserver for images with data-sizes="auto"
88
+ this.initializeResizeObserver()
47
89
  // look for lazyload sections. if we find, add an observer that triggers
48
90
  // lazyload for all images within.
49
91
  this.initializeSections()
50
92
 
51
93
  // if we have native lazyload, use it.
52
- if (
53
- 'loading' in HTMLImageElement.prototype &&
54
- this.opts.useNativeLazyloadIfAvailable
55
- ) {
56
- const lazyImages = document.querySelectorAll('[data-ll-image]')
57
- 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 => {
58
97
  img.setAttribute('loading', 'lazy')
59
98
  this.swapImage(img)
60
99
  })
61
100
 
62
- const lazyPictures = document.querySelectorAll('[data-ll-srcset]')
63
- lazyPictures.forEach((picture) => {
64
- picture
65
- .querySelectorAll('img')
66
- .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'))
67
104
  this.swapPicture(picture)
68
105
  })
69
106
 
70
107
  return
71
108
  }
72
109
 
73
- this.lazyPictures = document.querySelectorAll('[data-ll-srcset]')
110
+ this.lazyPictures = this.target.querySelectorAll('[data-ll-srcset]')
74
111
 
75
112
  this.loadObserver = new IntersectionObserver(
76
113
  this.handleLoadEntries.bind(this),
@@ -90,7 +127,7 @@ export default class Lazyload {
90
127
  this.opts.intersectionObserverConfig
91
128
  )
92
129
 
93
- this.lazyImages = document.querySelectorAll('[data-ll-image]')
130
+ this.lazyImages = this.target.querySelectorAll('[data-ll-image]')
94
131
  this.lazyImages.forEach((img, idx) => {
95
132
  img.setAttribute('data-ll-blurred', '')
96
133
  img.setAttribute('data-ll-idx', idx)
@@ -103,7 +140,7 @@ export default class Lazyload {
103
140
  this.lazyPictures.forEach((picture, idx) => {
104
141
  if (setAttrs) {
105
142
  picture.setAttribute('data-ll-srcset-initialized', '')
106
- picture.querySelectorAll('img:not([data-ll-loaded])').forEach((img) => {
143
+ picture.querySelectorAll('img:not([data-ll-loaded])').forEach(img => {
107
144
  img.setAttribute('data-ll-blurred', '')
108
145
  img.setAttribute('data-ll-idx', idx)
109
146
  img.style.setProperty('--ll-idx', idx)
@@ -115,45 +152,82 @@ export default class Lazyload {
115
152
 
116
153
  forceLoad($container = document.body) {
117
154
  const images = Dom.all($container, '[data-ll-image]')
118
- images.forEach((img) => this.swapImage(img))
155
+ images.forEach(img => this.swapImage(img))
119
156
 
120
157
  const pictures = Dom.all($container, '[data-ll-srcset]')
121
- pictures.forEach((picture) => this.revealPicture(picture))
158
+ pictures.forEach(picture => this.revealPicture(picture))
122
159
  }
123
160
 
124
- initializeAutoSizes() {
125
- if (this.opts.updateSizes) {
126
- this.$autoSizesImages = Dom.all('[data-sizes="auto"]')
127
- this.autoSizes()
128
- window.addEventListener(Events.APPLICATION_RESIZE, () => this.autoSizes())
161
+ initializeResizeObserver() {
162
+ if (!this.opts.updateSizes) {
163
+ return
129
164
  }
130
- }
131
165
 
132
- /**
133
- * Set sizes attribute for all imgs with `data-sizes="auto"` and source within the <picture>
134
- */
135
- autoSizes() {
136
- Array.from(this.$autoSizesImages).forEach((img) => {
137
- const width = this.getWidth(img)
138
- img.setAttribute('sizes', `${width}px`)
139
- if (img.parentNode) {
140
- Array.from(Dom.all(img.parentNode, 'source')).forEach((source) =>
141
- source.setAttribute('sizes', `${width}px`)
142
- )
143
- }
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)
144
207
  })
145
208
  }
146
209
 
147
- getWidth(img) {
148
- let width = img.offsetWidth
149
- let parent = img.parentNode
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`
150
215
 
151
- while (width < this.opts.minSize && parent) {
152
- width = parent.offsetWidth
153
- parent = parent.parentNode
154
- }
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
+ })
155
228
 
156
- return width
229
+ this.resizePending.clear()
230
+ this.rafId = null
157
231
  }
158
232
 
159
233
  initializeSections() {
@@ -162,12 +236,12 @@ export default class Lazyload {
162
236
  const sectionObserver = (section, children) => {
163
237
  const imagesInSection = Dom.all(section, 'img')
164
238
  return new IntersectionObserver((entries, self) => {
165
- entries.forEach((entry) => {
239
+ entries.forEach(entry => {
166
240
  if (entry.isIntersecting || entry.intersectionRatio > 0) {
167
241
  imagesAreLoaded(imagesInSection, true).then(() => {
168
242
  dispatchElementEvent(section, Events.SECTION_LAZYLOADED)
169
243
  })
170
- children.forEach((picture) => {
244
+ children.forEach(picture => {
171
245
  this.loadPicture(picture)
172
246
  this.loadObserver.unobserve(picture)
173
247
  })
@@ -177,7 +251,7 @@ export default class Lazyload {
177
251
  }, this.opts.intersectionObserverConfig)
178
252
  }
179
253
 
180
- sections.forEach((section) => {
254
+ sections.forEach(section => {
181
255
  const children = section.querySelectorAll('picture')
182
256
  const obs = sectionObserver(section, children)
183
257
  obs.observe(section)
@@ -187,7 +261,7 @@ export default class Lazyload {
187
261
 
188
262
  // we load the picture a ways before it enters the viewport
189
263
  handleLoadEntries(elements) {
190
- elements.forEach((item) => {
264
+ elements.forEach(item => {
191
265
  if (item.isIntersecting || item.intersectionRatio > 0) {
192
266
  const picture = item.target
193
267
  this.loadPicture(picture)
@@ -198,26 +272,15 @@ export default class Lazyload {
198
272
 
199
273
  // we reveal the picture when it enters the viewport
200
274
  handleRevealEntries(elements) {
201
- const srcsetReadyObserver = new MutationObserver((mutations) => {
202
- mutations.forEach((record) => {
203
- if (
204
- record.type === 'attributes' &&
205
- record.attributeName === 'data-ll-srcset-ready'
206
- ) {
207
- this.revealPicture(record.target)
208
- this.revealObserver.unobserve(record.target)
209
- }
210
- })
211
- })
212
-
213
- elements.forEach((item) => {
275
+ elements.forEach(item => {
214
276
  if (item.isIntersecting || item.intersectionRatio > 0) {
215
277
  const picture = item.target
216
278
  const ready = item.target.hasAttribute('data-ll-srcset-ready')
217
279
  if (!ready) {
218
280
  // element is not loaded, observe the picture and wait for
219
281
  // `data-ll-srcset-ready` before revealing
220
- srcsetReadyObserver.observe(picture, { attributes: true })
282
+ // Use reusable MutationObserver to prevent memory leaks
283
+ this.srcsetReadyObserver.observe(picture, { attributes: true })
221
284
  } else {
222
285
  this.revealPicture(picture)
223
286
  this.revealObserver.unobserve(item.target)
@@ -249,23 +312,8 @@ export default class Lazyload {
249
312
  const img = picture.querySelector('img')
250
313
 
251
314
  const onload = () => {
252
- if (
253
- !img.getAttribute('data-ll-ready') &&
254
- this.app.browser === 'firefox'
255
- ) {
256
- // set sizes attribute on load again,
257
- // since firefox sometimes is a bit slow to
258
- // get the actual image width
259
- const width = this.getWidth(img)
260
-
261
- img.setAttribute('sizes', `${width}px`)
262
- if (img.parentNode) {
263
- Array.from(Dom.all(img.parentNode, 'source')).forEach((source) =>
264
- source.setAttribute('sizes', `${width}px`)
265
- )
266
- }
267
- }
268
-
315
+ // ResizeObserver now handles size updates automatically,
316
+ // including Firefox's delayed dimension calculation
269
317
  img.removeAttribute('data-ll-placeholder')
270
318
  img.removeAttribute('data-ll-blurred')
271
319
  img.removeAttribute('data-ll-loading')
@@ -309,7 +357,7 @@ export default class Lazyload {
309
357
  }
310
358
 
311
359
  lazyloadImages(elements) {
312
- elements.forEach((item) => {
360
+ elements.forEach(item => {
313
361
  if (item.isIntersecting || item.intersectionRatio > 0) {
314
362
  const image = item.target
315
363
  this.swapImage(image)
@@ -1,9 +1,38 @@
1
- import { Manager, Swipe } from '@egjs/hammerjs'
2
- import { gsap } from 'gsap'
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'
6
-
5
+ import { set, PausedTimeline } from '../../utils/motion-helpers'
6
+
7
+ /**
8
+ * @typedef {Object} LightboxElements
9
+ * @property {Function} [arrowRight] - Function to create right arrow element
10
+ * @property {Function} [arrowLeft] - Function to create left arrow element
11
+ * @property {Function} [close] - Function to create close button element
12
+ * @property {Function} [dot] - Function to create dot/indicator element
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} LightboxOptions
17
+ * @property {boolean} [captions=false] - Enable captions
18
+ * @property {boolean} [numbers=false] - Enable index numbers
19
+ * @property {string|boolean} [trigger=false] - Selector for trigger element to open the lightbox
20
+ * @property {LightboxElements} [elements] - Custom elements configuration
21
+ * @property {Function} [onClick] - Click handler for lightbox
22
+ * @property {Function} [onPointerLeft] - Called when pointer moves left
23
+ * @property {Function} [onPointerRight] - Called when pointer moves right
24
+ * @property {Function} [onCaptionOut] - Called when caption fades out
25
+ * @property {Function} [onCaptionIn] - Called when caption fades in
26
+ * @property {Function} [onImageOut] - Called when image fades out
27
+ * @property {Function} [onImageIn] - Called when image fades in
28
+ * @property {Function} [onNumbers] - Called when numbers display updates
29
+ * @property {Function} [onBeforeOpen] - Called before opening lightbox
30
+ * @property {Function} [onOpen] - Called when lightbox opens
31
+ * @property {Function} [onAfterClose] - Called after lightbox closes
32
+ * @property {Function} [onClose] - Called when lightbox closes
33
+ */
34
+
35
+ /** @type {LightboxOptions} */
7
36
  const DEFAULT_OPTIONS = {
8
37
  /* enable captions */
9
38
  captions: false,
@@ -11,9 +40,6 @@ const DEFAULT_OPTIONS = {
11
40
  /* enable index numbers */
12
41
  numbers: false,
13
42
 
14
- /* enable swipe — this breaks native zoom! */
15
- swipe: true,
16
-
17
43
  /* set to a selector if you want a specific trigger element to open the box */
18
44
  trigger: false,
19
45
 
@@ -33,7 +59,7 @@ const DEFAULT_OPTIONS = {
33
59
  },
34
60
 
35
61
  close: () => document.createTextNode('×'),
36
- dot: () => document.createTextNode('▪')
62
+ dot: () => document.createTextNode('▪'),
37
63
  },
38
64
 
39
65
  onClick: (lightbox, section, e) => {
@@ -56,7 +82,10 @@ const DEFAULT_OPTIONS = {
56
82
  return
57
83
  }
58
84
 
59
- lightbox.timelines.caption.to(lightbox.elements.caption, { duration: 0.4, autoAlpha: 0 })
85
+ lightbox.timelines.caption.to(lightbox.elements.caption, {
86
+ duration: 0.4,
87
+ autoAlpha: 0,
88
+ })
60
89
  },
61
90
 
62
91
  onCaptionIn: (lightbox, captionHasChanged) => {
@@ -64,16 +93,26 @@ const DEFAULT_OPTIONS = {
64
93
  return
65
94
  }
66
95
 
67
- lightbox.timelines.caption.to(lightbox.elements.caption, { duration: 0.4, autoAlpha: 1 })
96
+ lightbox.timelines.caption.to(lightbox.elements.caption, {
97
+ duration: 0.4,
98
+ autoAlpha: 1,
99
+ })
68
100
  },
69
101
 
70
- onImageOut: lightbox => {
71
- lightbox.timelines.image.to(lightbox.currentImage, { duration: 0.5, autoAlpha: 0 })
102
+ onImageOut: (lightbox) => {
103
+ lightbox.timelines.image.to(lightbox.currentImage, {
104
+ duration: 0.5,
105
+ autoAlpha: 0,
106
+ })
72
107
  },
73
108
 
74
- onImageIn: lightbox => {
109
+ onImageIn: (lightbox) => {
75
110
  const delay = lightbox.firstTransition ? 0.6 : 0.4
76
- lightbox.timelines.image.to(lightbox.nextImage, { duration: 0.5, autoAlpha: 1, delay })
111
+ lightbox.timelines.image.to(lightbox.nextImage, {
112
+ duration: 0.5,
113
+ autoAlpha: 1,
114
+ delay,
115
+ })
77
116
  },
78
117
 
79
118
  onNumbers: (lightbox, section) => {
@@ -82,52 +121,47 @@ const DEFAULT_OPTIONS = {
82
121
 
83
122
  onBeforeOpen: () => {},
84
123
 
85
- onOpen: h => {
124
+ onOpen: (h) => {
86
125
  h.app.scrollLock()
87
126
 
88
- gsap.to(h.elements.wrapper, {
89
- duration: 0.5,
90
- opacity: 1
91
- })
127
+ animate(h.elements.wrapper, { opacity: 1 }, { duration: 0.5 })
92
128
  },
93
129
 
94
130
  onAfterClose: () => {},
95
131
 
96
- onClose: h => {
132
+ onClose: (h) => {
97
133
  if (h.opts.captions) {
98
- gsap.to(h.elements.caption, {
99
- duration: 0.45,
100
- opacity: 0
101
- })
134
+ animate(h.elements.caption, { opacity: 0 }, { duration: 0.45 })
102
135
  }
103
136
 
104
- gsap.to(
137
+ animate(
105
138
  [
106
139
  h.elements.imgWrapper,
107
140
  h.elements.nextArrow,
108
141
  h.elements.prevArrow,
109
142
  h.elements.close,
110
- h.elements.dots
143
+ h.elements.dots,
111
144
  ],
112
- {
113
- duration: 0.5,
114
- opacity: 0,
115
- onComplete: () => {
116
- gsap.to(h.elements.wrapper, {
117
- duration: 0.45,
118
- opacity: 0,
119
- onComplete: () => {
120
- h.app.scrollRelease()
121
- h.destroy()
122
- }
123
- })
124
- }
125
- }
126
- )
127
- }
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
+ })
153
+ },
128
154
  }
129
155
 
156
+ /**
157
+ * Lightbox component for displaying images in a fullscreen overlay
158
+ */
130
159
  export default class Lightbox {
160
+ /**
161
+ * Create a new Lightbox instance
162
+ * @param {Object} app - Application instance
163
+ * @param {LightboxOptions} [opts={}] - Lightbox options
164
+ */
131
165
  constructor(app, opts = {}) {
132
166
  this.app = app
133
167
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
@@ -140,16 +174,17 @@ export default class Lightbox {
140
174
  this.firstTransition = true
141
175
  this.previousCaption = null
142
176
  this.timelines = {
143
- caption: gsap.timeline({ paused: true }),
144
- image: gsap.timeline({ paused: true })
177
+ caption: new PausedTimeline(),
178
+ image: new PausedTimeline(),
145
179
  }
146
180
 
147
- this.lightboxes.forEach(lightbox => {
181
+ this.lightboxes.forEach((lightbox) => {
148
182
  const href = lightbox.getAttribute('data-lightbox')
149
183
  const srcset = lightbox.getAttribute('data-srcset')
150
184
  const originalImage = lightbox.querySelector('img')
151
185
  const alt = originalImage.getAttribute('alt')
152
- const section = lightbox.getAttribute('data-lightbox-section') || 'general'
186
+ const section =
187
+ lightbox.getAttribute('data-lightbox-section') || 'general'
153
188
  let trigger = lightbox
154
189
  if (this.opts.trigger) {
155
190
  trigger = Dom.find(lightbox, this.opts.trigger) || lightbox
@@ -162,12 +197,12 @@ export default class Lightbox {
162
197
  const image = {
163
198
  href,
164
199
  alt,
165
- srcset
200
+ srcset,
166
201
  }
167
202
 
168
203
  const index = this.sections[section].push(image) - 1
169
204
 
170
- trigger.addEventListener('click', e => {
205
+ trigger.addEventListener('click', (e) => {
171
206
  e.preventDefault()
172
207
  this.showBox(section, index)
173
208
  })
@@ -207,30 +242,30 @@ export default class Lightbox {
207
242
  this.elements.nextArrow.appendChild(this.opts.elements.arrowRight())
208
243
  this.elements.nextArrow.href = '#'
209
244
 
210
- this.elements.nextArrow.addEventListener('click', e => {
245
+ this.elements.nextArrow.addEventListener('click', (e) => {
211
246
  e.stopPropagation()
212
247
  e.preventDefault()
213
248
  this.setImg(section, this.getNextIdx(section))
214
249
  })
215
250
 
216
- this.elements.prevArrow.addEventListener('click', e => {
251
+ this.elements.prevArrow.addEventListener('click', (e) => {
217
252
  e.stopPropagation()
218
253
  e.preventDefault()
219
254
  this.setImg(section, this.getPrevIdx(section))
220
255
  })
221
256
 
222
- this.keyUpCallback = event => {
257
+ this.keyUpCallback = (event) => {
223
258
  this.onKeyup(event, section)
224
259
  }
225
260
 
226
261
  document.removeEventListener('keyup', this.keyUpCallback)
227
262
  document.addEventListener('keyup', this.keyUpCallback)
228
263
 
229
- this.elements.wrapper.addEventListener('mousemove', event => {
264
+ this.elements.wrapper.addEventListener('mousemove', (event) => {
230
265
  this.onMouseMove(event)
231
266
  })
232
267
 
233
- this.elements.wrapper.addEventListener('click', event => {
268
+ this.elements.wrapper.addEventListener('click', (event) => {
234
269
  this.onClick(event, section)
235
270
  })
236
271
 
@@ -242,7 +277,8 @@ export default class Lightbox {
242
277
 
243
278
  this.sections[section].forEach((img, x) => {
244
279
  const imgElement = document.createElement('img')
245
- gsap.set(imgElement, { autoAlpha: 0 })
280
+ set(imgElement, { opacity: 0 })
281
+ imgElement.style.visibility = 'hidden'
246
282
  imgElement.classList.add('lightbox-image', 'm-lg')
247
283
  imgElement.setAttribute('data-idx', x)
248
284
  this.elements.imgWrapper.appendChild(imgElement)
@@ -257,7 +293,7 @@ export default class Lightbox {
257
293
  activeLink = a
258
294
  }
259
295
 
260
- a.addEventListener('click', e => {
296
+ a.addEventListener('click', (e) => {
261
297
  a.classList.add('active')
262
298
  activeLink.classList.remove('active')
263
299
  activeLink = a
@@ -290,13 +326,10 @@ export default class Lightbox {
290
326
  document.body.appendChild(this.elements.wrapper)
291
327
 
292
328
  this.setImg(section, index, this.getPrevIdx(section))
293
- if (this.opts.swipe) {
294
- this.attachSwiper(section, this.elements.content, index)
295
- }
296
329
 
297
330
  this.opts.onOpen(this)
298
331
 
299
- this.elements.close.addEventListener('click', e => {
332
+ this.elements.close.addEventListener('click', (e) => {
300
333
  e.preventDefault()
301
334
  e.stopPropagation()
302
335
 
@@ -334,7 +367,8 @@ export default class Lightbox {
334
367
  activeDot.classList.add('active')
335
368
 
336
369
  if (this.elements.caption) {
337
- captionHasChanged = this.previousCaption !== this.sections[section][index].alt
370
+ captionHasChanged =
371
+ this.previousCaption !== this.sections[section][index].alt
338
372
  this.previousCaption = this.sections[section][index].alt
339
373
  this.opts.onCaptionOut(this, captionHasChanged)
340
374
  this.timelines.caption.call(() => {
@@ -357,7 +391,10 @@ export default class Lightbox {
357
391
  if (this.imgs[index + x]) {
358
392
  this.imgs[index + x].src = this.sections[section][index + x].href
359
393
  if (this.sections[section][index + x].srcset) {
360
- this.imgs[index + x].setAttribute('srcset', this.sections[section][index + x].srcset)
394
+ this.imgs[index + x].setAttribute(
395
+ 'srcset',
396
+ this.sections[section][index + x].srcset
397
+ )
361
398
  }
362
399
  } else {
363
400
  break
@@ -367,7 +404,10 @@ export default class Lightbox {
367
404
  this.nextImage = this.imgs[index]
368
405
  this.nextImage.src = this.sections[section][index].href
369
406
  if (this.sections[section][index].srcset) {
370
- this.nextImage.setAttribute('srcset', this.sections[section][index].srcset)
407
+ this.nextImage.setAttribute(
408
+ 'srcset',
409
+ this.sections[section][index].srcset
410
+ )
371
411
  }
372
412
 
373
413
  this.opts.onImageIn(this)
@@ -442,23 +482,4 @@ export default class Lightbox {
442
482
  this.opts.onPointerRight(this)
443
483
  }
444
484
  }
445
-
446
- attachSwiper(section, el, initialIdx) {
447
- const hammerManager = new Manager(el)
448
- const swipeHandler = new Swipe()
449
-
450
- this.elements.content.setAttribute('data-current-idx', initialIdx)
451
-
452
- hammerManager.add(swipeHandler)
453
-
454
- hammerManager.on('swipeleft', () => {
455
- const index = this.getNextIdx(section)
456
- this.setImg(section, index)
457
- })
458
-
459
- hammerManager.on('swiperight', () => {
460
- const index = this.getPrevIdx(section)
461
- this.setImg(section, index)
462
- })
463
- }
464
485
  }