967 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			967 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* eslint-disable no-console */
 | 
						|
/* eslint-disable no-case-declarations */
 | 
						|
/* eslint-disable no-unused-vars */
 | 
						|
import { Elements } from './utils.js'
 | 
						|
import Poppable from './poppable.js'
 | 
						|
 | 
						|
/** A Popup that shows text labels, images, or html
 | 
						|
 */
 | 
						|
export default class Popup extends Poppable {
 | 
						|
    /**
 | 
						|
     * Creates an instance of Popup.
 | 
						|
     * @param {any} [{
 | 
						|
     *     parent = null,     - The DOM parent element.
 | 
						|
     *     content = null,    - A dict object with type strings (text, img, html) as keys
 | 
						|
     *                       and corresponding values.
 | 
						|
     *     context = window,    - A context object for poppable elements
 | 
						|
     *     fontSize = "1em",  - Describes the font size as CSS value
 | 
						|
     *     fontFamily = "Arial", - Describes the font family as CSS value
 | 
						|
     *     padding = 16,      - {number || string} padding - Describes the padding as CSS value
 | 
						|
     *     notchSize = 10,    - {number || string} notchSize - Describes the size of the notch (callout) as CSS value
 | 
						|
     *     switchPos = false,
 | 
						|
     *     minWidth = null,
 | 
						|
     *     maxWidth = 800,
 | 
						|
     *     backgroundColor = "#EEE",  - The color of the background as CSS value
 | 
						|
     *     normalColor = "#444", - normalColor  - The color of textitems as CSS value
 | 
						|
     *     notchPosition = "bottomLeft",
 | 
						|
     *     zIndex = 0,
 | 
						|
     *     keepWithin = null, - Ensure that popup is visible within the bounds of the given container
 | 
						|
     *     autoClose = true,  - Autoclose the Popup on tap
 | 
						|
     *     closeIcon = null,
 | 
						|
     *     resizeIcon = null,
 | 
						|
     *     closeCommand = null,
 | 
						|
     *     draggable = false
 | 
						|
     *      noStyle = false - When true, prevents the popup from doing any aesthetic manipulations to the DOM leaving the styling completely to the style sheets.
 | 
						|
     * }={}]
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    constructor({
 | 
						|
        parent = null,
 | 
						|
        content = null,
 | 
						|
        context = window,
 | 
						|
        fontSize = '1em',
 | 
						|
        fontFamily = 'Arial',
 | 
						|
        padding = 16,
 | 
						|
        notchSize = 10,
 | 
						|
        switchPos = false,
 | 
						|
        minWidth = null,
 | 
						|
        maxWidth = 800,
 | 
						|
        backgroundColor = '#EEE',
 | 
						|
        normalColor = '#444',
 | 
						|
        notchPosition = 'bottomCenter',
 | 
						|
        zIndex = 0,
 | 
						|
        keepWithin = null,
 | 
						|
        autoClose = true,
 | 
						|
        closeIcon = null,
 | 
						|
        resizeIcon = null,
 | 
						|
        closeCommand = null,
 | 
						|
        draggable = false,
 | 
						|
        posOffset = 0,
 | 
						|
        targetBoundingBox = null,
 | 
						|
        useEventPosWithBoundingBox = false,
 | 
						|
        interactive = false,
 | 
						|
        onResize = null,
 | 
						|
        onMove = null,
 | 
						|
        noStyle = false,
 | 
						|
        hideOnUp = true,
 | 
						|
    } = {}) {
 | 
						|
        super()
 | 
						|
        this.context = context
 | 
						|
        this.noStyle = noStyle
 | 
						|
        this.hideOnUp = hideOnUp
 | 
						|
        this.padding = padding
 | 
						|
        this.notchPosition = notchPosition
 | 
						|
        this.notchSize = notchSize
 | 
						|
        this.switchPos = switchPos
 | 
						|
        this.fontSize = fontSize
 | 
						|
        this.fontFamily = fontFamily
 | 
						|
        this.minWidth = minWidth
 | 
						|
        this.maxWidth = maxWidth
 | 
						|
        this.normalColor = normalColor
 | 
						|
        this.backgroundColor = backgroundColor
 | 
						|
        this.keepWithin = keepWithin
 | 
						|
        this.autoClose = autoClose
 | 
						|
        this.resizeIcon = resizeIcon
 | 
						|
        this.closeIcon = closeIcon
 | 
						|
        this.closeCommand = closeCommand
 | 
						|
        this.zIndex = zIndex
 | 
						|
        this.parent = parent || document.body
 | 
						|
        this.draggable = draggable
 | 
						|
        this.posOffset = posOffset
 | 
						|
        this.targetBoundingBox = targetBoundingBox
 | 
						|
        this.useEventPosWithBoundingBox = useEventPosWithBoundingBox
 | 
						|
        this.currentPos = null
 | 
						|
        this.insertedNode = null
 | 
						|
        this.loaded = false
 | 
						|
        this.interactive = interactive
 | 
						|
        this.onload = null
 | 
						|
        this.onResize = onResize
 | 
						|
        this.onMove = onMove
 | 
						|
        if (content) {
 | 
						|
            this.show(content)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /** Setup popup with a dictionary of content types and contents.
 | 
						|
     * @param {Object} content - A dict object with type strings (text, img, html) as keys
 | 
						|
     *                            and corresponding values.
 | 
						|
     * @return {Popup} this
 | 
						|
     */
 | 
						|
    setup(content) {
 | 
						|
        //console.log("Popup.setup", this.draggable)
 | 
						|
        this.content = content
 | 
						|
        this.items = {}
 | 
						|
        this.element = document.createElement('div')
 | 
						|
        this.element.classList.add('popup')
 | 
						|
        this.setAlpha(this.element, 0)
 | 
						|
        // this.element.style.opacity = 0
 | 
						|
        Elements.addClass(this.element, 'unselectable')
 | 
						|
        this.notch = document.createElement('div')
 | 
						|
        Elements.setStyle(this.notch, this.notchStyle())
 | 
						|
        this.notch.className = 'notch'
 | 
						|
        this.setupDraggable()
 | 
						|
        if (this.closeIcon) {
 | 
						|
            let img = document.createElement('img')
 | 
						|
            img.setAttribute('draggable', false)
 | 
						|
            img.src = this.closeIcon
 | 
						|
            img.style.position = 'absolute'
 | 
						|
            img.style.right = '0px'
 | 
						|
            img.style.top = '0px'
 | 
						|
            img.style.width = '16px'
 | 
						|
            img.style.height = '16px'
 | 
						|
            img.onclick = (e) => {
 | 
						|
                this.close()
 | 
						|
            }
 | 
						|
            this.element.appendChild(img)
 | 
						|
        }
 | 
						|
        if (this.resizeIcon) {
 | 
						|
            let img = document.createElement('img')
 | 
						|
            img.style.position = 'absolute'
 | 
						|
            img.style.right = '0px'
 | 
						|
            img.style.bottom = '0px'
 | 
						|
            img.style.width = '16px'
 | 
						|
            img.style.height = '16px'
 | 
						|
            img.src = this.resizeIcon
 | 
						|
            img.setAttribute('draggable', true)
 | 
						|
            img.ondragstart = (e) => {
 | 
						|
                this.currentPos = { x: e.clientX, y: e.clientY }
 | 
						|
                return true
 | 
						|
            }
 | 
						|
            img.ondrag = (e) => {
 | 
						|
                e.preventDefault()
 | 
						|
 | 
						|
                let target = this.element.querySelector('iframe') || this.element
 | 
						|
                let delta = {
 | 
						|
                    x: e.clientX - this.currentPos.x,
 | 
						|
                    y: e.clientY - this.currentPos.y,
 | 
						|
                }
 | 
						|
 | 
						|
                this.currentPos = { x: e.clientX, y: e.clientY }
 | 
						|
                if (delta.x == 0 && delta.y == 0) return
 | 
						|
 | 
						|
                let rect = target.getBoundingClientRect()
 | 
						|
                let width = rect.width + delta.x
 | 
						|
                let height = rect.height + delta.y
 | 
						|
                target.style.width = width + 'px'
 | 
						|
                target.style.height = height + 'px'
 | 
						|
 | 
						|
                switch (this.notchPosition) {
 | 
						|
                    case 'bottomLeft':
 | 
						|
                    case 'bottomCenter':
 | 
						|
                        let bottom = parseFloat(this.element.style.bottom)
 | 
						|
                        this.element.style.bottom = bottom - delta.y + 'px'
 | 
						|
                        break
 | 
						|
                    default:
 | 
						|
                        break
 | 
						|
                }
 | 
						|
                //console.log("onResize", this.onResize)
 | 
						|
                if (this.onResize) {
 | 
						|
                    this.onResize({ target, delta, width, height })
 | 
						|
                }
 | 
						|
            }
 | 
						|
            img.ondragend = (e) => {}
 | 
						|
            this.element.appendChild(img)
 | 
						|
        }
 | 
						|
 | 
						|
        for (let key in content) {
 | 
						|
            console.log('using', key, this.loaded)
 | 
						|
            switch (key) {
 | 
						|
                case 'selector':
 | 
						|
                    break
 | 
						|
                case 'text':
 | 
						|
                    let text = document.createElement('span')
 | 
						|
                    this.element.appendChild(text)
 | 
						|
                    text.innerHTML = content[key]
 | 
						|
                    Elements.setStyle(text, { color: this.normalColor })
 | 
						|
                    Elements.addClass(text, 'unselectable')
 | 
						|
                    Elements.addClass(text, 'PopupContent')
 | 
						|
                    this.insertedNode = text
 | 
						|
                    this.loaded = true
 | 
						|
                    break
 | 
						|
                case 'img':
 | 
						|
                    alert('img to be implemented')
 | 
						|
                    break
 | 
						|
                case 'iframe':
 | 
						|
                    let iframe = document.createElement('iframe')
 | 
						|
                    iframe.setAttribute('frameBorder', 0)
 | 
						|
                    iframe.src = content[key]
 | 
						|
                    iframe.onload = (e) => {
 | 
						|
                        let body = iframe.contentWindow.document.body
 | 
						|
                        let observer = new MutationObserver(() => {
 | 
						|
                            this.iframeChanged(iframe)
 | 
						|
                        })
 | 
						|
                        observer.observe(iframe.contentWindow.document, {
 | 
						|
                            attributes: true,
 | 
						|
                            subtree: true,
 | 
						|
                            childList: true,
 | 
						|
                            characterData: true,
 | 
						|
                        })
 | 
						|
                        let w = Math.max(body.scrollWidth, body.offsetWidth)
 | 
						|
                        let h = Math.max(body.scrollHeight, body.offsetHeight)
 | 
						|
                        iframe.style.width = w + 'px'
 | 
						|
                        iframe.style.height = h + 'px'
 | 
						|
                        this.layoutAfterInsert()
 | 
						|
                        if (this.onload != null) {
 | 
						|
                            this.onload()
 | 
						|
                        }
 | 
						|
                        this.loaded = true
 | 
						|
                    }
 | 
						|
                    this.element.appendChild(iframe)
 | 
						|
                    Elements.addClass(iframe, 'PopupContent')
 | 
						|
                    this.insertIntoDOM()
 | 
						|
                    return
 | 
						|
                case 'html':
 | 
						|
                    this.loaded = false
 | 
						|
                    let div = document.createElement('div')
 | 
						|
                    Elements.addClass(div, 'PopupContent')
 | 
						|
                    this.element.appendChild(div)
 | 
						|
                    div.innerHTML = content.html
 | 
						|
                    //console.log("insert", content)
 | 
						|
                    let selector = content.selector
 | 
						|
                    if (selector) {
 | 
						|
                        this.insertedNode = div.querySelector(selector)
 | 
						|
                        if (this.insertedNode == null) {
 | 
						|
                            div.innerHTML = `<p style="color:red;">Popup content not found. Missing ${selector}</p>`
 | 
						|
                            this.insertedNode = div.firstElementChild
 | 
						|
                        }
 | 
						|
                    } else {
 | 
						|
                        this.insertedNode = div.firstElementChild || div
 | 
						|
                    }
 | 
						|
                    this.setAlpha(this.insertedNode, 0)
 | 
						|
                    let images = this.element.querySelectorAll('img')
 | 
						|
                    let total = 0
 | 
						|
                    if (images.length > 0) {
 | 
						|
                        let count = 0
 | 
						|
                        for (let image of images) {
 | 
						|
                            if (!image.complete && !image.src.startsWith('data:')) {
 | 
						|
                                total += 1
 | 
						|
                                console.log('image not complete', image.src)
 | 
						|
                                image.onload = (e) => {
 | 
						|
                                    count += 1
 | 
						|
                                    if (count == total) {
 | 
						|
                                        this.loaded = true
 | 
						|
                                        if (this.onload != null) {
 | 
						|
                                            this.onload()
 | 
						|
                                        }
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    if (total == 0) {
 | 
						|
                        this.loaded = true
 | 
						|
                    }
 | 
						|
                    break
 | 
						|
                case 'node':
 | 
						|
                    this.loaded = true
 | 
						|
                    Elements.addClass(content.node, 'PopupContent')
 | 
						|
                    this.element.appendChild(content.node)
 | 
						|
                    this.insertedNode = content.node
 | 
						|
                    this.setAlpha(this.insertedNode, 0)
 | 
						|
                    break
 | 
						|
                default:
 | 
						|
                    alert('Unexpected content type: ' + key)
 | 
						|
                    break
 | 
						|
            }
 | 
						|
        }
 | 
						|
        this.insertIntoDOM()
 | 
						|
        this.layoutAfterInsert()
 | 
						|
        this.setupEventHandler()
 | 
						|
        return this
 | 
						|
    }
 | 
						|
 | 
						|
    handleClose(e) {
 | 
						|
        let closing = this.closingEvent(e)
 | 
						|
        if (closing) {
 | 
						|
            this.close(e)
 | 
						|
        } else {
 | 
						|
            this.setupCloseHandler()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    setupCloseHandler() {
 | 
						|
        let close = this.handleClose
 | 
						|
        if (this.hideOnUp) {
 | 
						|
            if (window.PointerEvent)
 | 
						|
                this.parent.addEventListener('pointerup', close.bind(this), {
 | 
						|
                    capture: true,
 | 
						|
                    once: true,
 | 
						|
                })
 | 
						|
            else if (window.TouchEvent)
 | 
						|
                this.parent.addEventListener('touchend', close.bind(this), {
 | 
						|
                    capture: true,
 | 
						|
                    once: true,
 | 
						|
                })
 | 
						|
            else
 | 
						|
                this.parent.addEventListener('mouseup', close.bind(this), {
 | 
						|
                    capture: true,
 | 
						|
                    once: true,
 | 
						|
                })
 | 
						|
        } else {
 | 
						|
            if (window.PointerEvent)
 | 
						|
                this.parent.addEventListener('pointerdown', close.bind(this), {
 | 
						|
                    capture: true,
 | 
						|
                    once: true,
 | 
						|
                })
 | 
						|
            else if (window.TouchEvent)
 | 
						|
                this.parent.addEventListener('touchstart', close.bind(this), {
 | 
						|
                    capture: true,
 | 
						|
                    once: true,
 | 
						|
                })
 | 
						|
            else
 | 
						|
                this.parent.addEventListener('mousedown', close.bind(this), {
 | 
						|
                    capture: true,
 | 
						|
                    once: true,
 | 
						|
                })
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    setupEventHandler() {
 | 
						|
        if (this.autoClose) {
 | 
						|
            this.setupCloseHandler()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    closingEvent(e) {
 | 
						|
        if (this.interactive) {
 | 
						|
            let node = e.target.closest('.PopupContent')
 | 
						|
            return node == null
 | 
						|
        }
 | 
						|
        return true
 | 
						|
    }
 | 
						|
 | 
						|
    iframeChanged(iframe) {
 | 
						|
        let body = iframe.contentWindow.document.body
 | 
						|
        let w = Math.max(body.scrollWidth, body.offsetWidth)
 | 
						|
        let h = Math.max(body.scrollHeight, body.offsetHeight)
 | 
						|
        iframe.style.width = w + 'px'
 | 
						|
        iframe.style.height = h + 'px'
 | 
						|
    }
 | 
						|
 | 
						|
    setupDraggable() {
 | 
						|
        if (this.draggable) {
 | 
						|
            let target = this.element
 | 
						|
            target.setAttribute('draggable', true)
 | 
						|
            target.ondragstart = (e) => {
 | 
						|
                this.currentPos = { x: e.clientX, y: e.clientY }
 | 
						|
                var img = document.createElement('img')
 | 
						|
                img.src = 'data:image/gifbase64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
 | 
						|
                e.dataTransfer.setDragImage(img, 0, 0)
 | 
						|
            }
 | 
						|
            target.ondrag = (e) => {
 | 
						|
                e.preventDefault()
 | 
						|
                let delta = {
 | 
						|
                    x: e.clientX - this.currentPos.x,
 | 
						|
                    y: e.clientY - this.currentPos.y,
 | 
						|
                }
 | 
						|
                this.currentPos = { x: e.clientX, y: e.clientY }
 | 
						|
                let left = parseFloat(target.style.left)
 | 
						|
                let top = parseFloat(target.style.top)
 | 
						|
 | 
						|
                target.style.left = left + delta.x + 'px'
 | 
						|
                target.style.top = top + delta.y + 'px'
 | 
						|
 | 
						|
                //console.log("Popup.ondrag", target, event.target)
 | 
						|
                if (this.onMove) {
 | 
						|
                    this.onMove({ target, delta })
 | 
						|
                }
 | 
						|
 | 
						|
                this.lastDrag = { left, top }
 | 
						|
            }
 | 
						|
            target.ondragend = (e) => {
 | 
						|
                target.style.left = this.lastDrag.left + 'px'
 | 
						|
                target.style.top = this.lastDrag.top + 'px'
 | 
						|
                this.currentPos = null
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    moveDragged(target) {}
 | 
						|
 | 
						|
    insertIntoDOM(layout = true) {
 | 
						|
        this.setAlpha(this.insertedNode, 0)
 | 
						|
        this.element.appendChild(this.notch)
 | 
						|
        this.parent.appendChild(this.element)
 | 
						|
    }
 | 
						|
 | 
						|
    layoutAfterInsert() {
 | 
						|
        Elements.setStyle(this.element, this.defaultStyle())
 | 
						|
        this.layout()
 | 
						|
        //this.element.style.opacity = 1
 | 
						|
    }
 | 
						|
 | 
						|
    /** Layout the menu items. Needed only in the subclass.
 | 
						|
     */
 | 
						|
    layout() {}
 | 
						|
 | 
						|
    remove() {
 | 
						|
        if (this.parent.contains(this.element)) this.parent.removeChild(this.element)
 | 
						|
        this.unregister(this.context)
 | 
						|
    }
 | 
						|
 | 
						|
    /** Close and remove the Popup from the DOM tree.
 | 
						|
     */
 | 
						|
    close(event) {
 | 
						|
        //console.log("Popup.close", this.closeCommand)
 | 
						|
        this.unregister(this.context)
 | 
						|
        if (this.closeCommand) {
 | 
						|
            this.closeCommand(this, () => this.remove(), event)
 | 
						|
        } else {
 | 
						|
            this.remove()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set the alpha value to show or hide the popup. Uses CSS transitions.
 | 
						|
     * (A former dependency on TweenLite has beeen removed.)
 | 
						|
     *
 | 
						|
     * @param {*} targets
 | 
						|
     * @param {*} value
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    setAlpha(targets, value) {
 | 
						|
        let objs = targets instanceof Array ? targets : [targets]
 | 
						|
        for (let obj of objs) {
 | 
						|
            if (value) {
 | 
						|
                obj.style.transition = 'opacity 0.2s ease-in'
 | 
						|
            }
 | 
						|
            obj.style.opacity = value
 | 
						|
        }
 | 
						|
        // if (value) {
 | 
						|
        //     TweenLite.to(targets, 0.2,  { autoAlpha: value })
 | 
						|
        // }
 | 
						|
        // else {
 | 
						|
        //     TweenLite.set(targets, { autoAlpha: 0 })
 | 
						|
        // }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Starts a fade in animation.
 | 
						|
     *
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    fadeIn() {
 | 
						|
        this.setAlpha([this.element, this.insertedNode], 1)
 | 
						|
    }
 | 
						|
 | 
						|
    /** Shows the Popup with the given commands at the specified point.
 | 
						|
     * @param {Object} content - A dict object with type strings (text, img, html) as keys
 | 
						|
     *                            and corresponding values.
 | 
						|
     * @param {Point} point - The position as x, y coordinates {px}.
 | 
						|
     * @return {Popup} this
 | 
						|
     */
 | 
						|
    showAt(content, point) {
 | 
						|
        this.setup(content)
 | 
						|
        console.log('showAt', this.loaded)
 | 
						|
        if (this.loaded) {
 | 
						|
            this.placeAt(point)
 | 
						|
            this.fadeIn()
 | 
						|
        } else {
 | 
						|
            this.setAlpha([this.element, this.insertedNode], 0)
 | 
						|
            this.onload = () => {
 | 
						|
                this.layoutAfterInsert()
 | 
						|
                this.placeAt(point)
 | 
						|
                this.fadeIn()
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return this
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     *  Place the origin, i.e. the upper left corner at the given position using CSS styles.
 | 
						|
     *
 | 
						|
     * @param {any} x
 | 
						|
     * @param {any} y
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    placeOrigin(x, y) {
 | 
						|
        Elements.setStyle(this.element, { left: x + 'px', top: y + 'px' })
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Calculate the local coordinates within the keepWithin container.
 | 
						|
     *
 | 
						|
     * @param {any} x
 | 
						|
     * @param {any} y
 | 
						|
     * @returns
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    localPointWithin(x, y, width, height) {
 | 
						|
        let pt = { x, y }
 | 
						|
        return pt
 | 
						|
    }
 | 
						|
 | 
						|
    withinDimensions() {
 | 
						|
        return {
 | 
						|
            width: this.keepWithin.offsetWidth,
 | 
						|
            height: this.keepWithin.offsetHeight,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    localDimensions() {
 | 
						|
        return {
 | 
						|
            width: this.element.offsetWidth,
 | 
						|
            height: this.element.offsetHeight,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the notch position depending on the local coordinates within the keepWithin container
 | 
						|
     * Divides the space vertically into top, center, bottom and horizontally into left, center, right
 | 
						|
     *
 | 
						|
     * @param {any} x
 | 
						|
     * @param {any} y
 | 
						|
     * @returns
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    notchPositionWithin(x, y) {
 | 
						|
        let horizontal = 'Center'
 | 
						|
        let vertical = 'center'
 | 
						|
        let { width, height } = this.withinDimensions()
 | 
						|
        let local = this.localPointWithin(x, y, width, height)
 | 
						|
        if (local.y < height * 0.33) {
 | 
						|
            vertical = 'top'
 | 
						|
        }
 | 
						|
        if (local.y > height * 0.66) {
 | 
						|
            vertical = 'bottom'
 | 
						|
        }
 | 
						|
        if (local.x < width * 0.33) {
 | 
						|
            horizontal = 'Left'
 | 
						|
        }
 | 
						|
        if (local.x > width * 0.66) {
 | 
						|
            horizontal = 'Right'
 | 
						|
        }
 | 
						|
        let result = vertical + horizontal
 | 
						|
        if (result == 'centerCenter') return this.notchPosition
 | 
						|
        return result
 | 
						|
    }
 | 
						|
 | 
						|
    placeAt(point) {
 | 
						|
        let x = point.x
 | 
						|
        let y = point.y
 | 
						|
        let notchPosition = this.notchPosition
 | 
						|
        if (this.keepWithin != null) {
 | 
						|
            notchPosition = this.notchPositionWithin(x, y)
 | 
						|
        }
 | 
						|
        Elements.setStyle(this.notch, this.notchStyle(notchPosition))
 | 
						|
        this.notch.className = 'notch ' + notchPosition
 | 
						|
        let { width, height } = this.localDimensions()
 | 
						|
 | 
						|
        //if targetBoundingBox is set, popup is placed next to the rectangle
 | 
						|
        if (this.targetBoundingBox) {
 | 
						|
            let bbTop = this.targetBoundingBox.y
 | 
						|
            let bbBottom = this.targetBoundingBox.y + this.targetBoundingBox.height
 | 
						|
            let bbLeft = this.targetBoundingBox.x
 | 
						|
            let bbRight = this.targetBoundingBox.x + this.targetBoundingBox.width
 | 
						|
            //console.log("place popup with bb set:", x, y, bbTop, bbBottom, bbLeft, bbRight)
 | 
						|
            switch (notchPosition) {
 | 
						|
                case 'bottomLeft':
 | 
						|
                case 'bottomRight':
 | 
						|
                case 'bottomCenter':
 | 
						|
                    y = bbTop
 | 
						|
                    if (!this.useEventPosWithBoundingBox) x = (bbLeft + bbRight) / 2
 | 
						|
                    break
 | 
						|
                case 'topLeft':
 | 
						|
                case 'topRight':
 | 
						|
                case 'topCenter':
 | 
						|
                    y = bbBottom
 | 
						|
                    if (!this.useEventPosWithBoundingBox) x = (bbLeft + bbRight) / 2
 | 
						|
                    break
 | 
						|
                case 'centerRight':
 | 
						|
                    x = bbLeft
 | 
						|
                    if (!this.useEventPosWithBoundingBox) y = (bbTop + bbBottom) / 2
 | 
						|
                    break
 | 
						|
                case 'centerLeft':
 | 
						|
                    x = bbRight
 | 
						|
                    if (!this.useEventPosWithBoundingBox) y = (bbTop + bbBottom) / 2
 | 
						|
                    break
 | 
						|
                default:
 | 
						|
                    break
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        //calculate position depending on several (optional) parameters
 | 
						|
        switch (notchPosition) {
 | 
						|
            case 'bottomLeft':
 | 
						|
                x -= this.padding
 | 
						|
                x -= this.notchSize
 | 
						|
                y -= height
 | 
						|
                y -= this.notchSize * 2
 | 
						|
                y -= this.posOffset
 | 
						|
                break
 | 
						|
            case 'bottomRight':
 | 
						|
                x -= width
 | 
						|
                x += this.padding
 | 
						|
                x += this.notchSize
 | 
						|
                y -= height
 | 
						|
                y -= this.notchSize * 2
 | 
						|
                y -= this.posOffset
 | 
						|
                break
 | 
						|
            case 'bottomCenter':
 | 
						|
                x -= width / 2
 | 
						|
                y -= height
 | 
						|
                y -= this.notchSize * 2
 | 
						|
                y -= this.posOffset
 | 
						|
                break
 | 
						|
            case 'topLeft':
 | 
						|
                x -= this.padding
 | 
						|
                x -= this.notchSize
 | 
						|
                y += this.notchSize * 2
 | 
						|
                y += this.posOffset
 | 
						|
                break
 | 
						|
            case 'topRight':
 | 
						|
                x -= width
 | 
						|
                x += this.padding
 | 
						|
                x += this.notchSize
 | 
						|
                y += this.notchSize * 2
 | 
						|
                y += this.posOffset
 | 
						|
                break
 | 
						|
            case 'topCenter':
 | 
						|
                x -= width / 2
 | 
						|
                y += this.notchSize * 2
 | 
						|
                y += this.posOffset
 | 
						|
                break
 | 
						|
            case 'centerRight':
 | 
						|
                x -= width + this.notchSize * 2
 | 
						|
                x -= this.posOffset
 | 
						|
                y -= height / 2
 | 
						|
                break
 | 
						|
            case 'centerLeft':
 | 
						|
                //console.log("height", height)
 | 
						|
                y -= height / 2
 | 
						|
                x += this.notchSize * 2
 | 
						|
                x += this.posOffset
 | 
						|
                break
 | 
						|
            default:
 | 
						|
                break
 | 
						|
        }
 | 
						|
        this.placeOrigin(x, y)
 | 
						|
    }
 | 
						|
 | 
						|
    /** Shows the Popup with the given commands at the current position.
 | 
						|
     * @param {Object} content - A dict object with type strings (text, img, html) as keys
 | 
						|
     *                            and corresponding values.
 | 
						|
     * @return {Popup} this
 | 
						|
     */
 | 
						|
    show(content) {
 | 
						|
        this.setup(content)
 | 
						|
        this.fadeIn()
 | 
						|
        return this
 | 
						|
    }
 | 
						|
 | 
						|
    /** Configuration object. Return default styles as CSS values.
 | 
						|
     */
 | 
						|
    defaultStyle() {
 | 
						|
        let padding = this.padding
 | 
						|
        let style = {
 | 
						|
            maxWidth: this.maxWidth + 'px',
 | 
						|
            zIndex: this.zIndex,
 | 
						|
            position: 'absolute',
 | 
						|
        }
 | 
						|
        if (this.minWidth) {
 | 
						|
            style.minWidth = this.minWidth + 'px'
 | 
						|
        }
 | 
						|
        if (!this.noStyle) {
 | 
						|
            Object.assign(style, {
 | 
						|
                borderRadius: Math.round(this.padding / 2) + 'px',
 | 
						|
                backgroundColor: this.backgroundColor,
 | 
						|
                padding: this.padding + 'px',
 | 
						|
                boxShadow: '0 10px 15px rgba(0, 0, 0, 0.3)',
 | 
						|
                fontFamily: this.fontFamily,
 | 
						|
                fontSize: this.fontSize,
 | 
						|
                stroke: 'black',
 | 
						|
                fill: 'white',
 | 
						|
            })
 | 
						|
        }
 | 
						|
 | 
						|
        return style
 | 
						|
    }
 | 
						|
 | 
						|
    /** Configuration object. Return notch styles as CSS values.
 | 
						|
     */
 | 
						|
    notchStyle(notchPosition = null) {
 | 
						|
        if (notchPosition == null) {
 | 
						|
            notchPosition = this.notchPosition
 | 
						|
        }
 | 
						|
        let width = 0
 | 
						|
        let height = 0
 | 
						|
        let left = this.padding
 | 
						|
        let size = this.localDimensions()
 | 
						|
        if (notchPosition.endsWith('Right')) {
 | 
						|
            left = size.width - this.padding - this.notchSize * 2
 | 
						|
        }
 | 
						|
        if (notchPosition.endsWith('Center')) {
 | 
						|
            left = size.width / 2 - this.notchSize
 | 
						|
        }
 | 
						|
        left = Math.round(left) + 'px'
 | 
						|
        let borderBottom = 0
 | 
						|
        let borderTop = 0
 | 
						|
 | 
						|
        if (notchPosition.startsWith('bottom')) {
 | 
						|
            if (this.noStyle) {
 | 
						|
                return {
 | 
						|
                    width,
 | 
						|
                    height,
 | 
						|
                    left,
 | 
						|
                    bottom: -this.notchSize + 'px',
 | 
						|
                    position: 'absolute',
 | 
						|
                    borderStyle: 'solid',
 | 
						|
                    borderTopWidth: this.notchSize + 'px',
 | 
						|
                    borderRight: this.notchSize + 'px solid transparent',
 | 
						|
                    borderLeft: this.notchSize + 'px solid transparent',
 | 
						|
                    borderBottom: 0,
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                return {
 | 
						|
                    width,
 | 
						|
                    height,
 | 
						|
                    left,
 | 
						|
                    boxShadow: '0 12px 15px rgba(0, 0, 0, 0.1)',
 | 
						|
                    bottom: -this.notchSize + 'px',
 | 
						|
                    position: 'absolute',
 | 
						|
                    borderTop: this.notchSize + 'px solid ' + this.backgroundColor,
 | 
						|
                    borderRight: this.notchSize + 'px solid transparent',
 | 
						|
                    borderLeft: this.notchSize + 'px solid transparent',
 | 
						|
                    borderBottom: 0,
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (notchPosition.startsWith('top')) {
 | 
						|
            if (this.noStyle) {
 | 
						|
                return {
 | 
						|
                    width,
 | 
						|
                    height,
 | 
						|
                    left,
 | 
						|
                    top: -this.notchSize + 'px',
 | 
						|
                    position: 'absolute',
 | 
						|
                    borderStyle: 'solid',
 | 
						|
                    borderBottomWidth: this.notchSize + 'px',
 | 
						|
                    borderRight: this.notchSize + 'px solid transparent',
 | 
						|
                    borderLeft: this.notchSize + 'px solid transparent',
 | 
						|
                    borderTop: 0,
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                return {
 | 
						|
                    width,
 | 
						|
                    height,
 | 
						|
                    left,
 | 
						|
                    top: -this.notchSize + 'px',
 | 
						|
                    position: 'absolute',
 | 
						|
                    borderBottom: this.notchSize + 'px solid ' + this.backgroundColor,
 | 
						|
                    borderRight: this.notchSize + 'px solid transparent',
 | 
						|
                    borderLeft: this.notchSize + 'px solid transparent',
 | 
						|
                    borderTop: 0,
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (this.noStyle) {
 | 
						|
            if (notchPosition.endsWith('Left')) {
 | 
						|
                left = -this.notchSize * 2 + 'px'
 | 
						|
            }
 | 
						|
 | 
						|
            if (notchPosition.endsWith('Right')) {
 | 
						|
                left = size.width + 'px'
 | 
						|
            }
 | 
						|
 | 
						|
            let top = size.height / 2 - this.notchSize
 | 
						|
            top = Math.round(top) + 'px'
 | 
						|
 | 
						|
            return {
 | 
						|
                width,
 | 
						|
                height,
 | 
						|
                left,
 | 
						|
                top,
 | 
						|
                borderRightWidth: this.notchSize,
 | 
						|
                borderLeftWidth: this.notchSize,
 | 
						|
                position: 'absolute',
 | 
						|
                borderTop: this.notchSize + 'px solid transparent',
 | 
						|
                borderBottom: this.notchSize + 'px solid transparent',
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            let borderRight = this.notchSize + 'px solid transparent'
 | 
						|
            let borderLeft = this.notchSize + 'px solid transparent'
 | 
						|
            let top = size.height / 2 - this.notchSize
 | 
						|
            if (notchPosition.endsWith('Left')) {
 | 
						|
                left = -this.notchSize * 2 + 'px'
 | 
						|
                borderRight = this.notchSize + 'px solid ' + this.backgroundColor
 | 
						|
                this.element.style.boxShadow = '15px 10px 15px  rgba(0, 0, 0, 0.3)'
 | 
						|
            }
 | 
						|
            if (notchPosition.endsWith('Right')) {
 | 
						|
                left = size.width + 'px'
 | 
						|
                borderLeft = this.notchSize + 'px solid ' + this.backgroundColor
 | 
						|
                this.element.style.boxShadow = '15px 5px 15px rgba(0, 0, 0, 0.3)'
 | 
						|
            }
 | 
						|
 | 
						|
            top = Math.round(top) + 'px'
 | 
						|
 | 
						|
            return {
 | 
						|
                width,
 | 
						|
                height,
 | 
						|
                left,
 | 
						|
                top,
 | 
						|
                borderRight,
 | 
						|
                borderLeft,
 | 
						|
                //  boxShadow,
 | 
						|
                position: 'absolute',
 | 
						|
                borderTop: this.notchSize + 'px solid transparent',
 | 
						|
                borderBottom: this.notchSize + 'px solid transparent',
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /** Convenient static methods to show and reuse a Popup implemented
 | 
						|
     * as a class variable.
 | 
						|
     * @param {Object} content - A dict object with type strings (text, img, html) as keys
 | 
						|
     *                            and corresponding values.
 | 
						|
     * @param {Point} point - The position as x, y coordinates {px}.
 | 
						|
     * @param {boolean} autoClose - Autoclose the menu after selecting an item.
 | 
						|
     */
 | 
						|
    static open(
 | 
						|
        content,
 | 
						|
        point,
 | 
						|
        {
 | 
						|
            parent = null,
 | 
						|
            context = window,
 | 
						|
            fontSize = '1em',
 | 
						|
            fontFamily = 'Arial',
 | 
						|
            padding = 16,
 | 
						|
            notchSize = 10,
 | 
						|
            switchPos = false,
 | 
						|
            minWidth = null,
 | 
						|
            maxWidth = 800,
 | 
						|
            backgroundColor = '#EEE',
 | 
						|
            zIndex = 0,
 | 
						|
            normalColor = '#444',
 | 
						|
            closeIcon = null,
 | 
						|
            resizeIcon = null,
 | 
						|
            closeCommand = null,
 | 
						|
            autoClose = true,
 | 
						|
            keepWithin = null,
 | 
						|
            draggable = false,
 | 
						|
            posOffset = 0,
 | 
						|
            targetBoundingBox = null,
 | 
						|
            useEventPosWithBoundingBox = false,
 | 
						|
            interactive = false,
 | 
						|
            onResize = null,
 | 
						|
            onMove = null,
 | 
						|
        } = {}
 | 
						|
    ) {
 | 
						|
        let notchPosition = switchPos && point.y < 50 ? 'topCenter' : 'bottomCenter'
 | 
						|
        let popup = new Popup({
 | 
						|
            parent,
 | 
						|
            context,
 | 
						|
            fontFamily,
 | 
						|
            fontSize,
 | 
						|
            padding,
 | 
						|
            notchSize,
 | 
						|
            switchPos,
 | 
						|
            minWidth,
 | 
						|
            maxWidth,
 | 
						|
            backgroundColor,
 | 
						|
            normalColor,
 | 
						|
            notchPosition,
 | 
						|
            zIndex,
 | 
						|
            autoClose,
 | 
						|
            keepWithin,
 | 
						|
            closeCommand,
 | 
						|
            closeIcon,
 | 
						|
            resizeIcon,
 | 
						|
            draggable,
 | 
						|
            posOffset,
 | 
						|
            targetBoundingBox,
 | 
						|
            useEventPosWithBoundingBox,
 | 
						|
            interactive,
 | 
						|
            onResize,
 | 
						|
            onMove,
 | 
						|
        })
 | 
						|
        popup.register(context)
 | 
						|
        popup.showAt(content, point)
 | 
						|
        return popup
 | 
						|
    }
 | 
						|
 | 
						|
    /** Convenient static method to close the Popup implemented
 | 
						|
     * as a class variable. Calls the close command.
 | 
						|
     */
 | 
						|
    static closePopup(context = window) {
 | 
						|
        let popup = Poppable.registrations.get(context)
 | 
						|
        if (popup != null) {
 | 
						|
            popup.close()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /** Convenient static methods to remove the Popup implemented
 | 
						|
     * as a class variable. Removes the popup without performing the close command.
 | 
						|
     */
 | 
						|
    static remove(context = window) {
 | 
						|
        let popup = Poppable.registrations.get(context)
 | 
						|
        if (popup != null) {
 | 
						|
            popup.remove()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Convenient static method to compute the clicked rect of objects that have multiple clients rects.
 | 
						|
     * Needed to position popups correctly above objects with line breaks, e.g. spans
 | 
						|
     *
 | 
						|
     * @static
 | 
						|
     * @param {*} event
 | 
						|
     * @returns {*} DOMRect
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    static targetRect(event) {
 | 
						|
        let target = event.target
 | 
						|
        let x = event.pageX
 | 
						|
        let y = event.pageY
 | 
						|
        for (let rect of target.getClientRects()) {
 | 
						|
            let withinX = x >= rect.left && x <= rect.left + rect.width
 | 
						|
            let withinY = y >= rect.top && y <= rect.top + rect.height
 | 
						|
            if (withinX && withinY) {
 | 
						|
                return rect
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return null
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Convenient static method to compute the center of objects that have multiple clients rects.
 | 
						|
     * Needed to position popups correctly above objects with line breaks, e.g. spans
 | 
						|
     *
 | 
						|
     * @static
 | 
						|
     * @param {*} event
 | 
						|
     * @returns {*} Point
 | 
						|
     * @memberof Popup
 | 
						|
     */
 | 
						|
    static targetCenter(event) {
 | 
						|
        let target = event.target
 | 
						|
        let x = event.pageX
 | 
						|
        let y = event.pageY
 | 
						|
        let rect = Popup.targetRect(event)
 | 
						|
        if (rect != null) {
 | 
						|
            x = rect.left + rect.width / 2
 | 
						|
            y = rect.top + rect.height / 2
 | 
						|
        }
 | 
						|
        return { x, y }
 | 
						|
    }
 | 
						|
}
 |