Source

client/ui/elements/tip.js

/**
 * 
 * Display a tip that follows the mouse cursor.
 * The tip is just a floating <Html> component that moves with the cursor.
 * 
 * @param {object} config
 * @param {string} config.text - Tip text
 * @param {string} [config.textAlign] - ex: "center"
 * @param {string} [config.target] - DOM target insertion point
 * @param {number} [tipConfig.x] - Optional static x
 * @param {number} [tipConfig.Y] - Optional static y
 * @param {object} [config.deltaX] - X position difference from the cursor position
 * @param {object} [config.deltaY] - Y position difference from the cursor position
 * @param {object} [config.minWidth]
 * @param {object} [config.maxWidth]
 * @returns this
 */
kiss.ui.Tip = class Tip {
    /**
     * You can create a Tip using the class, the shorthand, the HTMLElement "attachTip" method, or the "tip" property of KissJS components:
     * ```
     * // Using kiss namespace
     * new kiss.ui.Tip(config)
     * 
     * // Using the class
     * new Tip(config)
     * 
     * // Using the shorthand
     * createTip({
     *  target: "your_element_id",
     *  text: "your tip text",
     *  deltaX: 0,
     *  deltaY: 20
     * })
     * 
     * // You can also directly attach a tip to any HTMLElement or KissJS component like this:
     * myHTMLElementOrKissComponent.attachTip({
     *  text: "your tip text",
     *  deltaX: 0,
     *  deltaY: 20
     * })
     * 
     * // Finally, KissJS components have a "tip" property to attach a tip automatically:
     * const myTextField = createField({
     *  type: "textarea",
     *  label: "Comments",
     *  tip: "Please, tell us about what you think"
     * })
     * 
     * const myButton = createButton({
     *  icon: "fas fa-rocket",
     *  tip: "Click to launch the rocket!"
     * })
     * ```
     */
    constructor(config) {
        const tipId = uid()
        const targetElement = config.target
        const deltaX = config.deltaX || 0
        const deltaY = config.deltaY || 20
        const minWidth = config.minWidth || 50
        const maxWidth = config.maxWidth || 300
        const message = config.text.replaceAll("\n", "<br>")

        return createHtml({
            id: tipId,
            position: "absolute",
            display: "block",
            zIndex: 1000,
            class: "a-tip",
            minWidth,
            maxWidth,

            html: (config.textAlign) ? `<div style="width: 100%; text-align: ${config.textAlign}">${message}</div>` : message,

            methods: {
                /**
                 * When the tip is rendered, the component triggers this load method.
                 * We track the mousemove event at the document level.
                 * Note that it will overwrite other onmousemove events, if any.
                 */
                load() {                    
                    // Make the tip follow the mouse cursor
                    document.addEventListener("mousemove", this.showTip)

                    // Display the tip on the target when entering it
                    targetElement.onmouseenter = () => this.render()

                    // Hide the tip and destroy the document.mousemove event when leaving the target
                    targetElement.onmouseleave = () => this.hideTip()

                    // Hide the tip and destroy the document.mousemove event when the target is deleted
                    targetElement._beforeDelete = () => this.hideTip()
                }, 
                
                hideTip() {
                    document.removeEventListener("mousemove", this.showTip)
                    this.remove()
                },

                detach() {
                    document.removeEventListener("mousemove", this.showTip)
                    targetElement.onmouseenter = null
                    targetElement.onmouseleave = null
                    this.remove()
                },

                showTip(event) {
                    if (!event) return
                    const element = $(tipId)
                    element.showAt(config.x || event.pageX + deltaX, config.y || event.pageY + deltaY)
                }
            }
        })
    }
}

/**
 * Shorthand to create a new Tip. See [kiss.ui.Tip](kiss.ui.Tip.html)
 * 
 * @param {object} config
 * @returns HTMLElement
 */
const createTip = (config) => new kiss.ui.Tip(config)

;