@brandocms/jupiter 3.50.1 → 3.51.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.
package/README.md CHANGED
@@ -321,6 +321,12 @@ togglers.forEach(toggleEl => {
321
321
  This affects the fixed header amongst other things.
322
322
  - `scrollDuration` - `number` - `0.8` - how long the scroll lasts
323
323
 
324
+ By default anchor links get added to the browser history. You can skip this by setting
325
+ `data-skip-history` on the link **target**:
326
+
327
+ ```html
328
+ <a id="content" name="content" data-skip-history></a>
329
+ ```
324
330
 
325
331
 
326
332
  ## Moonwalk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandocms/jupiter",
3
- "version": "3.50.1",
3
+ "version": "3.51.1",
4
4
  "description": "Frontend helpers.",
5
5
  "author": "Univers/Twined",
6
6
  "license": "UNLICENSED",
@@ -20,7 +20,6 @@
20
20
  "homepage": "https://github.com/brandocms/jupiter#readme",
21
21
  "sideEffects": false,
22
22
  "scripts": {
23
- "lint": "eslint src/*.js",
24
23
  "test": "jest __tests__ --passWithNoTests",
25
24
  "coverage": "jest __tests__ --coverage"
26
25
  },
@@ -33,19 +32,15 @@
33
32
  "dependencies": {
34
33
  "@egjs/hammerjs": "^2.0.17",
35
34
  "body-scroll-lock": "^4.0.0-beta.0",
36
- "gsap": "3.12.2",
35
+ "gsap": "3.12.5",
37
36
  "lodash.defaultsdeep": "^4.6.1"
38
37
  },
39
38
  "devDependencies": {
40
- "@babel/core": "^7.19.6",
41
- "@babel/preset-env": "^7.19.4",
42
- "babel-jest": "^29.2.1",
43
- "eslint": "8.25.0",
44
- "eslint-config-airbnb-base": "^15.0.0",
45
- "eslint-plugin-import": "^2.26.0",
46
- "eslint-plugin-node": "^11.1.0",
47
- "eslint-plugin-promise": "^6.1.1",
48
- "jest": "^29.2.1",
49
- "jest-environment-jsdom": "^29.2.1"
50
- }
39
+ "@babel/core": "^7.25.2",
40
+ "@babel/preset-env": "^7.25.3",
41
+ "babel-jest": "^29.7.0",
42
+ "jest": "^29.7.0",
43
+ "jest-environment-jsdom": "^29.7.0"
44
+ },
45
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
51
46
  }
@@ -8,7 +8,8 @@ const DEFAULT_OPTIONS = {
8
8
  oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1)
9
9
 
10
10
  const timeline = gsap.timeline()
11
- c.setCookie('cookielaw_accepted', 1, oneYearFromNow, '/')
11
+ c.setCookie('COOKIES_CONSENT_STATUS', 1, oneYearFromNow, '/')
12
+ c.opts.setCookies(c)
12
13
 
13
14
  timeline
14
15
  .to(c.cc, { duration: 0.35, y: '120%', ease: 'power3.in' }, '0')
@@ -16,8 +17,36 @@ const DEFAULT_OPTIONS = {
16
17
  .set(c.cc, { display: 'none' })
17
18
  },
18
19
 
