@brandocms/jupiter 3.55.0 → 4.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.
Files changed (67) hide show
  1. package/README.md +318 -52
  2. package/package.json +26 -16
  3. package/src/index.js +5 -0
  4. package/src/modules/Application/index.js +36 -4
  5. package/src/modules/Breakpoints/index.js +116 -36
  6. package/src/modules/Cookies/index.js +67 -15
  7. package/src/modules/CoverOverlay/index.js +2 -2
  8. package/src/modules/Dom/index.js +6 -0
  9. package/src/modules/Dropdown/index.js +15 -6
  10. package/src/modules/EqualHeightElements/index.js +8 -6
  11. package/src/modules/EqualHeightImages/index.js +9 -6
  12. package/src/modules/FixedHeader/index.js +57 -1
  13. package/src/modules/FooterReveal/index.js +3 -3
  14. package/src/modules/HeroSlider/index.js +39 -30
  15. package/src/modules/HeroVideo/index.js +64 -24
  16. package/src/modules/Lazyload/index.js +27 -0
  17. package/src/modules/Lightbox/index.js +90 -31
  18. package/src/modules/Links/index.js +23 -2
  19. package/src/modules/MobileMenu/index.js +50 -21
  20. package/src/modules/Moonwalk/index.js +131 -4
  21. package/src/modules/Parallax/index.js +280 -57
  22. package/src/modules/Popover/index.js +28 -16
  23. package/src/modules/Popup/index.js +155 -29
  24. package/src/modules/ScrollSpy/index.js +21 -0
  25. package/src/modules/StackedBoxes/index.js +6 -4
  26. package/src/modules/StickyHeader/index.js +45 -24
  27. package/src/modules/Toggler/index.js +44 -5
  28. package/src/modules/Typography/index.js +33 -20
  29. package/types/README.md +159 -0
  30. package/types/events/index.d.ts +20 -0
  31. package/types/index.d.ts +35 -0
  32. package/types/modules/Application/index.d.ts +168 -0
  33. package/types/modules/Breakpoints/index.d.ts +38 -0
  34. package/types/modules/Cookies/index.d.ts +81 -0
  35. package/types/modules/CoverOverlay/index.d.ts +6 -0
  36. package/types/modules/Dataloader/index.d.ts +35 -0
  37. package/types/modules/Dom/index.d.ts +40 -0
  38. package/types/modules/Dropdown/index.d.ts +38 -0
  39. package/types/modules/EqualHeightElements/index.d.ts +8 -0
  40. package/types/modules/EqualHeightImages/index.d.ts +11 -0
  41. package/types/modules/FeatureTests/index.d.ts +27 -0
  42. package/types/modules/FixedHeader/index.d.ts +219 -0
  43. package/types/modules/Fontloader/index.d.ts +5 -0
  44. package/types/modules/FooterReveal/index.d.ts +5 -0
  45. package/types/modules/HeroSlider/index.d.ts +28 -0
  46. package/types/modules/HeroVideo/index.d.ts +83 -0
  47. package/types/modules/Lazyload/index.d.ts +80 -0
  48. package/types/modules/Lightbox/index.d.ts +128 -0
  49. package/types/modules/Links/index.d.ts +55 -0
  50. package/types/modules/Marquee/index.d.ts +23 -0
  51. package/types/modules/MobileMenu/index.d.ts +63 -0
  52. package/types/modules/Moonwalk/index.d.ts +331 -0
  53. package/types/modules/Parallax/index.d.ts +93 -0
  54. package/types/modules/Popover/index.d.ts +17 -0
  55. package/types/modules/Popup/index.d.ts +89 -0
  56. package/types/modules/ScrollSpy/index.d.ts +29 -0
  57. package/types/modules/StackedBoxes/index.d.ts +9 -0
  58. package/types/modules/StickyHeader/index.d.ts +63 -0
  59. package/types/modules/Toggler/index.d.ts +26 -0
  60. package/types/modules/Typography/index.d.ts +77 -0
  61. package/types/utils/dispatchElementEvent.d.ts +1 -0
  62. package/types/utils/imageIsLoaded.d.ts +1 -0
  63. package/types/utils/imagesAreLoaded.d.ts +1 -0
  64. package/types/utils/loadScript.d.ts +2 -0
  65. package/types/utils/prefersReducedMotion.d.ts +4 -0
  66. package/types/utils/rafCallback.d.ts +2 -0
  67. package/types/utils/zoom.d.ts +4 -0
