VGTooltip

Контекстные подсказки и информационные popover: от Data API до управления экземпляром.

API и жизненный цикл

Методы, отменяемые события, динамический DOM и справочник.

Под каждым примером — его HTML, JavaScript и CSS. Подключите JS из vgapp и SCSS из vgapp/scss (или готовый CSS пакета). Кнопки и раскладка используют OKAUX 1.0.2; тема сайта управляется OKAUX.

Методы и отменяемые события

Ручной экземпляр: show, hide, toggle, dispose и повторный init. Отмена show/hide видна в журнале. Автозакрытие отключено, чтобы внешние управляющие кнопки не скрывали подсказку.

Внешние управляющие элементы:

Журнал событий
HTML
<div id="tooltip-api-demo">
  <button id="tooltip-api-target" type="button" class="btn btn-surface"
    title="Управляемая подсказка">Триггер под управлением API</button>
  <p>Внешние управляющие элементы:</p>
  <div class="d-flex gap-3" style="flex-wrap: wrap">
    <button type="button" class="btn btn-primary btn-sm" id="tooltip-api-show">show()</button>
    <button type="button" class="btn btn-surface btn-sm" id="tooltip-api-hide">hide()</button>
    <button type="button" class="btn btn-surface btn-sm" id="tooltip-api-toggle">toggle()</button>
    <button type="button" class="btn btn-surface btn-sm" id="tooltip-api-dispose">dispose()</button>
    <button type="button" class="btn btn-surface btn-sm" id="tooltip-api-init">init()</button>
  </div>
  <div class="d-flex gap-3 mt-4" style="flex-wrap: wrap">
    <label><input type="checkbox" id="tooltip-api-prevent-show"> Отменять show</label>
    <label><input type="checkbox" id="tooltip-api-prevent-hide"> Отменять hide</label>
  </div>
  <pre id="tooltip-api-log" class="tooltip-demo-log" aria-live="polite">Журнал событий</pre>
</div>
JavaScript
/**
 * Описание: ручное управление экземпляром VGTooltip.
 * Возможности: методы, повторная инициализация и отмена событий show/hide.
 */
import { VGTooltip } from 'vgapp';

function initTooltipDemo() {
  const root = document.querySelector('#tooltip-api-demo');
  if (!root || root.dataset.ready) return;
  root.dataset.ready = 'true';
  const target = root.querySelector('#tooltip-api-target');
  const log = root.querySelector('#tooltip-api-log');
  const write = message => {
    log.textContent = (log.textContent + '\n' + message).split('\n').slice(-10).join('\n');
  };
  const init = () => {
    VGTooltip.getOrCreateInstance(target, {
      closeOnOutsideClick: false,
      closeOther: false,
      keyboard: false,
      delay: { show: 0, hide: 0 },
      animation: { enable: false, delay: 0 }
    });
    write('init: экземпляр готов');
  };
  ['show', 'shown', 'hide', 'hidden'].forEach(name => {
    target.addEventListener('vg.tooltip.' + name, event => {
      const prevent = root.querySelector('#tooltip-api-prevent-' + name);
      if (prevent?.checked) {
        event.preventDefault();
        write(name + ': отменено');
      } else {
        write(name);
      }
    });
  });
  ['show', 'hide', 'toggle', 'dispose'].forEach(method => {
    root.querySelector('#tooltip-api-' + method).addEventListener('click', () => {
      const instance = VGTooltip.getInstance(target);
      if (!instance) { write('Сначала нажмите init()'); return; }
      instance[method]();
      if (method === 'dispose') write('dispose: экземпляр удалён, title восстановлен');
    });
  });
  root.querySelector('#tooltip-api-init').addEventListener('click', init);
  init();
}

initTooltipDemo();
CSS
.tooltip-demo-log {
  margin-top: 1rem;
  padding: 1rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: var(--vg-bg-secondary);
  color: var(--vg-text-primary);
  border: 1px solid var(--vg-border-default);
  border-radius: var(--vg-radius-lg);
}

