import { tracked } from "@glimmer/tracking"; import { getOwner } from "@ember/application"; import { action } from "@ember/object"; import { guidFor } from "@ember/object/internals"; import Service from "@ember/service"; import DTooltipInstance from "float-kit/lib/d-tooltip-instance"; import { updatePosition } from "float-kit/lib/update-position"; export default class Tooltip extends Service { @tracked activeTooltip; @tracked portalOutletElement; /** * Render a tooltip * * @param {Element | DTooltipInstance} * - trigger - the element that triggered the tooltip, can also be an object implementing `getBoundingClientRect` * - tooltip - an instance of a tooltip * @param {Object} [options] - options, if trigger given as first argument * @param {String | Element | Component} [options.content] - Specifies the content of the tooltip * @param {Integer} [options.maxWidth] - Specifies the maximum width of the content * @param {Object} [options.data] - An object which will be passed as the `@data` argument when content is a `Component` * @param {Boolean} [options.arrow] - Determines if the tooltip has an arrow * @param {Boolean} [options.offset] - Displaces the content from its reference trigger in pixels * @param {String} [options.identifier] - Add a data-identifier attribute to the trigger and the content * @param {Boolean} [options.inline] - Improves positioning for trigger that spans over multiple lines * * @returns {Promise} */ @action async show() { let instance; if (arguments[0] instanceof DTooltipInstance) { instance = arguments[0]; if (this.activeTooltip === instance && this.activeTooltip.expanded) { return; } } else { const trigger = arguments[0]; if ( this.activeTooltip && this.activeTooltip.id === (trigger?.id?.length ? trigger.id : guidFor(trigger)) && this.activeTooltip.expanded ) { this.activeTooltip?.close(); return; } instance = new DTooltipInstance(getOwner(this), trigger, arguments[1]); } await this.replace(instance); instance.expanded = true; return instance; } /** * Replaces any active tooltip */ @action async replace(tooltip) { await this.activeTooltip?.close(); this.activeTooltip = tooltip; } /** * Closes the active tooltip * @param {DTooltipInstance} [tooltip] - the tooltip to close, if not provider will close any active tooltip */ @action async close(tooltip) { if (this.activeTooltip && tooltip && this.activeTooltip.id !== tooltip.id) { return; } await this.activeTooltip?.close(); this.activeTooltip = null; } /** * Update the tooltip position * @param {DTooltipInstance} [tooltip] - the tooltip to update, if not provider will update any active tooltip */ @action async update(tooltip) { const instance = tooltip || this.activeTooltip; if (!instance) { return; } await updatePosition(instance.trigger, instance.content, instance.options); await instance.show(); } /** * Register event listeners on a trigger to show a tooltip * * @param {Element} trigger - the element that triggered the tooltip, can also be an object implementing `getBoundingClientRect` * @param {Object} [options] - @see `show` * * @returns {DTooltipInstance} An instance of the tooltip */ @action register(trigger, options = {}) { return new DTooltipInstance(getOwner(this), trigger, { ...options, listeners: true, beforeTrigger: async (tooltip) => { await this.replace(tooltip); }, }); } @action registerPortalOutletElement(element) { this.portalOutletElement = element; } }