@@ -1,9 +1,39 @@
1
1
  import { Manager, Swipe } from '@egjs/hammerjs'
2
- import { gsap } from 'gsap'
2
+ import { gsap } from 'gsap/all'
3
3
  import _defaultsDeep from 'lodash.defaultsdeep'
4
4
  import imageIsLoaded from '../../utils/imageIsLoaded'
5
5
  import Dom from '../Dom'
6
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 {boolean} [swipe=true] - Enable swipe - this breaks native zoom
20
+ * @property {string|boolean} [trigger=false] - Selector for trigger element to open the lightbox
21
+ * @property {LightboxElements} [elements] - Custom elements configuration
22
+ * @property {Function} [onClick] - Click handler for lightbox
23
+ * @property {Function} [onPointerLeft] - Called when pointer moves left
24
+ * @property {Function} [onPointerRight] - Called when pointer moves right
25
+ * @property {Function} [onCaptionOut] - Called when caption fades out
26
+ * @property {Function} [onCaptionIn] - Called when caption fades in
27
+ * @property {Function} [onImageOut] - Called when image fades out
28
+ * @property {Function} [onImageIn] - Called when image fades in
29
+ * @property {Function} [onNumbers] - Called when numbers display updates
30
+ * @property {Function} [onBeforeOpen] - Called before opening lightbox
31
+ * @property {Function} [onOpen] - Called when lightbox opens
32
+ * @property {Function} [onAfterClose] - Called after lightbox closes
33
+ * @property {Function} [onClose] - Called when lightbox closes
34
+ */
35
+
36
+ /** @type {LightboxOptions} */
7
37
  const DEFAULT_OPTIONS = {
8
38
  /* enable captions */
9
39
  captions: false,
@@ -33,7 +63,7 @@ const DEFAULT_OPTIONS = {
33
63
  },
34
64
 
35
65
  close: () => document.createTextNode('×'),
36
- dot: () => document.createTextNode('▪')
66
+ dot: () => document.createTextNode('▪'),
37
67
  },
38
68
 
39
69
  onClick: (lightbox, section, e) => {
@@ -56,7 +86,10 @@ const DEFAULT_OPTIONS = {
56
86
  return
57
87
  }
58
88
 
59
- lightbox.timelines.caption.to(lightbox.elements.caption, { duration: 0.4, autoAlpha: 0 })
89
+ lightbox.timelines.caption.to(lightbox.elements.caption, {
90
+ duration: 0.4,
91
+ autoAlpha: 0,
92
+ })
60
93
  },
61
94
 
62
95
  onCaptionIn: (lightbox, captionHasChanged) => {
@@ -64,16 +97,26 @@ const DEFAULT_OPTIONS = {
64
97
  return
65
98
  }
66
99
 
67
- lightbox.timelines.caption.to(lightbox.elements.caption, { duration: 0.4, autoAlpha: 1 })
100
+ lightbox.timelines.caption.to(lightbox.elements.caption, {
101
+ duration: 0.4,
102
+ autoAlpha: 1,
103
+ })
68
104
  },
69
105
 
