@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,79 +1,302 @@
1
1
  import _defaultsDeep from 'lodash.defaultsdeep'
2
- import { gsap } from 'gsap'
3
2
  import * as Events from '../../events'
4
3
 
5
- // Default Settings
4
+ /**
5
+ * @typedef {Object} ParallaxOptions
6
+ * @property {string|HTMLElement} [el='[data-parallax]'] - Target element selector or element
7
+ * @property {number} [factor=1.3] - Default parallax movement factor
8
+ * @property {boolean} [fadeContent=true] - Whether to fade content while scrolling
9
+ * @property {number} [scale=1.2] - Scale factor to apply to parallax images
10
+ * @property {number} [delay=0.1] - Delay factor to smooth the effect
11
+ * @property {string} [orientation='up'] - Direction of parallax effect ('up', 'down', 'left', 'right')
12
+ * @property {boolean} [overflow=false] - Whether to show element overflow
13
+ */
14
+
15
+ /** @type {ParallaxOptions} */
6
16
  const DEFAULT_OPTIONS = {
7
17
  el: '[data-parallax]',
8
18
  factor: 1.3,
9
- fadeContent: true
19
+ fadeContent: true,
20
+ scale: 1.2,
21
+ delay: 0.1,
22
+ orientation: 'up',
23
+ overflow: false,
10
24
  }
11
25
 