Динамический DOM и новый текст

Добавленная кнопка сразу работает через делегированный Data API. Удалите её при открытой подсказке: модуль сам удалит overlay. Замена текста выполнена через dispose и повторную инициализацию — отдельного setContent/update API нет.

Нажмите «Добавить триггер».

HTML
<div id="tooltip-dynamic-demo">
<div class="d-flex gap-3" style="flex-wrap: wrap">
    <button type="button" class="btn btn-primary btn-sm" id="tooltip-dynamic-add">Добавить триггер</button>
    <button type="button" class="btn btn-surface btn-sm" id="tooltip-dynamic-update">Изменить текст</button>
    <button type="button" class="btn btn-surface btn-sm" id="tooltip-dynamic-remove">Удалить триггер</button>
  </div>
  <div id="tooltip-dynamic-slot" class="mt-4"></div>
  <p id="tooltip-dynamic-status" class="mt-4" role="status">Нажмите «Добавить триггер».</p>
</div>
JavaScript
/**
 * Описание: динамические триггеры VGTooltip.
 * Возможности: делегированный Data API, замена текста и очистка при удалении DOM.
 */
import { VGTooltip } from 'vgapp';

function initTooltipDemo() {
  const root = document.querySelector('#tooltip-dynamic-demo');
  if (!root || root.dataset.ready) return;
  root.dataset.ready = 'true';
  const slot = root.querySelector('#tooltip-dynamic-slot');
  const status = root.querySelector('#tooltip-dynamic-status');
  let revision = 1;
  root.querySelector('#tooltip-dynamic-add').addEventListener('click', () => {
    if (slot.firstElementChild) { status.textContent = 'Триггер уже добавлен.'; return; }
    const target = document.createElement('button');
    target.type = 'button';
    target.className = 'btn btn-surface';
    target.textContent = 'Открыть динамическую подсказку';
    target.setAttribute('data-vg-toggle', 'tooltip');
    target.setAttribute('data-trigger', 'click');
    target.setAttribute('data-params', '{"closeOnOutsideClick":false,"closeOther":false}');
    target.setAttribute('data-vg-title', 'Версия текста: ' + revision);
    slot.append(target);
    status.textContent = 'Триггер добавлен без ручной инициализации.';
  });
  root.querySelector('#tooltip-dynamic-update').addEventListener('click', () => {
    const target = slot.firstElementChild;
    if (!target) { status.textContent = 'Сначала добавьте триггер.'; return; }
    VGTooltip.getInstance(target)?.dispose();
    target.setAttribute('data-vg-title', 'Версия текста: ' + (++revision));
    VGTooltip.getOrCreateInstance(target).show();
    status.textContent = 'Новый экземпляр показывает версию ' + revision + '.';
  });
  root.querySelector('#tooltip-dynamic-remove').addEventListener('click', () => {
    const target = slot.firstElementChild;
    if (!target) { status.textContent = 'Триггер уже удалён.'; return; }
    // Открытый экземпляр сам обнаружит удаление через MutationObserver.
    // Неоткрытый экземпляр освобождаем явно перед удалением.
    if (!target.classList.contains('show')) VGTooltip.getInstance(target)?.dispose();
    target.remove();
    status.textContent = 'Триггер удалён. Открытая подсказка очищается модулем.';
  });
}

initTooltipDemo();
CSS
/* Дополнительные стили не требуются. */

Справочник VGTooltip

Публичный контракт текущего исходника.

Данные и запуск