20
+ onRefuse: c => {
21
+ const oneYearFromNow = new Date()
22
+ oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1)
23
+
24
+ const timeline = gsap.timeline()
25
+ c.setCookie('COOKIES_CONSENT_STATUS', 0, oneYearFromNow, '/')
26
+
27
+ timeline
28
+ .to(c.cc, { duration: 0.35, y: '120%', ease: 'power3.in' }, '0')
29
+ .to(c.inner, { duration: 0.3, opacity: 0, ease: 'power3.in' }, '0')
30
+ .set(c.cc, { display: 'none' })
31
+ },
32
+
33
+ alreadyConsented: c => {
34
+ // user has already consented to cookies. Can be used to update/load gtm etc.
35
+ },
36
+
37
+ alreadyRefused: c => {
38
+ // user has already refused cookies.
39
+ },
40
+
41
+ setCookies: c => {},
42
+
19
43
  showCC: c => {
20
- if (c.hasCookie('cookielaw_accepted')) {
44
+ if (c.hasCookie('COOKIES_CONSENT_STATUS')) {
45
+ if (c.getCookie('COOKIES_CONSENT_STATUS') === '1') {
46
+ c.opts.alreadyConsented(c)
47
+ } else {
48
+ c.opts.alreadyRefused(c)
49
+ }
21
50
  return
22
51
  }
23
52
 
@@ -78,6 +107,7 @@ export default class Cookies {
78
107
  this.text = document.querySelector('.cookie-law-text')
79
108
  this.btns = document.querySelector('.cookie-law-buttons')
80
109
  this.btn = document.querySelector('.dismiss-cookielaw')
110
+ this.btnRefuse = document.querySelector('.refuse-cookielaw')
81
111
 
82
112
  if (!this.btn) {
83
113
  return
@@ -90,6 +120,11 @@ export default class Cookies {
90
120
  this.btn.addEventListener('click', () => {
91
121
  this.opts.onAccept(this)
92
122
  })
123
+ if (this.btnRefuse) {
124
+ this.btnRefuse.addEventListener('click', () => {
125
+ this.opts.onRefuse(this)
126
+ })
127
+ }
93
128
  }
94
129
 
95
130
  getCookie(sKey) {
@@ -48,9 +48,9 @@ const DEFAULT_OPTIONS = {
48
48
  *
49
49
  * Example:
50
50
  *
51
- * const mw = new Moonwalk(dataloader.app, configureMoonwalk(dataloader.app), dataloader.$el)
52
- * new Lazyload(dataloader.app, { useNativeLazyloadIfAvailable: false }, dataloader.$el)
53
- * new EqualHeightImages(dataloader.app, {}, dataloader.$el)
51
+ * const mw = new Moonwalk(dataloader.app, configureMoonwalk(dataloader.app), dataloader.$canvasEl)
52
+ * new Lazyload(dataloader.app, { useNativeLazyloadIfAvailable: false }, dataloader.$canvasEl)
53
+ * new EqualHeightImages(dataloader.app, {}, dataloader.$canvasEl)
54
54
  * mw.ready()
55
55
  */
56
56
  }
@@ -70,6 +70,19 @@ export default class Dataloader {
70
70
  this.initialize()
71
71
  }
72
72
 
73
+ static replaceInnerHTML(el, url) {
74
+ return new Promise(resolve => {
75
+ fetch(url)
76
+ .then(res => {
77
+ return res.text()
78
+ })
79
+ .then(html => {
80
+ el.innerHTML = html
81
+ return resolve(el)
82
+ })
83
+ })
84
+ }
85
+
73
86
  debounce(func, delay = 650) {
74
87
  let timerId
75
88
  return (...args) => {
@@ -80,6 +93,10 @@ export default class Dataloader {
80
93
  }
81
94
  }
82
95
 
96
+ updateBaseURL(url) {
97
+ this.baseURL = url
98
+ }
99
+
83
100
  initialize() {
84
101
  this.baseURL = this.$el.dataset.loader
85
102
  this.$paramEls = Dom.all(this.$el, '[data-loader-param]')
@@ -122,14 +139,56 @@ export default class Dataloader {
122
139
  }
123
140
 
124
141
  onParam(e) {
125
- e.preventDefault()
126
142
  this.loading()
127
143
  // reset page when switching param!
128
144
  this.opts.page = 0
129
- this.$paramEls.forEach($paramEl => $paramEl.removeAttribute('data-loader-param-selected'))
130
- e.currentTarget.setAttribute('data-loader-param-selected', '')
131
- const key = e.currentTarget.dataset.loaderParamKey || 'defaultParam'
132
- this.opts.loaderParam[key] = e.currentTarget.dataset.loaderParam
145
+
146
+ // param can have multiple values
147
+ const multiVals = e.currentTarget.hasAttribute('data-loader-param-multi')
148
+
149
+ // special case if it's a checkbox!
150
+ if (e.currentTarget.getAttribute('type') === 'checkbox') {
151
+ const key = e.currentTarget.dataset.loaderParamKey || 'defaultParam'
152
+ this.opts.loaderParam[key] = e.currentTarget.checked
153
+ } else {
154
+ e.preventDefault()
155
+ if (e.currentTarget.hasAttribute('data-loader-param-selected')) {
156
+ // if already selected, clear it
157
+ const key = e.currentTarget.dataset.loaderParamKey || 'defaultParam'
158
+ if (multiVals) {
159
+ this.opts.loaderParam[key] = this.opts.loaderParam[key].filter(val => {
160
+ return val !== e.currentTarget.dataset.loaderParam
161
+ })
162
+ } else {
163
+ delete this.opts.loaderParam[key]
164
+ }
165
+ e.currentTarget.removeAttribute('data-loader-param-selected')
166
+ console.log(this.opts.loaderParam[key])
167
+ } else {
168
+ if (multiVals) {
169
+ const key = e.currentTarget.dataset.loaderParamKey || 'defaultParam'
170
+ if (!this.opts.loaderParam.hasOwnProperty(key)) {
171
+ this.opts.loaderParam[key] = []
172
+ }
173
+ this.opts.loaderParam[key].push(e.currentTarget.dataset.loaderParam)
174
+ e.currentTarget.setAttribute('data-loader-param-selected', '')
175
+ } else {
176
+ const paramKey = e.currentTarget.dataset.loaderParamKey
177
+ this.$paramEls.forEach($paramEl => {
178
+ if (paramKey) {
179
+ if ($paramEl.dataset.loaderParamKey === paramKey) {
180
+ $paramEl.removeAttribute('data-loader-param-selected')
181
+ }
182
+ } else {
183
+ $paramEl.removeAttribute('data-loader-param-selected')
184
+ }
185
+ })
186
+ e.currentTarget.setAttribute('data-loader-param-selected', '')
187
+ const key = e.currentTarget.dataset.loaderParamKey || 'defaultParam'
188
+ this.opts.loaderParam[key] = e.currentTarget.dataset.loaderParam
189
+ }
190
+ }
191
+ }
133
192
 
134
193
  this.fetch()
135
194
  }
@@ -53,6 +53,7 @@ export default class Dropdown {
53
53
  }
54
54
  this.elements.menuItems = Dom.all(this.elements.menu, this.opts.selectors.menuItems)
55
55
  this.initialize()
56
+ this.checkForInitialOpen()
56
57
  }
57
58
 
58
59
  initialize() {
@@ -62,9 +63,10 @@ export default class Dropdown {
62
63
  .from(
63
64
  this.elements.menu,
64
65
  {
65
- duration: 0.1,
66
- className: `${this.elements.menu.className} zero-height`
66
+ className: `${this.elements.menu.className} zero-height`,
67
+ duration: 0.1
67
68
  },
69
+
68
70
  'open'
69
71
  )
70
72
  .to(
@@ -92,7 +94,9 @@ export default class Dropdown {
92
94
  }
93
95
  })
94
96
  .to(this.elements.menu, { opacity: 1 })
95
- .from(this.elements.menuItems, this.opts.tweens.items, 'open+=.1')
97
+ if (this.elements.menuItems.length) {
98
+ this.timeline.from(this.elements.menuItems, this.opts.tweens.items, 'open+=.1')
99
+ }
96
100
 
97
101
  if (!this.elements.trigger) {
98
102
  return
@@ -139,4 +143,10 @@ export default class Dropdown {
139
143
  this.timeline.reverse()
140
144
  }
141
145
  }
146
+
147
+ checkForInitialOpen() {
148
+ if (this.elements.trigger.hasAttribute('data-dropdown-active')) {
149
+ this.openMenu()
150
+ }
151
+ }
142
152
  }
@@ -22,9 +22,8 @@ export default class EqualHeightImages {
22
22
  }
23
23
  }
24
24
 
25
- initialize() {
26
- const canvases = Dom.all(this.container, '[data-eq-height-images]')
27
- Array.from(canvases).forEach(canvas => {
25
+ run() {
26
+ Array.from(this.canvases).forEach(canvas => {
28
27
  let lastTop = null
29
28
  const actionables = []
30
29
  let elements = []
@@ -71,6 +70,11 @@ export default class EqualHeightImages {
71
70
  })
72
71
  }
73
72
 
73
+ initialize() {
74
+ this.canvases = Dom.all(this.container, '[data-eq-height-images]')
75
+ this.run()
76
+ }
77
+
74
78
  getRenderedSize(contains, cWidth, cHeight, width, height, pos) {
75
79
  const oRatio = width / height
76
80
  const cRatio = cWidth / cHeight
@@ -74,6 +74,31 @@ export default class FeatureTests {
74
74
  * listen for events as well
75
75
  */
76
76
  testTouchMouseEvents() {
77
+ if (window.PointerEvent && 'maxTouchPoints' in navigator) {
78
+ // if Pointer Events are supported, just check maxTouchPoints
79
+ if (navigator.maxTouchPoints > 0) {
80
+ this.results.touch = true
81
+ this.results.mouse = false
82
+ this.testFor('touch', true)
83
+ this.testFor('mouse', false)
84
+ }
85
+ } else {
86
+ // no Pointer Events...
87
+ if (window.matchMedia && window.matchMedia('(any-pointer:coarse)').matches) {
88
+ // check for any-pointer:coarse which mostly means touchscreen
89
+ this.results.touch = true
90
+ this.results.mouse = false
91
+ this.testFor('touch', true)
92
+ this.testFor('mouse', false)
93
+ } else if (window.TouchEvent || 'ontouchstart' in window) {
94
+ // last resort - check for exposed touch events API / event handler
95
+ this.results.touch = true
96
+ this.results.mouse = false
97
+ this.testFor('touch', true)
98
+ this.testFor('mouse', false)
99
+ }
100
+ }
101
+
77
102
  const onTouchStart = () => {
78
103
  if (!this.results.touch) {
79
104
  this.results.touch = true
@@ -98,6 +98,7 @@ const DEFAULT_OPTIONS = {
98
98
  on: Events.APPLICATION_REVEALED,
99
99
  unpinOnForcedScrollStart: true,
100
100
  pinOnForcedScrollEnd: true,
101
+ rafScroll: true,
101
102
 
102
103
  default: {
103
104
  unPinOnResize: true,
@@ -226,7 +227,12 @@ export default class FixedHeader {
226
227
  }
227
228
 
228
229
  this.app.registerCallback(Events.APPLICATION_REVEALED, () => {
229
- window.addEventListener(Events.APPLICATION_SCROLL, this.update.bind(this), {
230
+ let SCROLL_EVENT = Events.APPLICATION_SCROLL
231
+ if (!this.mainOpts.rafScroll) {
232
+ SCROLL_EVENT = 'scroll'
233
+ }
234
+
235
+ window.addEventListener(SCROLL_EVENT, this.redraw.bind(this), {
230
236
  capture: false,
231
237
  passive: true
232
238
  })
@@ -249,9 +255,11 @@ export default class FixedHeader {
249
255
  }
250
256
 
251
257
  preflight() {
252
- this.checkSize(true)
253
- this.checkBg(true)
254
- this.checkTop(true)
258
+ if (!this.opts.enter) {
259
+ this.checkSize(true)
260
+ this.checkBg(true)
261
+ this.checkTop(true)
262
+ }
255
263
 
256
264
  this.app.registerCallback(Events.APPLICATION_REVEALED, () => {
257
265
  setTimeout(() => {
@@ -120,6 +120,9 @@ export default class Links {
120
120
 
121
121
  if (dataTarget) {
122
122
  this.opts.onAnchor(dataTarget, this)
123
+ if (!dataTarget.hasAttribute('data-skip-history')) {
124
+ history.pushState({}, '', href)
125
+ }
123
126
 
124
127
  if (this.app.header && dataTarget.id !== 'top') {
125
128
  setTimeout(() => {
@@ -141,6 +144,9 @@ export default class Links {
141
144
  bindLinks(links) {
142
145
  Array.from(links).forEach(link => {
143
146
  const href = link.getAttribute('href')
147
+ if (!href) {
148
+ return
149
+ }
144
150
  const internalLink = href.indexOf(document.location.hostname) > -1 || href.startsWith('/')
145
151
  if (this.opts.openExternalInWindow && !internalLink) {
146
152
  link.setAttribute('target', '_blank')
@@ -26,8 +26,12 @@ export default class Popover {
26
26
 
27
27
  this.popover.classList.add(this.className)
28
28
 
29
- this.trigger.addEventListener('mouseenter', this.handleMouseEnter.bind(this))
30
- this.trigger.addEventListener('mouseleave', this.handleMouseLeave.bind(this))
29
+ if (!app.featureTests.results.touch) {
30
+ this.trigger.addEventListener('mouseenter', this.handleMouseEnter.bind(this))
31
+ this.trigger.addEventListener('mouseleave', this.handleMouseLeave.bind(this))
32
+ } else {
33
+ this.trigger.addEventListener('touchstart', this.handleTouchStart.bind(this))
34
+ }
31
35
  }
32
36
 
33
37
  handleMouseEnter(e) {
@@ -38,6 +42,10 @@ export default class Popover {
38
42
  this.hide()
39
43
  }
40
44
 
45
+ handleTouchStart(e) {
46
+ this.toggle()
47
+ }
48
+
41
49
  get isVisible() {
42
50
  return document.body.contains(this.popover)
43
51
  }
@@ -1,14 +1,11 @@
1
1
  import { gsap } from 'gsap'
2
2
  import Dom from '../Dom'
3
3
 
4
- const DEFAULT_OPTIONS = {}
5
-
6
4
  export default class Toggler {
7
5
  constructor(app, el) {
8
6
  this.open = false
9
7
  this.app = app
10
8
  this.el = el
11
- // this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
12
9
  this.trigger = Dom.find(this.el, '[data-toggle-trigger]')
13
10
  this.triggerIcon = Dom.find(this.trigger, 'span.icon')
14
11
  this.content = Dom.find(this.el, '[data-toggle-content]')
@@ -19,12 +16,16 @@ export default class Toggler {
19
16
  this.toggleState()
20
17
 
21
18
  if (this.open) {
22
- this.triggerIcon.classList.toggle('active')
23
- gsap.set(this.content, { height: 'auto' })
19
+ if (this.triggerIcon) {
20
+ this.triggerIcon.classList.toggle('active')
21
+ }
22
+ gsap.set(this.content, { height: 'auto', display: 'block' })
24
23
  this.el.classList.toggle('open')
25
24
  gsap.from(this.content, { height: 0, ease: 'power1.inOut' })
26
25
  } else {
27
- this.triggerIcon.classList.toggle('active')
26
+ if (this.triggerIcon) {
27
+ this.triggerIcon.classList.toggle('active')
28
+ }
28
29
  gsap.to(this.content, {
29
30
  duration: 0.25,
30
31
  onComplete: () => {