|
|
@@ -15,8 +15,19 @@ Options:
|
|
|
|
|
|
import * as $ from 'jquery'
|
|
|
import { getScrollParent } from '../util'
|
|
|
+import { listenViaDelegation } from '../util/dom'
|
|
|
import { default as ListenerMixin, ListenerInterface } from './ListenerMixin'
|
|
|
|
|
|
+export interface PopoverOptions {
|
|
|
+ className?: string
|
|
|
+ content?: HTMLElement[]
|
|
|
+ parentEl: HTMLElement
|
|
|
+ autoHide?: boolean
|
|
|
+ top?: number
|
|
|
+ left?: number
|
|
|
+ right?: number
|
|
|
+ viewportConstrain?: boolean
|
|
|
+}
|
|
|
|
|
|
export default class Popover {
|
|
|
|
|
|
@@ -24,13 +35,13 @@ export default class Popover {
|
|
|
stopListeningTo: ListenerInterface['stopListeningTo']
|
|
|
|
|
|
isHidden: boolean = true
|
|
|
- options: any
|
|
|
- el: JQuery // the container element for the popover. generated by this object
|
|
|
+ options: PopoverOptions
|
|
|
+ el: HTMLElement // the container element for the popover. generated by this object
|
|
|
margin: number = 10 // the space required between the popover and the edges of the scroll container
|
|
|
|
|
|
|
|
|
- constructor(options) {
|
|
|
- this.options = options || {}
|
|
|
+ constructor(options: PopoverOptions) {
|
|
|
+ this.options = options
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -40,7 +51,7 @@ export default class Popover {
|
|
|
if (!this.el) {
|
|
|
this.render()
|
|
|
}
|
|
|
- this.el.show()
|
|
|
+ this.el.style.display = ''
|
|
|
this.position()
|
|
|
this.isHidden = false
|
|
|
this.trigger('show')
|
|
|
@@ -51,7 +62,7 @@ export default class Popover {
|
|
|
// Hides the popover, through CSS, but does not remove it from the DOM
|
|
|
hide() {
|
|
|
if (!this.isHidden) {
|
|
|
- this.el.hide()
|
|
|
+ this.el.style.display = 'none'
|
|
|
this.isHidden = true
|
|
|
this.trigger('hide')
|
|
|
}
|
|
|
@@ -62,19 +73,23 @@ export default class Popover {
|
|
|
render() {
|
|
|
let options = this.options
|
|
|
|
|
|
- this.el = $('<div class="fc-popover"/>')
|
|
|
- .addClass(options.className || '')
|
|
|
- .css({
|
|
|
- // position initially to the top left to avoid creating scrollbars
|
|
|
- top: 0,
|
|
|
- left: 0
|
|
|
- })
|
|
|
- .append(options.content)
|
|
|
- .appendTo(options.parentEl)
|
|
|
+ let el = this.el = document.createElement('div')
|
|
|
+ el.classList.add('fc-popover')
|
|
|
+ if (options.className) {
|
|
|
+ el.classList.add(options.className)
|
|
|
+ }
|
|
|
+ el.style.top = '0'
|
|
|
+ el.style.left = '0'
|
|
|
+
|
|
|
+ options.content.forEach(function(node) {
|
|
|
+ el.appendChild(node)
|
|
|
+ })
|
|
|
+
|
|
|
+ options.parentEl.appendChild(el)
|
|
|
|
|
|
// when a click happens on anything inside with a 'fc-close' className, hide the popover
|
|
|
- this.el.on('click', '.fc-close', () => {
|
|
|
- this.hide()
|
|
|
+ listenViaDelegation(el, 'click', 'fc-close', function(ev) {
|
|
|
+ el.style.display = 'none'
|
|
|
})
|
|
|
|
|
|
if (options.autoHide) {
|
|
|
@@ -108,14 +123,10 @@ export default class Popover {
|
|
|
// Positions the popover optimally, using the top/left/right options
|
|
|
position() {
|
|
|
let options = this.options
|
|
|
- let origin = this.el.offsetParent().offset()
|
|
|
- let width = this.el.outerWidth()
|
|
|
- let height = this.el.outerHeight()
|
|
|
- let windowEl = $(window)
|
|
|
- let viewportEl = getScrollParent(this.el)
|
|
|
- let viewportTop
|
|
|
- let viewportLeft
|
|
|
- let viewportOffset
|
|
|
+ let el = this.el
|
|
|
+ let rect = el.getBoundingClientRect()
|
|
|
+ let viewportEl = getScrollParent($(el))[0]
|
|
|
+ let viewportRect
|
|
|
let top // the "position" (not "offset") values for the popover
|
|
|
let left //
|
|
|
|
|
|
@@ -124,37 +135,41 @@ export default class Popover {
|
|
|
if (options.left !== undefined) {
|
|
|
left = options.left
|
|
|
} else if (options.right !== undefined) {
|
|
|
- left = options.right - width // derive the left value from the right value
|
|
|
+ left = options.right - rect.width // derive the left value from the right value
|
|
|
} else {
|
|
|
left = 0
|
|
|
}
|
|
|
|
|
|
- if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result
|
|
|
- viewportEl = windowEl
|
|
|
- viewportTop = 0 // the window is always at the top left
|
|
|
- viewportLeft = 0 // (and .offset() won't work if called here)
|
|
|
+ // normalize getScrollParent's result
|
|
|
+ if (viewportEl === (document as any)) {
|
|
|
+ viewportEl = (window as any)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (viewportEl === (window as any)) {
|
|
|
+ viewportRect = {
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ width: document.documentElement.clientWidth,
|
|
|
+ height: document.documentElement.clientHeight
|
|
|
+ }
|
|
|
} else {
|
|
|
- viewportOffset = viewportEl.offset()
|
|
|
- viewportTop = viewportOffset.top
|
|
|
- viewportLeft = viewportOffset.left
|
|
|
+ viewportRect = viewportEl.getBoundingClientRect()
|
|
|
}
|
|
|
|
|
|
// if the window is scrolled, it causes the visible area to be further down
|
|
|
- viewportTop += windowEl.scrollTop()
|
|
|
- viewportLeft += windowEl.scrollLeft()
|
|
|
+ viewportRect.top += window.scrollY
|
|
|
+ viewportRect.left += window.scrollX
|
|
|
|
|
|
// constrain to the view port. if constrained by two edges, give precedence to top/left
|
|
|
if (options.viewportConstrain !== false) {
|
|
|
- top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin)
|
|
|
- top = Math.max(top, viewportTop + this.margin)
|
|
|
- left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin)
|
|
|
- left = Math.max(left, viewportLeft + this.margin)
|
|
|
+ top = Math.min(top, viewportRect.top + viewportRect.height - rect.height - this.margin)
|
|
|
+ top = Math.max(top, viewportRect.top + this.margin)
|
|
|
+ left = Math.min(left, viewportRect.left + viewportRect.width - rect.width - this.margin)
|
|
|
+ left = Math.max(left, viewportRect.left + this.margin)
|
|
|
}
|
|
|
|
|
|
- this.el.css({
|
|
|
- top: top - origin.top,
|
|
|
- left: left - origin.left
|
|
|
- })
|
|
|
+ el.style.top = (top - rect.top) + 'px'
|
|
|
+ el.style.left = (left - rect.left) + 'px'
|
|
|
}
|
|
|
|
|
|
|