title: '', content: '', html: false, popover: false
Непустой title обязателен. Приоритет текста: параметр title, data-vg-title, сохранённый title. Для content доступен параметр content и алиас data-vg-content. HTML вставляется без санитизации.
trigger: 'hover'
Data API поддерживает hover, click и строку 'hover click'. data-vg-toggle="popover" принудительно выбирает click. Автоматического focus-триггера нет. Без data-vg-toggle конструктор создаёт экземпляр, но показ вызывается вручную.
Data API
Общие параметры: data-trigger, data-placement, data-html, data-container, data-delay-show, data-delay-hide, data-custom-class, data-animation-enable. CamelCase и массивы передавайте через data-params='{"closeOther":false,"offset":[0,16]}'. Параметры считываются при создании экземпляра; изменение атрибутов не является универсальным update API.
delay: {show: 100, hide: 100}
Миллисекунды до события и начала перехода. Уход указателя отменяет ожидающий показ.
closeOther: true, closeOnOutsideClick: true, keyboard: true
Закрытие других подсказок, клик вне click/popover и Esc. Глобальные обработчики работают с триггерами data-vg-toggle="tooltip" / "popover". Для ручного триггера без этих атрибутов закрытие обеспечивает вызывающий код.

Геометрия и анимация

placement: 'top', container: 'body', offset: [8, 8]
Сторона с необязательным -start / -end; container — CSS-селектор существующего узла. Для локального контейнера задайте position: relative. Смещения задаются по X/Y; выравнивание и сторона определяют, как они применяются.
autoFlip: true, overflowProtection: true, fallbackPlacements: ['bottom', 'right', 'left']
Выбор доступной стороны и ограничение координат окном. В VGTooltip clamp включён всегда; overflowProtection: false сам по себе не отключает ограничение координат. Реальная сторона записывается в data-vg-placement всплывающего элемента.
arrow.padding: 8, custom.class: ''
Отступ центра стрелки от краёв и дополнительный класс на .vg-tooltip.
animation: {enable: true, in: 'animate__backInUp', out: 'animate__backOutDown', delay: 300, effect: 'none'}
Классы анимаций предоставляет animate.css (подключён на демо-сайте). animation.delay — ожидание завершения перехода, отдельно от delay.show/hide. Собственный SCSS tooltip фиксирует transform; не следует ожидать движения от transform-анимаций. Для минимального перехода задайте enable: false и delay: 0.

Методы, события и ограничения

getInstance(element), getOrCreateInstance(element, params), new VGTooltip(element, params)
Получение или создание экземпляра. getOrCreateInstance не меняет настройки уже существующего экземпляра.
show(relatedTarget), hide(), toggle(relatedTarget), dispose()
Показ и скрытие асинхронны. dispose немедленно удаляет overlay, очищает таймеры и наблюдатель, восстанавливает title; не вызывает hidden. После dispose можно создать новый экземпляр.
vg.tooltip.show / shown / hide / hidden
DOM-события на триггере; show и hide можно отменить через preventDefault(). show/shown передают event.relatedTarget. Callbacks в параметрах не предусмотрены.
Доступность и DOM
Tooltip имеет role="tooltip", popover — role="dialog"; триггер получает aria-describedby. Popover не является полноценным диалогом: нет focus trap, а pointer-events: none исключает взаимодействие с содержимым. Disabled-триггер не открывается; без заголовка overlay не создаётся. При удалении открытого триггера экземпляр очищается автоматически. Закрытые экземпляры перед удалением освобождайте через dispose(). Встроенных AJAX, loading/error и setContent нет.

CSS-переменные

Переопределяйте на .vg-tooltip.ваш-класс: overlay по умолчанию находится в body, поэтому переменные только на кнопке до него не наследуются. Префикс всех переменных — --vg-tooltip-.

Геометрия
zindex: 1080; max-width: 260px; padding: 8px 12px; border-radius: 6px; arrow-width / arrow-height: 8px.
Текст
font-size: 13px; line-height: 1.4; word-break: break-word; color — инверсный цвет текста темы.
Поверхность
background — инверсный фон темы; box-shadow — тень поверхности; border-color — разделитель заголовка popover. Точное оформление связано с темой через публичный SCSS-адаптер vgapp/theme и общий data-theme, без отдельного состояния VGTooltip.