@brandocms/jupiter 3.48.3 → 3.50.0
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/package.json +2 -2
- package/src/index.js +2 -0
- package/src/modules/Application/index.js +10 -3
- package/src/modules/Breakpoints/index.js +8 -3
- package/src/modules/Dataloader/index.js +24 -5
- package/src/modules/Dom/index.js +8 -4
- package/src/modules/Dropdown/index.js +53 -20
- package/src/modules/EqualHeightImages/index.js +10 -1
- package/src/modules/Lazyload/index.js +25 -5
- package/src/modules/Marquee/index.js +1 -2
- package/src/modules/Moonwalk/index.js +30 -5
- package/src/modules/Popover/index.js +111 -0
- package/src/modules/StickyHeader/index.js +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandocms/jupiter",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.50.0",
|
|
4
4
|
"description": "Frontend helpers.",
|
|
5
5
|
"author": "Univers/Twined",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@egjs/hammerjs": "^2.0.17",
|
|
35
35
|
"body-scroll-lock": "^4.0.0-beta.0",
|
|
36
|
-
"gsap": "3.
|
|
36
|
+
"gsap": "3.12.2",
|
|
37
37
|
"lodash.defaultsdeep": "^4.6.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
package/src/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import Links from './modules/Links'
|
|
|
24
24
|
import Marquee from './modules/Marquee'
|
|
25
25
|
import MobileMenu from './modules/MobileMenu'
|
|
26
26
|
import Moonwalk from './modules/Moonwalk'
|
|
27
|
+
import Popover from './modules/Popover'
|
|
27
28
|
import Popup from './modules/Popup'
|
|
28
29
|
import ScrollSpy from './modules/ScrollSpy'
|
|
29
30
|
import StackedBoxes from './modules/StackedBoxes'
|
|
@@ -60,6 +61,7 @@ export {
|
|
|
60
61
|
Marquee,
|
|
61
62
|
MobileMenu,
|
|
62
63
|
Moonwalk,
|
|
64
|
+
Popover,
|
|
63
65
|
Popup,
|
|
64
66
|
ScrollSpy,
|
|
65
67
|
StackedBoxes,
|
|
@@ -11,7 +11,9 @@ import Fontloader from '../Fontloader'
|
|
|
11
11
|
import Dom from '../Dom'
|
|
12
12
|
|
|
13
13
|
gsap.registerPlugin(ScrollToPlugin)
|
|
14
|
-
gsap.defaults({
|
|
14
|
+
gsap.defaults({
|
|
15
|
+
ease: 'sine.out'
|
|
16
|
+
})
|
|
15
17
|
|
|
16
18
|
window.onpageshow = event => {
|
|
17
19
|
if (event.persisted) {
|
|
@@ -107,6 +109,10 @@ export default class Application {
|
|
|
107
109
|
left: 0
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
this.state = {
|
|
113
|
+
revealed: false
|
|
114
|
+
}
|
|
115
|
+
|
|
110
116
|
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
111
117
|
this.focusableSelectors = this.opts.focusableSelectors
|
|
112
118
|
|
|
@@ -323,14 +329,14 @@ export default class Application {
|
|
|
323
329
|
document.addEventListener('touchmove', this.scrollVoid, false)
|
|
324
330
|
}
|
|
325
331
|
|
|
326
|
-
scrollRelease() {
|
|
332
|
+
scrollRelease(defaultOverflow = 'scroll') {
|
|
327
333
|
if (!this.SCROLL_LOCKED) {
|
|
328
334
|
return
|
|
329
335
|
}
|
|
330
336
|
const ev = new window.CustomEvent(Events.APPLICATION_SCROLL_RELEASED, this)
|
|
331
337
|
window.dispatchEvent(ev)
|
|
332
338
|
this.SCROLL_LOCKED = false
|
|
333
|
-
gsap.set(document.body, {
|
|
339
|
+
gsap.set(document.body, { overflow: defaultOverflow })
|
|
334
340
|
gsap.set(this._scrollPaddedElements, { clearProps: 'paddingRight' })
|
|
335
341
|
document.removeEventListener('touchmove', this.scrollVoid, false)
|
|
336
342
|
}
|
|
@@ -487,6 +493,7 @@ export default class Application {
|
|
|
487
493
|
|
|
488
494
|
_emitRevealedEvent() {
|
|
489
495
|
if (!document.body.hasAttribute('data-app-revealed')) {
|
|
496
|
+
this.state.revealed = true
|
|
490
497
|
document.body.dataset.appRevealed = true
|
|
491
498
|
window.dispatchEvent(this.revealedEvent)
|
|
492
499
|
this.executeCallbacks(Events.APPLICATION_REVEALED)
|
|
@@ -21,10 +21,15 @@ export default class Breakpoints {
|
|
|
21
21
|
this.app = app
|
|
22
22
|
this.mediaQueries = {}
|
|
23
23
|
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
24
|
-
window.addEventListener(Events.APPLICATION_PRELUDIUM,
|
|
24
|
+
window.addEventListener(Events.APPLICATION_PRELUDIUM, () => {
|
|
25
|
+
this.initialize(false)
|
|
26
|
+
})
|
|
27
|
+
window.addEventListener(Events.APPLICATION_REVEALED, () => {
|
|
28
|
+
this.initialize(true)
|
|
29
|
+
})
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
initialize() {
|
|
32
|
+
initialize(reveal = false) {
|
|
28
33
|
this.opts.breakpoints.forEach(size => {
|
|
29
34
|
this.mediaQueries[size] = this._getVal(`--breakpoint-${size}`)
|
|
30
35
|
})
|
|
@@ -52,7 +57,7 @@ export default class Breakpoints {
|
|
|
52
57
|
}
|
|
53
58
|
})
|
|
54
59
|
|
|
55
|
-
if (this.opts.runListenerOnInit) {
|
|
60
|
+
if (reveal && this.opts.runListenerOnInit) {
|
|
56
61
|
const { key, mq } = this.getCurrentBreakpoint()
|
|
57
62
|
if (Object.prototype.hasOwnProperty.call(this.opts.listeners, key)) {
|
|
58
63
|
this.opts.listeners[key](mq)
|
|
@@ -26,11 +26,21 @@ import _defaultsDeep from 'lodash.defaultsdeep'
|
|
|
26
26
|
* </div>
|
|
27
27
|
* </div>
|
|
28
28
|
*
|
|
29
|
+
* You can set a custom key for each param:
|
|
30
|
+
*
|
|
31
|
+
* <a class="noanim" href="{{ category.url }}" data-loader-param-key="category" data-loader-param="all" data-loader-param-selected>All</a>
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
* You can also set a target for the canvas if the category selector and canvas are in different modules:
|
|
35
|
+
*
|
|
36
|
+
* <div data-loader="/api/posts" data-loader-id="news" data-loader-canvas-target="#news-canvas">
|
|
37
|
+
*
|
|
38
|
+
* <div data-loader-canvas id="#news-canvas">
|
|
29
39
|
*/
|
|
30
40
|
|
|
31
41
|
const DEFAULT_OPTIONS = {
|
|
32
42
|
page: 0,
|
|
33
|
-
loaderParam:
|
|
43
|
+
loaderParam: {},
|
|
34
44
|
filter: '',
|
|
35
45
|
onFetch: dataloader => {
|
|
36
46
|
/**
|
|
@@ -51,7 +61,11 @@ export default class Dataloader {
|
|
|
51
61
|
this.status = 'available'
|
|
52
62
|
this.app = app
|
|
53
63
|
this.$el = $el
|
|
54
|
-
|
|
64
|
+
if ($el.hasAttribute('data-loader-canvas-target')) {
|
|
65
|
+
this.$canvasEl = Dom.find($el.getAttribute('data-loader-canvas-target'))
|
|
66
|
+
} else {
|
|
67
|
+
this.$canvasEl = Dom.find($el, '[data-loader-canvas]')
|
|
68
|
+
}
|
|
55
69
|
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
56
70
|
this.initialize()
|
|
57
71
|
}
|
|
@@ -114,15 +128,20 @@ export default class Dataloader {
|
|
|
114
128
|
this.opts.page = 0
|
|
115
129
|
this.$paramEls.forEach($paramEl => $paramEl.removeAttribute('data-loader-param-selected'))
|
|
116
130
|
e.currentTarget.setAttribute('data-loader-param-selected', '')
|
|
117
|
-
|
|
131
|
+
const key = e.currentTarget.dataset.loaderParamKey || 'defaultParam'
|
|
132
|
+
this.opts.loaderParam[key] = e.currentTarget.dataset.loaderParam
|
|
133
|
+
|
|
118
134
|
this.fetch()
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
fetch(addEntries = false) {
|
|
122
|
-
const
|
|
138
|
+
const { defaultParam, ...otherParams } = this.opts.loaderParam
|
|
123
139
|
const filter = this.opts.filter
|
|
124
140
|
|
|
125
|
-
fetch(
|
|
141
|
+
fetch(
|
|
142
|
+
`${this.baseURL}/${defaultParam ? defaultParam + '/' : ''}${this.opts.page}?` +
|
|
143
|
+
new URLSearchParams({ filter, ...otherParams })
|
|
144
|
+
)
|
|
126
145
|
.then(res => {
|
|
127
146
|
this.status = res.headers.get('jpt-dataloader') || 'available'
|
|
128
147
|
this.updateButton()
|
package/src/modules/Dom/index.js
CHANGED
|
@@ -115,12 +115,16 @@ class DOM {
|
|
|
115
115
|
return width
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
getCSSVar(key) {
|
|
119
|
-
return getComputedStyle(
|
|
118
|
+
getCSSVar(key, element = document.documentElement) {
|
|
119
|
+
return getComputedStyle(element).getPropertyValue(key).trim()
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
setCSSVar(key, val) {
|
|
123
|
-
|
|
122
|
+
setCSSVar(key, val, element = document.documentElement) {
|
|
123
|
+
element.style.setProperty(`--${key}`, val)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
removeCSSVar(key, element = document.documentElement) {
|
|
127
|
+
element.style.removeProperty(`--${key}`)
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
offset(el) {
|
|
@@ -10,6 +10,14 @@ import Dom from '../Dom'
|
|
|
10
10
|
* <li>Item</li>
|
|
11
11
|
* </ul>
|
|
12
12
|
* </ul>
|
|
13
|
+
*
|
|
14
|
+
* If you need to trigger a dropdown menu outside of the data-dropdown element, you can target it
|
|
15
|
+
* with
|
|
16
|
+
*
|
|
17
|
+
* <li data-dropdown-trigger data-dropdown-target="#mydropdown">Trigger</li>
|
|
18
|
+
*
|
|
19
|
+
* This is useful if you run into clipping bugs/problems. Move your dropdown
|
|
20
|
+
* menu outside of the clipping container.
|
|
13
21
|
*/
|
|
14
22
|
|
|
15
23
|
const DEFAULT_OPTIONS = {
|
|
@@ -37,29 +45,54 @@ export default class Dropdown {
|
|
|
37
45
|
this.element = opts.el
|
|
38
46
|
this.timeline = gsap.timeline({ paused: true, reversed: true })
|
|
39
47
|
this.elements.trigger = Dom.find(this.element, this.opts.selectors.trigger)
|
|
40
|
-
this.elements.
|
|
41
|
-
|
|
48
|
+
if (this.elements.trigger.hasAttribute('data-dropdown-target')) {
|
|
49
|
+
const dropdownTarget = this.elements.trigger.getAttribute('data-dropdown-target')
|
|
50
|
+
this.elements.menu = Dom.find(dropdownTarget)
|
|
51
|
+
} else {
|
|
52
|
+
this.elements.menu = Dom.find(this.element, this.opts.selectors.menu)
|
|
53
|
+
}
|
|
54
|
+
this.elements.menuItems = Dom.all(this.elements.menu, this.opts.selectors.menuItems)
|
|
42
55
|
this.initialize()
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
initialize() {
|
|
46
|
-
this.timeline
|
|
47
|
-
this.elements.menu,
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
this.timeline
|
|
60
|
+
.set(this.elements.menu, { display: 'none', clearProps: 'height' })
|
|
61
|
+
.set(this.elements.menu, { display: 'flex', opacity: 0 })
|
|
62
|
+
.from(
|
|
63
|
+
this.elements.menu,
|
|
64
|
+
{
|
|
65
|
+
duration: 0.1,
|
|
66
|
+
className: `${this.elements.menu.className} zero-height`
|
|
67
|
+
},
|
|
68
|
+
'open'
|
|
69
|
+
)
|
|
70
|
+
.to(
|
|
71
|
+
this.elements.menu,
|
|
72
|
+
{
|
|
73
|
+
height: 'auto',
|
|
74
|
+
duration: 0.1
|
|
75
|
+
},
|
|
76
|
+
'open'
|
|
77
|
+
)
|
|
78
|
+
.call(() => {
|
|
79
|
+
// check if we have space
|
|
80
|
+
const subMenuBound = this.elements.menu.getBoundingClientRect()
|
|
81
|
+
const windowHeight = window.innerHeight
|
|
82
|
+
|
|
83
|
+
const subMenuY = subMenuBound.y
|
|
84
|
+
const subMenuHeight = subMenuBound.height
|
|
85
|
+
|
|
86
|
+
Dom.setCSSVar('dropdown-menu-height', `${subMenuHeight}px`, this.elements.menu)
|
|
61
87
|
|
|
62
|
-
|
|
88
|
+
if (subMenuHeight + subMenuY > windowHeight) {
|
|
89
|
+
this.elements.menu.setAttribute('data-dropdown-placement', 'top')
|
|
90
|
+
} else {
|
|
91
|
+
this.elements.menu.setAttribute('data-dropdown-placement', 'bottom')
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
.to(this.elements.menu, { opacity: 1 })
|
|
95
|
+
.from(this.elements.menuItems, this.opts.tweens.items, 'open+=.1')
|
|
63
96
|
|
|
64
97
|
if (!this.elements.trigger) {
|
|
65
98
|
return
|
|
@@ -72,10 +105,8 @@ export default class Dropdown {
|
|
|
72
105
|
event.stopPropagation()
|
|
73
106
|
|
|
74
107
|
if (this.open) {
|
|
75
|
-
delete this.elements.trigger.dataset.dropdownActive
|
|
76
108
|
this.closeMenu()
|
|
77
109
|
} else {
|
|
78
|
-
this.elements.trigger.dataset.dropdownActive = ''
|
|
79
110
|
this.openMenu()
|
|
80
111
|
}
|
|
81
112
|
}
|
|
@@ -88,6 +119,7 @@ export default class Dropdown {
|
|
|
88
119
|
this.app.currentMenu = this
|
|
89
120
|
}
|
|
90
121
|
this.open = true
|
|
122
|
+
this.elements.trigger.dataset.dropdownActive = ''
|
|
91
123
|
|
|
92
124
|
if (this.timeline.reversed()) {
|
|
93
125
|
this.timeline.play()
|
|
@@ -99,6 +131,7 @@ export default class Dropdown {
|
|
|
99
131
|
closeMenu() {
|
|
100
132
|
this.app.currentMenu = null
|
|
101
133
|
this.open = false
|
|
134
|
+
delete this.elements.trigger.dataset.dropdownActive
|
|
102
135
|
|
|
103
136
|
if (this.timeline.reversed()) {
|
|
104
137
|
this.timeline.play()
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { gsap } from 'gsap'
|
|
2
2
|
import Dom from '../Dom'
|
|
3
|
+
import * as Events from '../../events'
|
|
3
4
|
import imagesAreLoaded from '../../utils/imagesAreLoaded'
|
|
4
5
|
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
5
6
|
|
|
6
|
-
const DEFAULT_OPTIONS = {
|
|
7
|
+
const DEFAULT_OPTIONS = {
|
|
8
|
+
listenForResize: true
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
export default class EqualHeightImages {
|
|
9
12
|
constructor(app, opts = {}, container = document.body) {
|
|
@@ -11,6 +14,12 @@ export default class EqualHeightImages {
|
|
|
11
14
|
this.container = container
|
|
12
15
|
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
13
16
|
this.initialize()
|
|
17
|
+
|
|
18
|
+
if (opts.listenForResize) {
|
|
19
|
+
window.addEventListener(Events.APPLICATION_RESIZE, () => {
|
|
20
|
+
this.initialize()
|
|
21
|
+
})
|
|
22
|
+
}
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
initialize() {
|
|
@@ -27,9 +27,13 @@ export default class Lazyload {
|
|
|
27
27
|
this.initialize()
|
|
28
28
|
|
|
29
29
|
if (this.opts.registerCallback) {
|
|
30
|
-
this.app.
|
|
30
|
+
if (this.app.state.revealed) {
|
|
31
31
|
this.watch()
|
|
32
|
-
}
|
|
32
|
+
} else {
|
|
33
|
+
this.app.registerCallback(Events.APPLICATION_REVEALED, () => {
|
|
34
|
+
this.watch()
|
|
35
|
+
})
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
|
|
@@ -73,7 +77,6 @@ export default class Lazyload {
|
|
|
73
77
|
this.opts.revealIntersectionObserverConfig
|
|
74
78
|
)
|
|
75
79
|
|
|
76
|
-
this.lazyPictures = document.querySelectorAll('[data-ll-srcset]')
|
|
77
80
|
this.initObserver(this.loadObserver)
|
|
78
81
|
|
|
79
82
|
// Deprecate data-ll-image sometime
|
|
@@ -94,6 +97,7 @@ export default class Lazyload {
|
|
|
94
97
|
initObserver(observer, setAttrs = true) {
|
|
95
98
|
this.lazyPictures.forEach((picture, idx) => {
|
|
96
99
|
if (setAttrs) {
|
|
100
|
+
picture.setAttribute('data-ll-srcset-initialized', '')
|
|
97
101
|
picture.querySelectorAll('img:not([data-ll-loaded])').forEach(img => {
|
|
98
102
|
img.setAttribute('data-ll-blurred', '')
|
|
99
103
|
img.setAttribute('data-ll-idx', idx)
|
|
@@ -181,11 +185,27 @@ export default class Lazyload {
|
|
|
181
185
|
|
|
182
186
|
// we reveal the picture when it enters the viewport
|
|
183
187
|
handleRevealEntries(elements) {
|
|
188
|
+
const srcsetReadyObserver = new MutationObserver(mutations => {
|
|
189
|
+
mutations.forEach(record => {
|
|
190
|
+
if (record.type === 'attributes' && record.attributeName === 'data-ll-srcset-ready') {
|
|
191
|
+
this.revealPicture(record.target)
|
|
192
|
+
this.revealObserver.unobserve(record.target)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
184
197
|
elements.forEach(item => {
|
|
185
198
|
if (item.isIntersecting || item.intersectionRatio > 0) {
|
|
186
199
|
const picture = item.target
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
const ready = item.target.hasAttribute('data-ll-srcset-ready')
|
|
201
|
+
if (!ready) {
|
|
202
|
+
// element is not loaded, observe the picture and wait for
|
|
203
|
+
// `data-ll-srcset-ready` before revealing
|
|
204
|
+
srcsetReadyObserver.observe(picture, { attributes: true })
|
|
205
|
+
} else {
|
|
206
|
+
this.revealPicture(picture)
|
|
207
|
+
this.revealObserver.unobserve(item.target)
|
|
208
|
+
}
|
|
189
209
|
}
|
|
190
210
|
})
|
|
191
211
|
}
|
|
@@ -63,8 +63,7 @@ export default class Marquee {
|
|
|
63
63
|
const holderWidth = this.elements.$holder.offsetWidth
|
|
64
64
|
const $allHolders = Dom.all(this.elements.$el, '[data-marquee-holder]')
|
|
65
65
|
const marqueeWidth = holderWidth * $allHolders.length
|
|
66
|
-
|
|
67
|
-
this.duration = holderWidth / this.opts.speed
|
|
66
|
+
this.duration = (holderWidth + marqueeWidth) / this.opts.speed
|
|
68
67
|
|
|
69
68
|
gsap.set(this.elements.$marquee, { width: marqueeWidth })
|
|
70
69
|
this.initializeTween()
|
|
@@ -224,10 +224,17 @@ export default class Moonwalk {
|
|
|
224
224
|
return Array.from(runs).map(run => {
|
|
225
225
|
const foundRun = this.opts.runs[run.getAttribute('data-moonwalk-run')]
|
|
226
226
|
if (foundRun) {
|
|
227
|
+
if (foundRun.initialize) {
|
|
228
|
+
foundRun.initialize(run)
|
|
229
|
+
}
|
|
227
230
|
return {
|
|
228
231
|
el: run,
|
|
229
232
|
threshold: foundRun.threshold || 0,
|
|
230
|
-
|
|
233
|
+
initialize: foundRun.initialize,
|
|
234
|
+
callback: foundRun.callback,
|
|
235
|
+
onExit: foundRun.onExit,
|
|
236
|
+
repeated: foundRun.repeated,
|
|
237
|
+
rootMargin: foundRun.rootMargin
|
|
231
238
|
}
|
|
232
239
|
}
|
|
233
240
|
|
|
@@ -531,7 +538,11 @@ export default class Moonwalk {
|
|
|
531
538
|
if (idx === this.sections.length - 1) {
|
|
532
539
|
rootMargin = '0px'
|
|
533
540
|
} else {
|
|
534
|
-
|
|
541
|
+
if (run.rootMargin) {
|
|
542
|
+
rootMargin = run.rootMargin
|
|
543
|
+
} else {
|
|
544
|
+
rootMargin = opts.rootMargin
|
|
545
|
+
}
|
|
535
546
|
}
|
|
536
547
|
|
|
537
548
|
const runObserver = this.runObserver(run, rootMargin)
|
|
@@ -571,9 +582,23 @@ export default class Moonwalk {
|
|
|
571
582
|
(entries, self) => {
|
|
572
583
|
for (let i = 0; i < entries.length; i += 1) {
|
|
573
584
|
const entry = entries[i]
|
|
574
|
-
if (entry.isIntersecting) {
|
|
575
|
-
|
|
576
|
-
|
|
585
|
+
if (entry.isIntersecting && run.callback) {
|
|
586
|
+
const runRepeated = entry.target.hasAttribute('data-moonwalk-run-triggered')
|
|
587
|
+
run.callback(entry.target, runRepeated)
|
|
588
|
+
entry.target.setAttribute('data-moonwalk-run-triggered', '')
|
|
589
|
+
if (!run.onExit && !run.repeated) {
|
|
590
|
+
self.unobserve(entry.target)
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
if (run.onExit && entry.target.hasAttribute('data-moonwalk-run-triggered')) {
|
|
594
|
+
const runExited = entry.target.hasAttribute('data-moonwalk-run-exit-triggered')
|
|
595
|
+
// entry.target.removeAttribute('data-moonwalk-run-triggered')
|
|
596
|
+
entry.target.setAttribute('data-moonwalk-run-exit-triggered', '')
|
|
597
|
+
run.onExit(entry.target, runExited)
|
|
598
|
+
if (!run.repeated) {
|
|
599
|
+
self.unobserve(entry.target)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
577
602
|
}
|
|
578
603
|
}
|
|
579
604
|
},
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { gsap } from 'gsap'
|
|
2
|
+
import Dom from '../Dom'
|
|
3
|
+
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_OPTIONS = {}
|
|
6
|
+
|
|
7
|
+
export default class Popover {
|
|
8
|
+
constructor(app, trigger, opts = {}) {
|
|
9
|
+
this.app = app
|
|
10
|
+
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
11
|
+
|
|
12
|
+
this.trigger = trigger
|
|
13
|
+
this.position = this.trigger.getAttribute('data-popover-position') || 'top'
|
|
14
|
+
this.className = 'popover'
|
|
15
|
+
this.orderedPositions = ['top', 'right', 'bottom', 'left']
|
|
16
|
+
|
|
17
|
+
const popoverTemplate = document.querySelector(
|
|
18
|
+
`[data-popover-template=${trigger.dataset.popoverTarget}]`
|
|
19
|
+
)
|
|
20
|
+
this.popover = document.createElement('div')
|
|
21
|
+
this.popover.innerHTML = popoverTemplate.innerHTML
|
|
22
|
+
|
|
23
|
+
Object.assign(this.popover.style, {
|
|
24
|
+
position: 'fixed'
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
this.popover.classList.add(this.className)
|
|
28
|
+
|
|
29
|
+
this.trigger.addEventListener('mouseenter', this.handleMouseEnter.bind(this))
|
|
30
|
+
this.trigger.addEventListener('mouseleave', this.handleMouseLeave.bind(this))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
handleMouseEnter(e) {
|
|
34
|
+
this.show()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
handleMouseLeave(e) {
|
|
38
|
+
this.hide()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get isVisible() {
|
|
42
|
+
return document.body.contains(this.popover)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
show() {
|
|
46
|
+
document.body.appendChild(this.popover)
|
|
47
|
+
|
|
48
|
+
const { top: triggerTop, left: triggerLeft } = this.trigger.getBoundingClientRect()
|
|
49
|
+
const { offsetHeight: triggerHeight, offsetWidth: triggerWidth } = this.trigger
|
|
50
|
+
const { offsetHeight: popoverHeight, offsetWidth: popoverWidth } = this.popover
|
|
51
|
+
|
|
52
|
+
const positionIndex = this.orderedPositions.indexOf(this.position)
|
|
53
|
+
|
|
54
|
+
const positions = {
|
|
55
|
+
top: {
|
|
56
|
+
name: 'top',
|
|
57
|
+
top: triggerTop - popoverHeight,
|
|
58
|
+
left: triggerLeft - (popoverWidth - triggerWidth) / 2
|
|
59
|
+
},
|
|
60
|
+
right: {
|
|
61
|
+
name: 'right',
|
|
62
|
+
top: triggerTop - (popoverHeight - triggerHeight) / 2,
|
|
63
|
+
left: triggerLeft + triggerWidth
|
|
64
|
+
},
|
|
65
|
+
bottom: {
|
|
66
|
+
name: 'bottom',
|
|
67
|
+
top: triggerTop + triggerHeight,
|
|
68
|
+
left: triggerLeft - (popoverWidth - triggerWidth) / 2
|
|
69
|
+
},
|
|
70
|
+
left: {
|
|
71
|
+
name: 'left',
|
|
72
|
+
top: triggerTop - (popoverHeight - triggerHeight) / 2,
|
|
73
|
+
left: triggerLeft - popoverWidth
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const position = this.orderedPositions
|
|
78
|
+
.slice(positionIndex)
|
|
79
|
+
.concat(this.orderedPositions.slice(0, positionIndex))
|
|
80
|
+
.map(pos => positions[pos])
|
|
81
|
+
.find(pos => {
|
|
82
|
+
this.popover.style.top = `${pos.top}px`
|
|
83
|
+
this.popover.style.left = `${pos.left}px`
|
|
84
|
+
return Dom.inViewport(this.popover)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
this.orderedPositions.forEach(pos => {
|
|
88
|
+
this.popover.classList.remove(`${this.className}--${pos}`)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
if (position) {
|
|
92
|
+
this.popover.classList.add(`${this.className}--${position.name}`)
|
|
93
|
+
} else {
|
|
94
|
+
this.popover.style.top = positions.bottom.top
|
|
95
|
+
this.popover.style.left = positions.bottom.left
|
|
96
|
+
this.popover.classList.add(`${this.className}--bottom`)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
hide() {
|
|
101
|
+
this.popover.remove()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
toggle() {
|
|
105
|
+
if (this.isVisible) {
|
|
106
|
+
this.hide()
|
|
107
|
+
} else {
|
|
108
|
+
this.show()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|