@brandocms/jupiter 3.54.4 → 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 +37 -5
  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,7 +1,26 @@
1
- import { gsap } from 'gsap'
1
+ import { gsap } from 'gsap/all'
2
2
  import _defaultsDeep from 'lodash.defaultsdeep'
3
3
 
4
+ /**
5
+ * @typedef {Object} PopupOptions
6
+ * @property {string} [selector] - CSS selector to find popup elements
7
+ * @property {Function} [responsive] - Function that determines if popup should be shown on current breakpoint
8
+ * @property {Function} [onOpen] - Called when popup opens
9
+ * @property {Function} [onClose] - Called when popup closes
10
+ * @property {Function} [tweenIn] - Animation function for opening popup
11
+ * @property {Function} [tweenOut] - Animation function for closing popup
12
+ */
13
+
14
+ /** @type {PopupOptions} */
4
15
  const DEFAULT_OPTIONS = {
16
+ /**
17
+ * selector
18
+ *
19
+ * CSS selector to find popup elements
20
+ * Default: '[data-popup]'
21
+ */
22
+ selector: '[data-popup]',
23
+
5
24
  /**
6
25
  * responsive
7
26
  *
@@ -30,104 +49,211 @@ const DEFAULT_OPTIONS = {
30
49
  x: -5,
31
50
  xPercent: -50,
32
51
  opacity: 0,
33
- display: 'block'
52
+ display: 'block',
34
53
  },
35
54
  {
36
55
  duration: 0.3,
37
56
  yPercent: -50,
38
57
  xPercent: -50,
39
58
  x: 0,
40
- opacity: 1
59
+ opacity: 1,
41
60
  }
42
61
  )
43
- }
62
+ },
44
63
  })
45
64
  },
46
65
 
47
- tweenOut: popup => {
48
- const popups = document.querySelectorAll('[data-popup]')
49
- gsap.to(popups, {
50
- duration: 0.3,
51
- opacity: 0,
52
- display: 'none'
53
- })
66
+ tweenOut: (popup) => {
67
+ console.log('default tweenOut')
68
+ const popupElement = popup.currentPopup
69
+ if (popupElement) {
70
+ gsap.to(popupElement, {
71
+ duration: 0.3,
72
+ opacity: 0,
73
+ display: 'none',
74
+ })
75
+ }
54
76
  gsap.to(popup.backdrop, {
55
77
  duration: 0.3,
56
78
  opacity: 0,
57
79
  onComplete: () => {
58
- gsap.set(popup.backdrop, { display: 'none' })
59
- }
80
+ // Remove the backdrop completely instead of just hiding it
81
+ popup.backdrop.remove()
82
+ },
60
83
  })
61
- }
84
+ },
62
85
  }
63
86
 
87
+ /**
88
+ * Popup component for modal dialogs and popups
89
+ */
64
90
  export default class Popup {
65
- constructor(app, opts = {}) {
91
+ /**
92
+ * Create a new Popup instance
93
+ * @param {Object} app - Application instance
94
+ * @param {string} [selector] - CSS selector to find popup elements
95
+ * @param {PopupOptions} [opts={}] - Popup options
96
+ */
97
+ constructor(app, selector = '[data-popup]', opts = {}) {
66
98
  this.app = app
67
99
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
68
- this.createBackdrop()
100
+ this.opts.selector = selector
101
+ this.backdrop = null
102
+ this.currentPopup = null
103
+ this.popupKey = null
69
104
  this.bindTriggers()
70
105
  }
71
106
 
107
+ /**
108
+ * Bind click handlers to popup triggers and close buttons
109
+ */
72
110
  bindTriggers() {
73
- const triggers = document.querySelectorAll('[data-popup-trigger]')
74
- const closers = document.querySelectorAll('[data-popup-close]')
111
+ // Find all triggers that match this popup's selector
112
+ const allTriggers = document.querySelectorAll('[data-popup-trigger]')
113
+ const matchingTriggers = Array.from(allTriggers).filter(trigger => {
114
+ const target = trigger.getAttribute('data-popup-trigger')
115
+ if (typeof target === 'string') {
116
+ const element = document.querySelector(target)
117
+ return element && element.matches(this.opts.selector)
118
+ }
119
+ return false
120
+ })
75
121
 
76
- Array.from(triggers).forEach(trigger => {
122
+ // Get all popups that match this instance's selector
123
+ const popups = document.querySelectorAll(this.opts.selector)
124
+
125
+ // Find all close buttons inside matching popups
126
+ const closers = []
127
+ popups.forEach(popup => {
128
+ const popupClosers = popup.querySelectorAll('[data-popup-close]')
129
+ closers.push(...popupClosers)
130
+ })
131
+
132
+ // Bind click handlers to matching triggers
133
+ matchingTriggers.forEach((trigger) => {
77
134
  const triggerTarget = trigger.getAttribute('data-popup-trigger')
78
- trigger.addEventListener('click', event => {
135
+ // Get the popup key if present
136
+ const popupKey = trigger.getAttribute('data-popup-key') || this.getKeyFromTarget(triggerTarget)
137
+
138
+ trigger.addEventListener('click', (event) => {
79
139
  if (this.opts.responsive(this.app)) {
80
140
  event.stopImmediatePropagation()
81
141
  event.preventDefault()
82
- this.open(trigger, triggerTarget)
142
+ this.open(trigger, triggerTarget, popupKey)
83
143
  }
84
144
  })
85
145
  })
86
146
 
87
- Array.from(closers).forEach(closer => {
88
- closer.addEventListener('click', event => {
147
+ // Bind click handlers to close buttons
148
+ closers.forEach((closer) => {
149
+ // Get the popup key from the closest parent popup element
150
+ const popupElement = closer.closest(this.opts.selector)
151
+ const popupKey = popupElement ? popupElement.getAttribute('data-popup-key') : null
152
+
153
+ closer.addEventListener('click', (event) => {
89
154
  event.stopImmediatePropagation()
90
155
  event.preventDefault()
91
- this.opts.onClose(this)
92
- this.close()
156
+
157
+ // Only close if keys match or no key is set
158
+ if (!this.popupKey || !popupKey || this.popupKey === popupKey) {
159
+ this.opts.onClose(this)
160
+ this.close()
161
+ }
93
162
  })
94
163
  })
95
164
  }
96
165
 
97
- createBackdrop() {
166
+ /**
167
+ * Extract key from target selector or element
168
+ * @param {HTMLElement|string} target - Target element or selector
169
+ * @returns {string|null} - The popup key or null
170
+ */
171
+ getKeyFromTarget(target) {
172
+ if (typeof target === 'string') {
173
+ const element = document.querySelector(target)
174
+ return element ? element.getAttribute('data-popup-key') : null
175
+ } else if (target instanceof HTMLElement) {
176
+ return target.getAttribute('data-popup-key')
177
+ }
178
+ return null
179
+ }
180
+
181
+ /**
182
+ * Create backdrop element for popup
183
+ * @param {string|null} key - Optional popup key to associate with backdrop
184
+ * @returns {HTMLElement} The created backdrop element
185
+ */
186
+ createBackdrop(key) {
98
187
  const backdrop = document.createElement('div')
99
188
  backdrop.setAttribute('data-popup-backdrop', '')
189
+ if (key) {
190
+ backdrop.setAttribute('data-popup-key', key)
191
+ }
100
192
  gsap.set(backdrop, { opacity: 0, display: 'none', zIndex: 4999 })
101
193
 
102
- backdrop.addEventListener('click', e => {
194
+ backdrop.addEventListener('click', (e) => {
103
195
  e.stopPropagation()
104
196
  this.close()
105
197
  })
106
198
 
107
199
  document.body.append(backdrop)
108
- this.backdrop = backdrop
200
+ return backdrop
109
201
  }
110
202
 
111
- open(trigger, target) {
203
+ /**
204
+ * Open a popup
205
+ * @param {HTMLElement} trigger - Element that triggered the popup
206
+ * @param {HTMLElement|string} target - Popup element or selector
207
+ * @param {string|null} key - Optional popup key
208
+ */
209
+ open(trigger, target, key = null) {
112
210
  this.keyUpListener = this.onKeyup.bind(this)
113
211
  document.addEventListener('keyup', this.keyUpListener)
212
+
213
+ // Store the popup key
214
+ this.popupKey = key || this.getKeyFromTarget(target)
215
+
216
+ // Create a new backdrop for this popup
217
+ this.backdrop = this.createBackdrop(this.popupKey)
218
+
114
219
  if (typeof target === 'string') {
115
220
  target = document.querySelector(target)
116
221
  }
117
222
 
118
223
  if (!target) {
119
224
  console.error(`JUPITER/POPUP >>> Element ${target} not found`)
225
+ return
226
+ }
227
+
228
+ // Store the current popup element for reference
229
+ this.currentPopup = target
230
+
231
+ // If key isn't already set on the popup element, set it now
232
+ if (this.popupKey && !target.hasAttribute('data-popup-key')) {
233
+ target.setAttribute('data-popup-key', this.popupKey)
120
234
  }
235
+
121
236
  this.opts.onOpen(trigger, target, this)
122
237
  this.opts.tweenIn(trigger, target, this)
123
238
  }
124
239
 
240
+ /**
241
+ * Close the popup
242
+ */
125
243
  close() {
126
244
  document.removeEventListener('keyup', this.keyUpListener)
127
245
  this.opts.onClose(this)
128
246
  this.opts.tweenOut(this)
247
+
248
+ // Reset popup state
249
+ this.popupKey = null
250
+ this.currentPopup = null
129
251
  }
130
252
 
253
+ /**
254
+ * Handle keyup event for Escape key to close popup
255
+ * @param {KeyboardEvent} e - Keyboard event
256
+ */
131
257
  onKeyup(e) {
132
258
  const key = e.keyCode || e.which
133
259
 
@@ -1,17 +1,34 @@
1
1
  import _defaultsDeep from 'lodash.defaultsdeep'
2
2
  import Dom from '../Dom'
3
3
 
4
+ /**
5
+ * @typedef {Object} ScrollSpyOptions
6
+ * @property {Function} [onIntersect] - Called when a target intersects with the viewport
7
+ */
8
+
9
+ /** @type {ScrollSpyOptions} */
4
10
  const DEFAULT_OPTIONS = {
5
11
  onIntersect: (target, trigger) => {}
6
12
  }
7
13
 
14
+ /**
15
+ * ScrollSpy component for highlighting active sections during scrolling
16
+ */
8
17
  export default class ScrollSpy {
18
+ /**
19
+ * Create a new ScrollSpy instance
20
+ * @param {Object} app - Application instance
21
+ * @param {ScrollSpyOptions} [opts={}] - ScrollSpy options
22
+ */
9
23
  constructor(app, opts = {}) {
10
24
  this.app = app
11
25
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
12
26
  this.initialize()
13
27
  }
14
28
 
29
+ /**
30
+ * Initialize ScrollSpy
31
+ */
15
32
  initialize() {
16
33
  this.triggers = Dom.all('[data-scrollspy-trigger]')
17
34
  const config = {
@@ -29,6 +46,10 @@ export default class ScrollSpy {
29
46
  this.triggers.forEach(section => observer.observe(section))
30
47
  }
31
48
 
49
+ /**
50
+ * Handle intersection with viewport
51
+ * @param {IntersectionObserverEntry} entry - Intersection observer entry
52
+ */
32
53
  intersectionHandler(entry) {
33
54
  const id = entry.target.dataset.scrollspyTrigger
34
55
  const currentlyActive = document.querySelector('[data-scrollspy-active]')
@@ -1,4 +1,4 @@
1
- import { gsap } from 'gsap'
1
+ import { gsap } from 'gsap/all'
2
2
  import _defaultsDeep from 'lodash.defaultsdeep'
3
3
 
4
4
  const DEFAULT_OPTIONS = {}
@@ -13,14 +13,14 @@ export default class StackedBoxes {
13
13
  initialize() {
14
14
  const boxes = document.querySelectorAll('[data-boxes-stacked]')
15
15
 
16
- const observer = new IntersectionObserver(entries => {
16
+ const observer = new IntersectionObserver((entries) => {
17
17
  const [{ isIntersecting, target }] = entries
18
18
  if (isIntersecting) {
19
19
  this.adjustBox(target)
20
20
  }
21
21
  })
22
22
 
23
- Array.from(boxes).forEach(box => {
23
+ Array.from(boxes).forEach((box) => {
24
24
  observer.observe(box)
25
25
  })
26
26
  }
@@ -53,7 +53,9 @@ export default class StackedBoxes {
53
53
  break
54
54
 
55
55
  default:
56
- console.error('==> JUPITER/STACKEDBOXES: `data-boxes-stacked-pull` has wrong value')
56
+ console.error(
57
+ '==> JUPITER/STACKEDBOXES: `data-boxes-stacked-pull` has wrong value'
58
+ )
57
59
  }
58
60
  this.pull(pull, pullPx)
59
61
  }
@@ -23,36 +23,36 @@
23
23
  *
24
24
  */
25
25
 
26
- import { gsap } from 'gsap'
26
+ import { gsap } from 'gsap/all'
27
27
  import _defaultsDeep from 'lodash.defaultsdeep'
28
28
  import * as Events from '../../events'
29
29
 
30
30
  const DEFAULT_EVENTS = {
31
- onMainVisible: h => {
31
+ onMainVisible: (h) => {
32
32
  gsap.to(h.el, {
33
33
  duration: 3,
34
34
  opacity: 1,
35
- delay: 0.5
35
+ delay: 0.5,
36
36
  })
37
37
  },
38
38
 
39
- onMainInvisible: h => {
39
+ onMainInvisible: (h) => {
40
40
  gsap.to(h.el, {
41
41
  duration: 1,
42
- opacity: 0
42
+ opacity: 0,
43
43
  })
44
44
  },
45
45
 
46
- onPin: h => {
46
+ onPin: (h) => {
47
47
  gsap.to(h.auxEl, {
48
48
  duration: 0.35,
49
49
  yPercent: '0',
50
50
  ease: 'sine.out',
51
- autoRound: true
51
+ autoRound: true,
52
52
  })
53
53
  },
54
54
 
55
- onUnpin: h => {
55
+ onUnpin: (h) => {
56
56
  h._hiding = true
57
57
  gsap.to(h.auxEl, {
58
58
  duration: 0.25,
@@ -61,10 +61,10 @@ const DEFAULT_EVENTS = {
61
61
  autoRound: true,
62
62
  onComplete: () => {
63
63
  h._hiding = false
64
- }
64
+ },
65
65
  })
66
66
  },
67
- onSmall: () => {}
67
+ onSmall: () => {},
68
68
  }
69
69
 
70
70
  const DEFAULT_OPTIONS = {
@@ -75,12 +75,12 @@ const DEFAULT_OPTIONS = {
75
75
  unPinOnResize: false,
76
76
 
77
77
  default: {
78
- onClone: h => h.el.cloneNode(true),
78
+ onClone: (h) => h.el.cloneNode(true),
79
79
  canvas: window,
80
- beforeEnter: h => {
80
+ beforeEnter: (h) => {
81
81
  gsap.set(h.el, { opacity: 0 })
82
82
  },
83
- enter: h => {
83
+ enter: (h) => {
84
84
  const timeline = gsap.timeline()
85
85
  timeline
86
86
  .set(h.auxEl, { yPercent: -100 })
@@ -89,7 +89,7 @@ const DEFAULT_OPTIONS = {
89
89
  yPercent: 0,
90
90
  delay: h.opts.enterDelay,
91
91
  ease: 'power3.out',
92
- autoRound: true
92
+ autoRound: true,
93
93
  })
94
94
  .staggerTo(h.lis, 0.8, { opacity: 1, ease: 'sine.in' }, 0.1, '-=1')
95
95
  },
@@ -98,8 +98,8 @@ const DEFAULT_OPTIONS = {
98
98
  offset: 0, // how far from the top before we trigger hide
99
99
  offsetSmall: 50, // how far from the top before we trigger the shrinked padding,
100
100
  offsetBg: 200, // how far down before changing backgroundcolor
101
- ...DEFAULT_EVENTS
102
- }
101
+ ...DEFAULT_EVENTS,
102
+ },
103
103
  }
104
104
 
105
105
  export default class StickyHeader {
@@ -174,14 +174,18 @@ export default class StickyHeader {
174
174
  this._bindMobileMenuListeners()
175
175
 
176
176
  if (this.opts.unPinOnResize) {
177
- window.addEventListener(Events.APPLICATION_RESIZE, this.setResizeTimer.bind(this), false)
177
+ window.addEventListener(
178
+ Events.APPLICATION_RESIZE,
179
+ this.setResizeTimer.bind(this),
180
+ false
181
+ )
178
182
  }
179
183
 
180
184
  this.opts.beforeEnter(this)
181
185
  }
182
186
 
183
187
  setupObserver() {
184
- this.observer = new IntersectionObserver(entries => {
188
+ this.observer = new IntersectionObserver((entries) => {
185
189
  const [{ isIntersecting }] = entries
186
190
 
187
191
  if (isIntersecting) {
@@ -200,7 +204,11 @@ export default class StickyHeader {
200
204
  }
201
205
  })
202
206
 
203
- window.addEventListener(Events.APPLICATION_SCROLL, this.update.bind(this), false)
207
+ window.addEventListener(
208
+ Events.APPLICATION_SCROLL,
209
+ this.update.bind(this),
210
+ false
211
+ )
204
212
 
205
213
  if (this.mainOpts.pinOnForcedScroll) {
206
214
  window.addEventListener(Events.APPLICATION_FORCED_SCROLL_START, () => {
@@ -292,7 +300,10 @@ export default class StickyHeader {
292
300
  }
293
301
 
294
302
  checkBot(force) {
295
- if (this.currentScrollY + this.getViewportHeight() >= this.getScrollerHeight()) {
303
+ if (
304
+ this.currentScrollY + this.getViewportHeight() >=
305
+ this.getScrollerHeight()
306
+ ) {
296
307
  if (force) {
297
308
  this.bottom()
298
309
  } else if (!this._bottom) {
@@ -420,7 +431,8 @@ export default class StickyHeader {
420
431
  isOutOfBounds() {
421
432
  const pastTop = this.currentScrollY < 0
422
433
  const pastBottom =
423
- this.currentScrollY + this.getScrollerPhysicalHeight() > this.getScrollerHeight()
434
+ this.currentScrollY + this.getScrollerPhysicalHeight() >
435
+ this.getScrollerHeight()
424
436
 
425
437
  return pastTop || pastBottom
426
438
  }
@@ -453,7 +465,9 @@ export default class StickyHeader {
453
465
 
454
466
  getViewportHeight() {
455
467
  return (
456
- window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
468
+ window.innerHeight ||
469
+ document.documentElement.clientHeight ||
470
+ document.body.clientHeight
457
471
  )
458
472
  }
459
473
 
@@ -472,11 +486,18 @@ export default class StickyHeader {
472
486
  if (this.opts.canvas.scrollTop !== undefined) {
473
487
  return this.opts.canvas.scrollTop
474
488
  }
475
- return (document.documentElement || document.body.parentNode || document.body).scrollTop
489
+ return (
490
+ document.documentElement ||
491
+ document.body.parentNode ||
492
+ document.body
493
+ ).scrollTop
476
494
  }
477
495
 
478
496
  toleranceExceeded() {
479
- return Math.abs(this.currentScrollY - this.lastKnownScrollY) >= this.opts.tolerance
497
+ return (
498
+ Math.abs(this.currentScrollY - this.lastKnownScrollY) >=
499
+ this.opts.tolerance
500
+ )
480
501
  }
481
502
 
482
503
  _getOptionsForSection(section, opts) {
@@ -1,17 +1,37 @@
1
- import { gsap } from 'gsap'
1
+ import { gsap } from 'gsap/all'
2
2
  import Dom from '../Dom'
3
3
 
4
+ /**
5
+ * Toggler component for show/hide functionality
6
+ * Uses [data-toggle-trigger] for the toggle button and [data-toggle-content] for toggleable content
7
+ */
4
8
  export default class Toggler {
9
+ /**
10
+ * Create a new Toggler instance
11
+ * @param {Object} app - Application instance
12
+ * @param {HTMLElement} el - Container element with [data-toggle] attribute
13
+ */
5
14
  constructor(app, el) {
6
15
  this.open = false
7
16
  this.app = app
8
17
  this.el = el
9
18
  this.trigger = Dom.find(this.el, '[data-toggle-trigger]')
19
+ this.triggerTarget = this.trigger.dataset.toggleTrigger
20
+ if (this.triggerTarget) {
21
+ this.content = Dom.all(
22
+ this.el,
23
+ `[data-toggle-content="${this.triggerTarget}"]`
24
+ )
25
+ } else {
26
+ this.content = Dom.all(this.el, '[data-toggle-content]')
27
+ }
10
28
  this.triggerIcon = Dom.find(this.trigger, 'span.icon')
11
- this.content = Dom.find(this.el, '[data-toggle-content]')
12
29
  this.trigger.addEventListener('click', this.onClick.bind(this))
13
30
  }
14
31
 
32
+ /**
33
+ * Handle click on trigger element
34
+ */
15
35
  onClick() {
16
36
  this.toggleState()
17
37
 
@@ -21,7 +41,17 @@ export default class Toggler {
21
41
  }
22
42
  gsap.set(this.content, { height: 'auto', display: 'block' })
23
43
  this.el.classList.toggle('open')
24
- gsap.from(this.content, { height: 0, ease: 'power1.inOut' })
44
+ gsap.from(this.content, {
45
+ height: 0,
46
+ ease: 'power1.inOut',
47
+ stagger: 0.1,
48
+ onComplete: () => {
49
+ this.content.forEach((el) => el.removeAttribute('data-toggle-hidden'))
50
+ this.content.forEach((el) =>
51
+ el.setAttribute('data-toggle-visible', '')
52
+ )
53
+ },
54
+ })
25
55
  } else {
26
56
  if (this.triggerIcon) {
27
57
  this.triggerIcon.classList.toggle('active')
@@ -30,13 +60,22 @@ export default class Toggler {
30
60
  duration: 0.25,
31
61
  onComplete: () => {
32
62
  this.el.classList.toggle('open')
33
- }
63
+ this.content.forEach((el) =>
64
+ el.removeAttribute('data-toggle-visible')
65
+ )
66
+ this.content.forEach((el) =>
67
+ el.setAttribute('data-toggle-hidden', '')
68
+ )
69
+ },
34
70
  })
35
71
 
36
- gsap.to(this.content, { height: 0, ease: 'power3.out' })
72
+ gsap.to(this.content, { height: 0, ease: 'power3.out', stagger: 0.1 })
37
73
  }
38
74
  }
39
75
 
76
+ /**
77
+ * Toggle open/closed state
78
+ */
40
79
  toggleState() {
41
80
  if (this.open) {
42
81
  this.open = false