70
- onImageOut: lightbox => {
71
- lightbox.timelines.image.to(lightbox.currentImage, { duration: 0.5, autoAlpha: 0 })
106
+ onImageOut: (lightbox) => {
107
+ lightbox.timelines.image.to(lightbox.currentImage, {
108
+ duration: 0.5,
109
+ autoAlpha: 0,
110
+ })
72
111
  },
73
112
 
74
- onImageIn: lightbox => {
113
+ onImageIn: (lightbox) => {
75
114
  const delay = lightbox.firstTransition ? 0.6 : 0.4
76
- lightbox.timelines.image.to(lightbox.nextImage, { duration: 0.5, autoAlpha: 1, delay })
115
+ lightbox.timelines.image.to(lightbox.nextImage, {
116
+ duration: 0.5,
117
+ autoAlpha: 1,
118
+ delay,
119
+ })
77
120
  },
78
121
 
79
122
  onNumbers: (lightbox, section) => {
@@ -82,22 +125,22 @@ const DEFAULT_OPTIONS = {
82
125
 
83
126
  onBeforeOpen: () => {},
84
127
 
85
- onOpen: h => {
128
+ onOpen: (h) => {
86
129
  h.app.scrollLock()
87
130
 
88
131
  gsap.to(h.elements.wrapper, {
89
132
  duration: 0.5,
90
- opacity: 1
133
+ opacity: 1,
91
134
  })
92
135
  },
93
136
 
94
137
  onAfterClose: () => {},
95
138
 
96
- onClose: h => {
139
+ onClose: (h) => {
97
140
  if (h.opts.captions) {
98
141
  gsap.to(h.elements.caption, {
99
142
  duration: 0.45,
100
- opacity: 0
143
+ opacity: 0,
101
144
  })
102
145
  }
103
146
 
@@ -107,7 +150,7 @@ const DEFAULT_OPTIONS = {
107
150
  h.elements.nextArrow,
108
151
  h.elements.prevArrow,
109
152
  h.elements.close,
110
- h.elements.dots
153
+ h.elements.dots,
111
154
  ],
112
155
  {
113
156
  duration: 0.5,
@@ -119,15 +162,23 @@ const DEFAULT_OPTIONS = {
119
162
  onComplete: () => {
120
163
  h.app.scrollRelease()
121
164
  h.destroy()
122
- }
165
+ },
123
166
  })
124
- }
167
+ },
125
168
  }
126
169
  )
127
- }
170
+ },
128
171
  }
129
172
 