26
+ /**
27
+ * Parallax scrolling effect inspired by SimpleParallax.js
28
+ */
12
29
  export default class Parallax {
30
+ /**
31
+ * Create a new Parallax instance
32
+ * @param {Object} app - Application instance
33
+ * @param {ParallaxOptions} [opts={}] - Parallax options
34
+ */
13
35
  constructor(app, opts = {}) {
14
36
  this.app = app
15
37
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
16
38
  this.elements = {}
17
-
39
+ this.parallaxElements = []
40
+
41
+ // Get all elements to apply parallax to
18
42
  if (typeof this.opts.el === 'string') {
19
- this.elements.wrapper = document.querySelector(this.opts.el)
20
- } else {
21
- this.elements.wrapper = this.opts.el
43
+ if (this.opts.el.includes('[data-parallax-parent]')) {
44
+ // Handle multiple elements within parent
45
+ const parent = document.querySelector(this.opts.el)
46
+ if (parent) {
47
+ this.elements.parent = parent
48
+ const children = parent.querySelectorAll('[data-parallax-factor]')
49
+ children.forEach(el => this.setupParallaxElement(el))
50
+ }
51
+ } else {
52
+ // Handle single element or multiple elements with same selector
53
+ const elements = document.querySelectorAll(this.opts.el)
54
+ elements.forEach(el => this.setupParallaxElement(el))
55
+ }
56
+ } else if (this.opts.el instanceof HTMLElement) {
57
+ // Handle single element passed directly
58
+ this.setupParallaxElement(this.opts.el)
22
59
  }
23
-
24
- this.elements.content = this.elements.wrapper.querySelector('[data-parallax-content]')
25
- this.elements.figure = this.elements.wrapper.querySelector('[data-parallax-figure]')
26
-
27
- this.initializeTimeline()
28
- window.addEventListener(Events.APPLICATION_SCROLL, this.onScroll.bind(this))
60
+
61
+ // Bind events
62
+ this.onScroll = this.onScroll.bind(this)
63
+ window.addEventListener(Events.APPLICATION_SCROLL, this.onScroll)
64
+ window.addEventListener('resize', this.onScroll)
65
+
66
+ // Initial positioning
67
+ this.onScroll()
29
68
  }
30
-
31
- initializeTimeline() {
32
- this.timeline = gsap.timeline({
33
- useFrames: true,
34
- paused: true
69
+
70
+ /**
71
+ * Set up a parallax element with its properties
72
+ * @param {HTMLElement} el - Element to set up
73
+ */
74
+ setupParallaxElement(el) {
75
+ // Get element-specific options
76
+ const factor = el.hasAttribute('data-parallax-factor') ?
77
+ parseFloat(el.getAttribute('data-parallax-factor')) :
78
+ this.opts.factor
79
+
80
+ // For traditional parallax, use the global fadeContent option
81
+ // For individual elements, check for data-parallax-fade attribute
82
+ let fadeContent = el.hasAttribute('data-parallax') ?
83
+ this.opts.fadeContent :
84
+ el.hasAttribute('data-parallax-fade')
85
+
86
+ const orientation = el.hasAttribute('data-parallax-orientation') ?
87
+ el.getAttribute('data-parallax-orientation') :
88
+ this.opts.orientation
89
+
90
+ // Set up for traditional parallax if needed
91
+ let contentEl = null
92
+ let figureEl = null
93
+
94
+ if (el.hasAttribute('data-parallax')) {
95
+ contentEl = el.querySelector('[data-parallax-content]')
96
+ figureEl = el.querySelector('[data-parallax-figure]')
97
+
98
+ // Apply overflow style if needed
99
+ if (!this.opts.overflow) {
100
+ el.style.overflow = 'hidden'
101
+ }
102
+
103
+ // Scale up the figure element to prevent showing background during parallax
104
+ if (figureEl) {
105
+ figureEl.style.transform = `scale(${this.opts.scale})`
106
+ figureEl.style.willChange = 'transform'
107
+ figureEl.style.transformOrigin = 'center'
108
+
109
+ // Make sure background fills the container
110
+ const computedStyle = window.getComputedStyle(figureEl)
111
+ if (computedStyle.backgroundImage && computedStyle.backgroundImage !== 'none') {
112
+ if (computedStyle.backgroundSize !== 'cover') {
113
+ figureEl.style.backgroundSize = 'cover'
114
+ }
115
+ if (computedStyle.backgroundPosition !== 'center') {
116
+ figureEl.style.backgroundPosition = 'center'
117
+ }
118
+ }
119
+ }
120
+
121
+ if (contentEl) {
122
+ contentEl.style.willChange = fadeContent ? 'transform, opacity' : 'transform'
123
+ contentEl.style.zIndex = '1' // Ensure content is above the figure
124
+ }
125
+ } else {
126
+ // For individual elements in multi-element parallax
127
+ el.style.willChange = fadeContent ? 'transform, opacity' : 'transform'
128
+ }
129
+
130
+ // Store the element and its settings
131
+ this.parallaxElements.push({
132
+ element: el,
133
+ factor,
134
+ fadeContent,
135
+ orientation,
136
+ content: contentEl,
137
+ figure: figureEl,
138
+ elementHeight: el.offsetHeight,
139
+ elementWidth: el.offsetWidth,
140
+ lastPosition: 0
35
141
  })
36
-
37
- if (this.opts.fadeContent) {
38
- this.timeline.to(
39
- this.elements.content,
40
- {
41
- duration: this.app.size.height * 0.4,
42
- opacity: 0,
43
- ease: 'power0.none'
44
- },
45
- 0
46
- )
142
+ }
143
+
144
+ /**
145
+ * Calculate the transform value based on scroll position
146
+ * @param {Object} item - Parallax element data
147
+ * @param {number} scrollPosition - Current scroll position
148
+ * @returns {Object} Transform and opacity values
149
+ */
150
+ calculateTransform(item, scrollPosition) {
151
+ const { element, factor, fadeContent, orientation } = item
152
+
153
+ // Get element position
154
+ const rect = element.getBoundingClientRect()
155
+ const windowHeight = this.app.size.height
156
+ const windowMiddle = windowHeight / 2
157
+
158
+ // Calculate how far the element is from the middle of the viewport
159
+ const elementMiddle = rect.top + rect.height / 2
160
+ const distanceFromMiddle = elementMiddle - windowMiddle
161
+
162
+ // Calculate percentage through viewport (-1 to 1 range, 0 at center)
163
+ const percentageThrough = distanceFromMiddle / windowHeight
164
+
165
+ // Apply the factor to create parallax effect
166
+ const movement = percentageThrough * factor * 100
167
+
168
+ // Calculate opacity for fade effect
169
+ let opacity = 1;
170
+
171
+ // Only apply fade if explicitly enabled
172
+ if (fadeContent) {
173
+ // Calculate position in viewport (0 = top of viewport, 1 = bottom of viewport)
174
+ const viewportPosition = rect.top / windowHeight;
175
+
176
+ // Fade in as element enters viewport from bottom (opacity 0->1)
177
+ // Fade out as element leaves viewport at top (opacity 1->0)
178
+ if (viewportPosition <= 0) {
179
+ // Element is at top or above viewport - fade out based on how far above
180
+ opacity = Math.max(0, 1 + (viewportPosition * 1.5));
181
+ } else if (viewportPosition >= 0.7) {
182
+ // Element is at bottom of viewport - fade in based on position
183
+ opacity = Math.max(0, 1 - ((viewportPosition - 0.7) * 3.33));
184
+ }
185
+
186
+ // Ensure opacity is within valid range
187
+ opacity = Math.max(0, Math.min(1, opacity));
47
188
  }
48
-
49
- this.timeline.to(
50
- this.elements.content,
51
- {
52
- duration: this.app.size.height * 0.5,
53
- y: this.app.size.height * 0.1,
54
- ease: 'power0.none'
55
- },
56
- 0
57
- )
58
-
59
- this.timeline.fromTo(
60
- this.elements.figure,
61
- {
62
- duration: this.app.size.height,
63
- yPercent: 0
64
- },
65
- {
66
- duration: this.app.size.height,
67
- yPercent: (this.app.size.height * this.opts.factor) / 100,
68
- ease: 'power0.none'
69
- },
70
- 0
71
- )
189
+
190
+ // Create transform based on orientation
191
+ let transform = ''
192
+
193
+ switch (orientation) {
194
+ case 'up':
195
+ transform = `translate3d(0, ${movement}px, 0)`
196
+ break
197
+ case 'down':
198
+ transform = `translate3d(0, ${-movement}px, 0)`
199
+ break
200
+ case 'left':
201
+ transform = `translate3d(${movement}px, 0, 0)`
202
+ break
203
+ case 'right':
204
+ transform = `translate3d(${-movement}px, 0, 0)`
205
+ break
206
+ default:
207
+ transform = `translate3d(0, ${movement}px, 0)`
208
+ }
209
+
210
+ return { transform, opacity }
72
211
  }
73
-
212
+
213
+ /**
214
+ * Apply a smooth transition between current and target position
215
+ * @param {Object} item - Parallax element data
216
+ * @param {Object} target - Target transform and opacity values
217
+ */
218
+ applyTransform(item, target) {
219
+ const { element, figure, content } = item
220
+
221
+ // For traditional parallax with background and content
222
+ if (figure) {
223
+ // Extract and maintain the scale transform
224
+ let scaleTransform = 'scale(1.2)';
225
+ const currentTransform = figure.style.transform;
226
+ if (currentTransform) {
227
+ const scaleMatch = currentTransform.match(/scale\([^)]+\)/);
228
+ if (scaleMatch) {
229
+ scaleTransform = scaleMatch[0];
230
+ }
231
+ }
232
+
233
+ figure.style.transform = `${scaleTransform} ${target.transform}`;
234
+ }
235
+
236
+ // Apply transform and opacity to content
237
+ if (content) {
238
+ content.style.transform = target.transform;
239
+ content.style.opacity = target.opacity;
240
+ }
241
+
242
+ // For multi-element parallax items
243
+ if (!figure && !content) {
244
+ element.style.transform = target.transform;
245
+ element.style.opacity = target.opacity;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Handle scroll event to update parallax effect
251
+ */
74
252
  onScroll() {
75
- const elTop = this.elements.wrapper.getBoundingClientRect().top
76
- const progress = Math.max(0, Math.min(elTop / this.timeline.duration(), 1))
77
- this.timeline.progress(progress)
253
+ if (!this.parallaxElements.length) return
254
+
255
+ const scrollPosition = window.pageYOffset
256
+
257
+ // Update each parallax element
258
+ this.parallaxElements.forEach(item => {
259
+ const rect = item.element.getBoundingClientRect()
260
+
261
+ // Only apply parallax if the element is in or near the viewport
262
+ if (rect.bottom > -100 && rect.top < this.app.size.height + 100) {
263
+ const transform = this.calculateTransform(item, scrollPosition)
264
+ this.applyTransform(item, transform)
265
+ }
266
+ })
267
+ }
268
+
269
+ /**
270
+ * Destroy the parallax instance and clean up
271
+ */
272
+ destroy() {
273
+ window.removeEventListener(Events.APPLICATION_SCROLL, this.onScroll)
274
+ window.removeEventListener('resize', this.onScroll)
275
+
276
+ // Reset element styles
277
+ this.parallaxElements.forEach(item => {
278
+ const { element, figure, content } = item
279
+
280
+ if (figure) {
281
+ figure.style.transform = ''
282
+ figure.style.willChange = ''
283
+ }
284
+
285
+ if (content) {
286
+ content.style.transform = ''
287
+ content.style.opacity = ''
288
+ content.style.willChange = ''
289
+ }
290
+
291
+ if (!figure && !content) {
292
+ element.style.transform = ''
293
+ element.style.opacity = ''
294
+ element.style.willChange = ''
295
+ }
296
+
297
+ element.style.overflow = ''
298
+ })
299
+
300
+ this.parallaxElements = []
78
301
  }
79
302
  }
@@ -1,4 +1,4 @@
1
- import { gsap } from 'gsap'
1
+ import { gsap } from 'gsap/all'
2
2
  import Dom from '../Dom'
3
3
  import _defaultsDeep from 'lodash.defaultsdeep'
4
4
 
@@ -21,16 +21,25 @@ export default class Popover {
21
21
  this.popover.innerHTML = popoverTemplate.innerHTML
22
22
 
23
23
  Object.assign(this.popover.style, {
24
- position: 'fixed'
24
+ position: 'fixed',
25
25
  })
26
26
 
27
27
  this.popover.classList.add(this.className)
28
28
 
29
29
  if (!app.featureTests.results.touch) {
30
- this.trigger.addEventListener('mouseenter', this.handleMouseEnter.bind(this))
31
- this.trigger.addEventListener('mouseleave', this.handleMouseLeave.bind(this))
30
+ this.trigger.addEventListener(
31
+ 'mouseenter',
32
+ this.handleMouseEnter.bind(this)
33
+ )
34
+ this.trigger.addEventListener(
35
+ 'mouseleave',
36
+ this.handleMouseLeave.bind(this)
37
+ )
32
38
  } else {
33
- this.trigger.addEventListener('touchstart', this.handleTouchStart.bind(this))
39
+ this.trigger.addEventListener(
40
+ 'touchstart',
41
+ this.handleTouchStart.bind(this)
42
+ )
34
43
  }
35
44
  }
36
45
 
@@ -53,9 +62,12 @@ export default class Popover {
53
62
  show() {
54
63
  document.body.appendChild(this.popover)
55
64
 
56
- const { top: triggerTop, left: triggerLeft } = this.trigger.getBoundingClientRect()
57
- const { offsetHeight: triggerHeight, offsetWidth: triggerWidth } = this.trigger
58
- const { offsetHeight: popoverHeight, offsetWidth: popoverWidth } = this.popover
65
+ const { top: triggerTop, left: triggerLeft } =
66
+ this.trigger.getBoundingClientRect()
67
+ const { offsetHeight: triggerHeight, offsetWidth: triggerWidth } =
68
+ this.trigger
69
+ const { offsetHeight: popoverHeight, offsetWidth: popoverWidth } =
70
+ this.popover
59
71
 
60
72
  const positionIndex = this.orderedPositions.indexOf(this.position)
61
73
 
@@ -63,36 +75,36 @@ export default class Popover {
63
75
  top: {
64
76
  name: 'top',
65
77
  top: triggerTop - popoverHeight,
66
- left: triggerLeft - (popoverWidth - triggerWidth) / 2
78
+ left: triggerLeft - (popoverWidth - triggerWidth) / 2,
67
79
  },
68
80
  right: {
69
81
  name: 'right',
70
82
  top: triggerTop - (popoverHeight - triggerHeight) / 2,
71
- left: triggerLeft + triggerWidth
83
+ left: triggerLeft + triggerWidth,
72
84
  },
73
85
  bottom: {
74
86
  name: 'bottom',
75
87
  top: triggerTop + triggerHeight,
76
- left: triggerLeft - (popoverWidth - triggerWidth) / 2
88
+ left: triggerLeft - (popoverWidth - triggerWidth) / 2,
77
89
  },
78
90
  left: {
79
91
  name: 'left',
80
92
  top: triggerTop - (popoverHeight - triggerHeight) / 2,
81
- left: triggerLeft - popoverWidth
82
- }
93
+ left: triggerLeft - popoverWidth,
94
+ },
83
95
  }
84
96
 
85
97
  const position = this.orderedPositions
86
98
  .slice(positionIndex)
87
99
  .concat(this.orderedPositions.slice(0, positionIndex))
88
- .map(pos => positions[pos])
89
- .find(pos => {
100
+ .map((pos) => positions[pos])
101
+ .find((pos) => {
90
102
  this.popover.style.top = `${pos.top}px`
91
103
  this.popover.style.left = `${pos.left}px`
92
104
  return Dom.inViewport(this.popover)
93
105
  })
94
106
 
95
- this.orderedPositions.forEach(pos => {
107
+ this.orderedPositions.forEach((pos) => {
96
108
  this.popover.classList.remove(`${this.className}--${pos}`)
97
109
  })
98
110