@brandocms/jupiter 3.49.0 → 3.50.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/package.json +2 -2
- package/src/index.js +2 -0
- package/src/modules/Application/index.js +5 -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/Links/index.js +8 -2
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandocms/jupiter",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.50.1",
|
|
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) {
|
|
@@ -327,14 +329,14 @@ export default class Application {
|
|
|
327
329
|
document.addEventListener('touchmove', this.scrollVoid, false)
|
|
328
330
|
}
|
|
329
331
|
|
|
330
|
-
scrollRelease() {
|
|
332
|
+
scrollRelease(defaultOverflow = 'scroll') {
|
|
331
333
|
if (!this.SCROLL_LOCKED) {
|
|
332
334
|
return
|
|
333
335
|
}
|
|
334
336
|
const ev = new window.CustomEvent(Events.APPLICATION_SCROLL_RELEASED, this)
|
|
335
337
|
window.dispatchEvent(ev)
|
|
336
338
|
this.SCROLL_LOCKED = false
|
|
337
|
-
gsap.set(document.body, {
|
|
339
|
+
gsap.set(document.body, { overflow: defaultOverflow })
|
|
338
340
|
gsap.set(this._scrollPaddedElements, { clearProps: 'paddingRight' })
|
|
339
341
|
document.removeEventListener('touchmove', this.scrollVoid, false)
|
|
340
342
|
}
|
|
@@ -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() {
|
|
@@ -8,6 +8,7 @@ const DEFAULT_OPTIONS = {
|
|
|
8
8
|
triggerEvents: true,
|
|
9
9
|
scrollDuration: 0.8,
|
|
10
10
|
mobileMenuDelay: 800,
|
|
11
|
+
openExternalInWindow: true,
|
|
11
12
|
linkQuery: 'a:not([href^="#"]):not([target="_blank"]):not([data-lightbox]):not(.noanim)',
|
|
12
13
|
anchorQuery: 'a[href^="#"]:not(.noanim)',
|
|
13
14
|
|
|
@@ -139,9 +140,14 @@ export default class Links {
|
|
|
139
140
|
|
|
140
141
|
bindLinks(links) {
|
|
141
142
|
Array.from(links).forEach(link => {
|
|
143
|
+
const href = link.getAttribute('href')
|
|
144
|
+
const internalLink = href.indexOf(document.location.hostname) > -1 || href.startsWith('/')
|
|
145
|
+
if (this.opts.openExternalInWindow && !internalLink) {
|
|
146
|
+
link.setAttribute('target', '_blank')
|
|
147
|
+
}
|
|
148
|
+
|
|
142
149
|
link.addEventListener('click', e => {
|
|
143
150
|
const loadingContainer = document.querySelector('.loading-container')
|
|
144
|
-
const href = link.getAttribute('href')
|
|
145
151
|
|
|
146
152
|
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
|
147
153
|
return
|
|
@@ -151,7 +157,7 @@ export default class Links {
|
|
|
151
157
|
loadingContainer.style.display = 'none'
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
if (
|
|
160
|
+
if (internalLink) {
|
|
155
161
|
e.preventDefault()
|
|
156
162
|
this.opts.onTransition(href, this.app)
|
|
157
163
|
}
|
|
@@ -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
|
+
}
|