import type { AutoTooltipOption, Column, SlickPlugin } from '../models/index.js'; import { type SlickEventData, Utils as Utils_ } from '../slick.core.js'; import type { SlickGrid } from '../slick.grid.js'; // for (iife) load Slick methods from global Slick object, or use imports for (esm) const Utils = IIFE_ONLY ? Slick.Utils : Utils_; /** * AutoTooltips plugin to show/hide tooltips when columns are too narrow to fit content. */ export class SlickAutoTooltips implements SlickPlugin { // -- // public API pluginName = 'AutoTooltips' as const; // -- // protected props protected _grid!: SlickGrid; protected _options?: AutoTooltipOption; protected _defaults: AutoTooltipOption = { enableForCells: true, enableForHeaderCells: false, maxToolTipLength: undefined, replaceExisting: true }; /** * Constructor of the SlickGrid 3rd party plugin, it can optionally receive options * @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells * @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells * @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip * @param {boolean} [options.replaceExisting=null] - Allow preventing custom tooltips from being overwritten by auto tooltips */ constructor(options?: AutoTooltipOption) { this._options = Utils.extend(true, {}, this._defaults, options); } /** * Initialize plugin. */ init(grid: SlickGrid) { this._grid = grid; if (this._options?.enableForCells) { this._grid.onMouseEnter.subscribe(this.handleMouseEnter.bind(this)); } if (this._options?.enableForHeaderCells) { this._grid.onHeaderMouseEnter.subscribe(this.handleHeaderMouseEnter.bind(this)); } } /** * Destroy plugin. */ destroy() { if (this._options?.enableForCells) { this._grid.onMouseEnter.unsubscribe(this.handleMouseEnter.bind(this)); } if (this._options?.enableForHeaderCells) { this._grid.onHeaderMouseEnter.unsubscribe(this.handleHeaderMouseEnter.bind(this)); } } /** * Handle mouse entering grid cell to add/remove tooltip. * @param {SlickEventData} event - The event */ protected handleMouseEnter(event: SlickEventData) { const cell = this._grid.getCellFromEvent(event); if (cell) { let node: HTMLElement | null = this._grid.getCellNode(cell.row, cell.cell); let text; if (this._options && node && (!node.title || this._options?.replaceExisting)) { if (node.clientWidth < node.scrollWidth) { text = node.textContent?.trim() ?? ''; if (this._options?.maxToolTipLength && text.length > this._options?.maxToolTipLength) { text = text.substring(0, this._options.maxToolTipLength - 3) + '...'; } } else { text = ''; } node.title = text; } node = null; } } /** * Handle mouse entering header cell to add/remove tooltip. * @param {SlickEventData} event - The event * @param {object} args.column - The column definition */ protected handleHeaderMouseEnter(event: SlickEventData, args: { column: Column; }) { const column = args.column; let node: HTMLDivElement | null; const targetElm = (event.target as HTMLDivElement); if (targetElm) { node = targetElm.closest('.slick-header-column'); if (node && !(column?.toolTip)) { const titleVal = (targetElm.clientWidth < node.clientWidth) ? column?.name ?? '' : ''; node.title = Utils.getHtmlStringOutput(titleVal, 'innerHTML'); } } node = null; } } // extend Slick namespace on window object when building as iife if (IIFE_ONLY && window.Slick) { Utils.extend(true, window, { Slick: { AutoTooltips: SlickAutoTooltips } }); }