173
+ /**
174
+ * Lightbox component for displaying images in a fullscreen overlay
175
+ */
130
176
  export default class Lightbox {
177
+ /**
178
+ * Create a new Lightbox instance
179
+ * @param {Object} app - Application instance
180
+ * @param {LightboxOptions} [opts={}] - Lightbox options
181
+ */
131
182
  constructor(app, opts = {}) {
132
183
  this.app = app
133
184
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
@@ -141,15 +192,16 @@ export default class Lightbox {
141
192
  this.previousCaption = null
142
193
  this.timelines = {
143
194
  caption: gsap.timeline({ paused: true }),
144
- image: gsap.timeline({ paused: true })
195
+ image: gsap.timeline({ paused: true }),
145
196
  }
146
197
 
147
- this.lightboxes.forEach(lightbox => {
198
+ this.lightboxes.forEach((lightbox) => {
148
199
  const href = lightbox.getAttribute('data-lightbox')
149
200
  const srcset = lightbox.getAttribute('data-srcset')
150
201
  const originalImage = lightbox.querySelector('img')
151
202
  const alt = originalImage.getAttribute('alt')
152
- const section = lightbox.getAttribute('data-lightbox-section') || 'general'
203
+ const section =
204
+ lightbox.getAttribute('data-lightbox-section') || 'general'
153
205
  let trigger = lightbox
154
206
  if (this.opts.trigger) {
155
207
  trigger = Dom.find(lightbox, this.opts.trigger) || lightbox
@@ -162,12 +214,12 @@ export default class Lightbox {
162
214
  const image = {
163
215
  href,
164
216
  alt,
165
- srcset
217
+ srcset,
166
218
  }
167
219
 
168
220
  const index = this.sections[section].push(image) - 1
169
221
 
170
- trigger.addEventListener('click', e => {
222
+ trigger.addEventListener('click', (e) => {
171
223
  e.preventDefault()
172
224
  this.showBox(section, index)
173
225
  })
@@ -207,30 +259,30 @@ export default class Lightbox {
207
259
  this.elements.nextArrow.appendChild(this.opts.elements.arrowRight())
208
260
  this.elements.nextArrow.href = '#'
209
261
 
210
- this.elements.nextArrow.addEventListener('click', e => {
262
+ this.elements.nextArrow.addEventListener('click', (e) => {
211
263
  e.stopPropagation()
212
264
  e.preventDefault()
213
265
  this.setImg(section, this.getNextIdx(section))
214
266
  })
215
267
 
216
- this.elements.prevArrow.addEventListener('click', e => {
268
+ this.elements.prevArrow.addEventListener('click', (e) => {
217
269
  e.stopPropagation()
218
270
  e.preventDefault()
219
271
  this.setImg(section, this.getPrevIdx(section))
220
272
  })
221
273
 
222
- this.keyUpCallback = event => {
274
+ this.keyUpCallback = (event) => {
223
275
  this.onKeyup(event, section)
224
276
  }
225
277
 
226
278
  document.removeEventListener('keyup', this.keyUpCallback)
227
279
  document.addEventListener('keyup', this.keyUpCallback)
228
280
 
229
- this.elements.wrapper.addEventListener('mousemove', event => {
281
+ this.elements.wrapper.addEventListener('mousemove', (event) => {
230
282
  this.onMouseMove(event)
231
283
  })
232
284
 
233
- this.elements.wrapper.addEventListener('click', event => {
285
+ this.elements.wrapper.addEventListener('click', (event) => {
234
286
  this.onClick(event, section)
235
287
  })
236
288
 
@@ -257,7 +309,7 @@ export default class Lightbox {
257
309
  activeLink = a
258
310
  }
259
311
 
260
- a.addEventListener('click', e => {
312
+ a.addEventListener('click', (e) => {
261
313
  a.classList.add('active')
262
314
  activeLink.classList.remove('active')
263
315
  activeLink = a
@@ -296,7 +348,7 @@ export default class Lightbox {
296
348
 
297
349
  this.opts.onOpen(this)
298
350
 
299
- this.elements.close.addEventListener('click', e => {
351
+ this.elements.close.addEventListener('click', (e) => {
300
352
  e.preventDefault()
301
353
  e.stopPropagation()
302
354
 
@@ -334,7 +386,8 @@ export default class Lightbox {
334
386
  activeDot.classList.add('active')
335
387
 
336
388
  if (this.elements.caption) {
337
- captionHasChanged = this.previousCaption !== this.sections[section][index].alt
389
+ captionHasChanged =
390
+ this.previousCaption !== this.sections[section][index].alt
338
391
  this.previousCaption = this.sections[section][index].alt
339
392
  this.opts.onCaptionOut(this, captionHasChanged)
340
393
  this.timelines.caption.call(() => {
@@ -357,7 +410,10 @@ export default class Lightbox {
357
410
  if (this.imgs[index + x]) {
358
411
  this.imgs[index + x].src = this.sections[section][index + x].href
359
412
  if (this.sections[section][index + x].srcset) {
360
- this.imgs[index + x].setAttribute('srcset', this.sections[section][index + x].srcset)
413
+ this.imgs[index + x].setAttribute(
414
+ 'srcset',
415
+ this.sections[section][index + x].srcset
416
+ )
361
417
  }
362
418
  } else {
363
419
  break
@@ -367,7 +423,10 @@ export default class Lightbox {
367
423
  this.nextImage = this.imgs[index]
368
424
  this.nextImage.src = this.sections[section][index].href
369
425
  if (this.sections[section][index].srcset) {
370
- this.nextImage.setAttribute('srcset', this.sections[section][index].srcset)
426
+ this.nextImage.setAttribute(
427
+ 'srcset',
428
+ this.sections[section][index].srcset
429
+ )
371
430
  }
372
431
 
373
432
  this.opts.onImageIn(this)
@@ -1,9 +1,22 @@
1
- import { gsap } from 'gsap'
2
- import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
1
+ import { gsap, ScrollToPlugin } from 'gsap/all'
3
2
  import _defaultsDeep from 'lodash.defaultsdeep'
4
3
 
5
4
  gsap.registerPlugin(ScrollToPlugin)
6
5
 
6
+ /**
7
+ * @typedef {Object} LinksOptions
8
+ * @property {boolean} [triggerEvents=true] - Whether to trigger events when scrolling
9
+ * @property {number} [scrollDuration=0.8] - Duration of scroll animation
10
+ * @property {boolean} [scrollOffsetNav=false] - Whether to offset scroll for nav header
11
+ * @property {number} [mobileMenuDelay=800] - Delay for mobile menu before scrolling
12
+ * @property {boolean} [openExternalInWindow=true] - Whether to open external links in new window
13
+ * @property {string} [linkQuery] - Query selector for regular links
14
+ * @property {string} [anchorQuery] - Query selector for anchor links
15
+ * @property {Function} [onAnchor] - Called when an anchor link is clicked
16
+ * @property {Function} [onTransition] - Called when transitioning between pages
17
+ */
18
+
19
+ /** @type {LinksOptions} */
7
20
  const DEFAULT_OPTIONS = {
8
21
  triggerEvents: true,
9
22
  scrollDuration: 0.8,
@@ -82,7 +95,15 @@ const DEFAULT_OPTIONS = {
82
95
  },
83
96
  }
84
97
 
98
+ /**
99
+ * Links handler for navigation and transitions
100
+ */
85
101
  export default class Links {
102
+ /**
103
+ * Create a new Links instance
104
+ * @param {Object} app - Application instance
105
+ * @param {LinksOptions} [opts={}] - Links options
106
+ */
86
107
  constructor(app, opts = {}) {
87
108
  this.app = app
88
109
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
@@ -1,7 +1,20 @@
1
- import { gsap } from 'gsap'
1
+ import { gsap } from 'gsap/all'
2
2
  import _defaultsDeep from 'lodash.defaultsdeep'
3
3
  import * as Events from '../../events'
4
4
 
5
+ /**
6
+ * @typedef {Object} MobileMenuOptions
7
+ * @property {string} [logoColor='#000'] - Color for logo when menu is open
8
+ * @property {string} [logoPathSelector='svg path'] - Selector for logo SVG paths
9
+ * @property {string} [contentSelector='section'] - Selector for menu content
10
+ * @property {string} [liSelector='li'] - Selector for menu items
11
+ * @property {string} [hamburgerColor='#000'] - Color for hamburger icon
12
+ * @property {Function|null} [onResize=null] - Called when window is resized
13
+ * @property {Function} [openTween] - Animation for opening menu
14
+ * @property {Function} [closeTween] - Animation for closing menu
15
+ */
16
+
17
+ /** @type {MobileMenuOptions} */
5
18
  const DEFAULT_OPTIONS = {
6
19
  logoColor: '#000',
7
20
  logoPathSelector: 'svg path',
@@ -10,7 +23,7 @@ const DEFAULT_OPTIONS = {
10
23
  hamburgerColor: '#000',
11
24
 
12
25
  onResize: null,
13
- openTween: m => {
26
+ openTween: (m) => {
14
27
  const timeline = gsap.timeline()
15
28
 
16
29
  m.hamburger.classList.toggle('is-active')
@@ -23,12 +36,12 @@ const DEFAULT_OPTIONS = {
23
36
  duration: 0.35,
24
37
  x: '0%',
25
38
  opacity: 0,
26
- height: window.innerHeight
39
+ height: window.innerHeight,
27
40
  },
28
41
  {
29
42
  duration: 0.35,
30
43
  opacity: 1,
31
- ease: 'sine.in'
44
+ ease: 'sine.in',
32
45
  }
33
46
  )
34
47
  .to(
@@ -36,7 +49,7 @@ const DEFAULT_OPTIONS = {
36
49
  {
37
50
  duration: 0.35,
38
51
  opacity: 0,
39
- ease: 'power3.out'
52
+ ease: 'power3.out',
40
53
  },
41
54
  '-=0.35'
42
55
  )
@@ -45,7 +58,7 @@ const DEFAULT_OPTIONS = {
45
58
  {
46
59
  duration: 0.55,
47
60
  backgroundColor: 'transparent',
48
- ease: 'power3.out'
61
+ ease: 'power3.out',
49
62
  },
50
63
  '-=0.35'
51
64
  )
@@ -61,13 +74,13 @@ const DEFAULT_OPTIONS = {
61
74
  {
62
75
  duration: 1,
63
76
  opacity: 0,
64
- x: 20
77
+ x: 20,
65
78
  },
66
79
  {
67
80
  duration: 1,
68
81
  x: 0,
69
82
  opacity: 1,
70
- ease: 'power3.out'
83
+ ease: 'power3.out',
71
84
  },
72
85
  0.05
73
86
  )
@@ -77,14 +90,14 @@ const DEFAULT_OPTIONS = {
77
90
  duration: 0.55,
78
91
  opacity: 1,
79
92
  xPercent: 0,
80
- ease: 'power3.inOut'
93
+ ease: 'power3.inOut',
81
94
  },
82
95
  '-=1.2'
83
96
  )
84
97
  .call(m._emitMobileMenuOpenEvent)
85
98
  },
86
99
 
87
- closeTween: m => {
100
+ closeTween: (m) => {
88
101
  document.body.classList.toggle('open-menu')
89
102
  const timeline = gsap.timeline()
90
103
 
@@ -97,13 +110,13 @@ const DEFAULT_OPTIONS = {
97
110
  {
98
111
  duration: 0.2,
99
112
  opacity: 1,
100
- xPercent: 0
113
+ xPercent: 0,
101
114
  },
102
115
  {
103
116
  duration: 0.2,
104
117
  opacity: 0,
105
118
  xPercent: 5,
106
- ease: 'power3.out'
119
+ ease: 'power3.out',
107
120
  }
108
121
  )
109
122
  .set(m.logoPath, { clearProps: 'fill' })
@@ -113,7 +126,7 @@ const DEFAULT_OPTIONS = {
113
126
  duration: 0.5,
114
127
  opacity: 0,
115
128
  x: 20,
116
- ease: 'power3.out'
129
+ ease: 'power3.out',
117
130
  },
118
131
  0.04
119
132
  )
@@ -123,7 +136,7 @@ const DEFAULT_OPTIONS = {
123
136
  {
124
137
  duration: 0.25,
125
138
  x: '100%',
126
- ease: 'sine.in'
139
+ ease: 'sine.in',
127
140
  },
128
141
  '-=0.3'
129
142
  )
@@ -138,12 +151,20 @@ const DEFAULT_OPTIONS = {
138
151
  .to(m.logo, {
139
152
  duration: 0.35,
140
153
  opacity: 1,
141
- ease: 'power3.in'
154
+ ease: 'power3.in',
142
155
  })
143
- }
156
+ },
144
157
  }
145
158
 
159
+ /**
160
+ * MobileMenu component for mobile navigation menu
161
+ */
146
162
  export default class MobileMenu {
163
+ /**
164
+ * Create a new MobileMenu instance
165
+ * @param {Object} app - Application instance
166
+ * @param {MobileMenuOptions} [opts={}] - MobileMenu options
167
+ */
147
168
  constructor(app, opts = {}) {
148
169
  this.app = app
149
170
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
@@ -152,9 +173,13 @@ export default class MobileMenu {
152
173
  this.header = document.querySelector('header')
153
174
  this.bg = this.header.querySelector('.mobile-bg')
154
175
  this.logo = this.header.querySelector('figure.brand')
155
- this.logoPath = this.logo ? this.logo.querySelectorAll(this.opts.logoPathSelector) : null
176
+ this.logoPath = this.logo
177
+ ? this.logo.querySelectorAll(this.opts.logoPathSelector)
178
+ : null
156
179
  this.menuButton = this.header.querySelector('figure.menu-button')
157
- this.hamburger = this.menuButton ? this.menuButton.querySelector('.hamburger') : null
180
+ this.hamburger = this.menuButton
181
+ ? this.menuButton.querySelector('.hamburger')
182
+ : null
158
183
  this.hamburgerInner = this.menuButton
159
184
  ? this.menuButton.querySelector('.hamburger-inner')
160
185
  : null
@@ -163,7 +188,7 @@ export default class MobileMenu {
163
188
  this.nav = this.header.querySelector('nav')
164
189
 
165
190
  if (this.hamburger) {
166
- this.hamburger.addEventListener('click', e => {
191
+ this.hamburger.addEventListener('click', (e) => {
167
192
  e.preventDefault()
168
193
  e.stopPropagation()
169
194
  this.toggleMenu()
@@ -198,12 +223,16 @@ export default class MobileMenu {
198
223
  }
199
224
 
200
225
  _emitMobileMenuOpenEvent() {
201
- const mobileMenuOpenEvent = new window.CustomEvent(Events.APPLICATION_MOBILE_MENU_OPEN)
226
+ const mobileMenuOpenEvent = new window.CustomEvent(
227
+ Events.APPLICATION_MOBILE_MENU_OPEN
228
+ )
202
229
  window.dispatchEvent(mobileMenuOpenEvent)
203
230
  }
204
231
 
205
232
  _emitMobileMenuClosedEvent() {
206
- const mobileMenuClosedEvent = new window.CustomEvent(Events.APPLICATION_MOBILE_MENU_CLOSED)
233
+ const mobileMenuClosedEvent = new window.CustomEvent(
234
+ Events.APPLICATION_MOBILE_MENU_CLOSED
235
+ )
207
236
  window.dispatchEvent(mobileMenuClosedEvent)
208
237
  }
209
238
  }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Vendor imports
3
3
  */
4
- import { gsap } from 'gsap'
5
- import { CSSPlugin } from 'gsap/CSSPlugin'
4
+ import { gsap, CSSPlugin } from 'gsap/all'
6
5
  import _defaultsDeep from 'lodash.defaultsdeep'
7
6
 
8
7
  /**
@@ -16,6 +15,50 @@ import Dom from '../Dom'
16
15
 
17
16
  gsap.registerPlugin(CSSPlugin)
18
17
 
18
+ /**
19
+ * @typedef {Object} MoonwalkTransition
20
+ * @property {Object} from - Starting properties for the transition
21
+ * @property {Object} to - Ending properties for the transition
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} MoonwalkWalk
26
+ * @property {number} [startDelay=0] - Delay before the animation starts
27
+ * @property {number} [interval=0.15] - Time between animations in a sequence
28
+ * @property {number} [duration=0.65] - Duration of the animation
29
+ * @property {boolean|Object} [alphaTween=false] - Whether to add a separate opacity tween
30
+ * @property {MoonwalkTransition} transition - The transition configuration
31
+ * @property {string} [sectionTargets] - CSS selector for targeting elements in named sections
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} MoonwalkRun
36
+ * @property {number} [threshold=0] - IntersectionObserver threshold
37
+ * @property {Function} callback - Function called when element enters viewport
38
+ * @property {Function} [onExit] - Function called when element exits viewport
39
+ * @property {boolean} [repeated=false] - Whether the run should repeat
40
+ * @property {string} [rootMargin] - IntersectionObserver rootMargin
41
+ * @property {Function} [initialize] - Function called during initialization
42
+ */
43
+
44
+ /**
45
+ * @typedef {Object} MoonwalkOptions
46
+ * @property {string|Function} [on=Events.APPLICATION_REVEALED] - Event to trigger animations
47
+ * @property {number} [initialDelay=0.1] - Delay before starting animations
48
+ * @property {boolean} [clearLazyload=false] - Clear data-ll-srcset attributes
49
+ * @property {boolean} [clearNestedSections=true] - Remove nested data-moonwalk-section attributes
50
+ * @property {boolean} [clearNestedWalks=true] - Remove nested data-moonwalk attributes
51
+ * @property {boolean} [clearMoonwalkOnAnchors=true] - Disable animations when page loaded via anchor
52
+ * @property {boolean} [warnRunWithSection=true] - Warn when run and section on same element
53
+ * @property {string} [rootMargin='-10% 0%'] - Default IntersectionObserver rootMargin
54
+ * @property {number} [threshold=0] - Default IntersectionObserver threshold
55
+ * @property {boolean} [uniqueIds=false] - Generate unique IDs for moonwalk elements
56
+ * @property {boolean} [addIndexes=false] - Add index attributes to elements
57
+ * @property {Object.<string, MoonwalkRun>} [runs={}] - Run configurations
58
+ * @property {Object.<string, MoonwalkWalk>} walks - Walk configurations
59
+ */
60
+
61
+ /** @type {MoonwalkOptions} */
19
62
  const DEFAULT_OPTIONS = {
20
63
  /**
21
64
  * If your app needs to do some initialization before the
@@ -102,7 +145,15 @@ const DEFAULT_OPTIONS = {
102
145
  },
103
146
  }
104
147
 
148
+ /**
149
+ * Moonwalk animation system for scroll-based reveal animations
150
+ */
105
151
  export default class Moonwalk {
152
+ /**
153
+ * @param {Object} app - The application instance
154
+ * @param {MoonwalkOptions} [opts={}] - Configuration options
155
+ * @param {HTMLElement} [container=document.body] - Container element
156
+ */
106
157
  constructor(app, opts = {}, container = document.body) {
107
158
  this.app = app
108
159
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
@@ -648,15 +699,56 @@ export default class Moonwalk {
648
699
  * @param {*} rootMargin
649
700
  */
650
701
  runObserver(run, rootMargin) {
702
+ // Store the previous positions of observed elements to compare for exit direction
703
+ const elementPositions = new WeakMap()
704
+
651
705
  return new IntersectionObserver(
652
706
  (entries, self) => {
653
707
  for (let i = 0; i < entries.length; i += 1) {
654
708
  const entry = entries[i]
709
+
710
+ // Store the element's current position in the viewport
711
+ const boundingRect = entry.boundingClientRect
712
+ const viewportHeight = window.innerHeight
713
+ const viewportWidth = window.innerWidth
714
+
655
715
  if (entry.isIntersecting && run.callback) {
716
+ // Calculate entry direction
717
+ let meta = { direction: null }
718
+
719
+ // Use the app's scroll direction for reliable detection
720
+ // If scrollDirection is null, the element was likely revealed on initial load
721
+ if (this.app.state && this.app.state.scrollDirection) {
722
+ // Map scroll direction to viewport entry direction
723
+ switch (this.app.state.scrollDirection) {
724
+ case 'down':
725
+ meta.direction = 'bottom' // When scrolling down, elements enter from bottom
726
+ break
727
+ case 'up':
728
+ meta.direction = 'top' // When scrolling up, elements enter from top
729
+ break
730
+ case 'right':
731
+ meta.direction = 'left' // When scrolling right, elements enter from left
732
+ break
733
+ case 'left':
734
+ meta.direction = 'right' // When scrolling left, elements enter from right
735
+ break
736
+ }
737
+ }
738
+ // If no scroll direction is available, direction remains null
739
+
740
+ // Store the element's position when it enters the viewport
741
+ elementPositions.set(entry.target, {
742
+ top: boundingRect.top,
743
+ bottom: boundingRect.bottom,
744
+ left: boundingRect.left,
745
+ right: boundingRect.right
746
+ })
747
+
656
748
  const runRepeated = entry.target.hasAttribute(
657
749
  'data-moonwalk-run-triggered'
658
750
  )
659
- run.callback(entry.target, runRepeated)
751
+ run.callback(entry.target, runRepeated, meta)
660
752
  entry.target.setAttribute('data-moonwalk-run-triggered', '')
661
753
  if (!run.onExit && !run.repeated) {
662
754
  self.unobserve(entry.target)
@@ -670,7 +762,42 @@ export default class Moonwalk {
670
762
  'data-moonwalk-run-exit-triggered'
671
763
  )
672
764
  entry.target.setAttribute('data-moonwalk-run-exit-triggered', '')
673
- run.onExit(entry.target, runExited)
765
+
766
+ // Calculate exit direction
767
+ let meta = { direction: null }
768
+
769
+ // Use the app's scroll direction for reliable detection
770
+ // For exit direction, it's the opposite of the entry direction for the same scroll
771
+ if (this.app.state && this.app.state.scrollDirection) {
772
+ // Map scroll direction to viewport exit direction
773
+ switch (this.app.state.scrollDirection) {
774
+ case 'down':
775
+ meta.direction = 'top' // When scrolling down, elements exit from top
776
+ break
777
+ case 'up':
778
+ meta.direction = 'bottom' // When scrolling up, elements exit from bottom
779
+ break
780
+ case 'right':
781
+ meta.direction = 'right' // When scrolling right, elements exit from right
782
+ break
783
+ case 'left':
784
+ meta.direction = 'left' // When scrolling left, elements exit from left
785
+ break
786
+ }
787
+ } else {
788
+ // If no scroll direction is available, use the simplest position-based check
789
+ if (boundingRect.bottom <= 0) {
790
+ meta.direction = 'top'
791
+ } else if (boundingRect.top >= viewportHeight) {
792
+ meta.direction = 'bottom'
793
+ } else if (boundingRect.right <= 0) {
794
+ meta.direction = 'left'
795
+ } else if (boundingRect.left >= viewportWidth) {
796
+ meta.direction = 'right'
797
+ }
798
+ }
799
+
800
+ run.onExit(entry.target, runExited, meta)
674
801
  if (!run.repeated) {
675
802
  self.unobserve(entry.target)
676
803
  }