wangeditor
Version: 
wangEditor - 轻量级 web 富文本编辑器,配置方便,使用简单,开源免费
191 lines (167 loc) • 6.32 kB
text/typescript
/**
 * @description Tooltip class
 * @author wangfupeng
 */
import $, { DomElement } from '../../utils/dom-core'
import Editor from '../../editor/index'
type PositionDataType = {
    top: number
    left: number
}
export type TooltipConfItemType = {
    $elem: DomElement
    onClick: Function
}
export type TooltipConfType = Array<TooltipConfItemType>
class Tooltip {
    private $container: DomElement
    private $targetElem: DomElement
    private editor: Editor
    private conf: TooltipConfType
    private _show: boolean
    private _isInsertTextContainer: boolean
    constructor(editor: Editor, $elem: DomElement, conf: TooltipConfType) {
        this.editor = editor
        this.$targetElem = $elem
        this.conf = conf
        this._show = false
        this._isInsertTextContainer = false
        // 定义 container
        const $container = $('<div></div>')
        $container.addClass('w-e-tooltip')
        this.$container = $container
    }
    /**
     * 获取 tooltip 定位
     */
    private getPositionData(): PositionDataType {
        const $container = this.$container
        let top = 0
        let left = 0
        // tooltip 的高度
        const tooltipHeight = 20
        // 网页的 scrollTop
        const pageScrollTop = document.documentElement.scrollTop
        // 目标元素的 rect
        const targetElemRect = this.$targetElem.getBoundingClientRect()
        // 编辑区域的 rect
        const textElemRect = this.editor.$textElem.getBoundingClientRect()
        // 获取基于 textContainerElem 的 位置信息
        const targetOffset = this.$targetElem.getOffsetData()
        const targetParentElem = $(targetOffset.parent)
        // 获取 编辑区域的滚动条信息
        const scrollTop = this.editor.$textElem.elems[0].scrollTop
        // 是否插入 textContainer 中
        this._isInsertTextContainer = targetParentElem.equal(this.editor.$textContainerElem)
        if (this._isInsertTextContainer) {
            // 父容器的高度
            const targetParentElemHeight = targetParentElem.getBoundingClientRect().height
            // 相对于父容器的位置信息
            const { top: offsetTop, left: offsetLeft, height: offsetHeight } = targetOffset
            // 元素基于父容器的 绝对top信息
            const absoluteTop = offsetTop - scrollTop
            if (absoluteTop > tooltipHeight + 5) {
                // 说明模板元素的顶部空间足够
                top = absoluteTop - tooltipHeight - 15
                $container.addClass('w-e-tooltip-up')
            } else if (absoluteTop + offsetHeight + tooltipHeight < targetParentElemHeight) {
                // 说明模板元素的底部空间足够
                top = absoluteTop + offsetHeight + 10
                $container.addClass('w-e-tooltip-down')
            } else {
                // 其他情况,tooltip 放在目标元素左上角
                top = (absoluteTop > 0 ? absoluteTop : 0) + tooltipHeight + 10
                $container.addClass('w-e-tooltip-down')
            }
            // 计算 left
            if (offsetLeft < 0) {
                left = 0
            } else {
                left = offsetLeft
            }
        } else {
            if (targetElemRect.top < tooltipHeight) {
                // 说明目标元素的顶部,因滑动隐藏在浏览器上方。tooltip 要放在目标元素下面
                top = targetElemRect.bottom + pageScrollTop + 5 // 5px 间距
                $container.addClass('w-e-tooltip-down')
            } else if (targetElemRect.top - textElemRect.top < tooltipHeight) {
                // 说明目标元素的顶部,因滑动隐藏在编辑区域上方。tooltip 要放在目标元素下面
                top = targetElemRect.bottom + pageScrollTop + 5 // 5px 间距
                $container.addClass('w-e-tooltip-down')
            } else {
                // 其他情况,tooltip 放在目标元素上方
                top = targetElemRect.top + pageScrollTop - tooltipHeight - 15 // 减去 toolbar 的高度,还有 15px 间距
                $container.addClass('w-e-tooltip-up')
            }
            // 计算 left
            if (targetElemRect.left < 0) {
                left = 0
            } else {
                left = targetElemRect.left
            }
        }
        // 返回结果
        return { top, left }
    }
    /**
     * 添加 tooltip 菜单
     */
    private appendMenus(): void {
        const conf = this.conf
        const editor = this.editor
        const $targetElem = this.$targetElem
        const $container = this.$container
        conf.forEach((item: TooltipConfItemType, index: number) => {
            // 添加元素
            const $elem = item.$elem
            const $wrapper = $('<div></div>')
            $wrapper.addClass('w-e-tooltip-item-wrapper ')
            $wrapper.append($elem)
            $container.append($wrapper)
            // 绑定点击事件
            $elem.on('click', (e: Event) => {
                e.preventDefault()
                const res = item.onClick(editor, $targetElem)
                if (res) this.remove()
            })
        })
    }
    /**
     * 创建 tooltip
     */
    public create(): void {
        const editor = this.editor
        const $container = this.$container
        // 生成 container 的内容
        this.appendMenus()
        // 设置定位
        const { top, left } = this.getPositionData()
        $container.css('top', `${top}px`)
        $container.css('left', `${left}px`)
        // 设置 z-index
        $container.css('z-index', editor.zIndex.get('tooltip'))
        // 添加到 DOM
        if (this._isInsertTextContainer) {
            this.editor.$textContainerElem.append($container)
        } else {
            $('body').append($container)
        }
        this._show = true
        editor.beforeDestroy(this.remove.bind(this))
        editor.txt.eventHooks.onBlurEvents.push(this.remove.bind(this))
    }
    /**
     * 移除该 tooltip
     */
    public remove(): void {
        this.$container.remove()
        this._show = false
    }
    /**
     * 是否显示
     */
    public get isShow(): boolean {
        return this._show
    }
}
export default Tooltip