Исправление зависаний (в некоторых случаях) всплывающих подсказок

This commit is contained in:
Valentin Silyutin
2025-10-16 00:04:57 +04:00
parent db9df24786
commit 690412dc33

125
index.js
View File

@@ -10,6 +10,18 @@ const attributeToOption = (attribute) => {
const isTouchDevice = () => 'ontouchstart' in window || navigator.maxTouchPoints > 0; const isTouchDevice = () => 'ontouchstart' in window || navigator.maxTouchPoints > 0;
let visibilityListenerRegistered = false;
const handleVisibilityChange = () => {
if (document.hidden) {
// Скрываем все активные всплывающие подсказки
for (const $tooltip of document.querySelectorAll('.tooltip')) {
if ($tooltip._reference?._tooltip?.isVisible) {
$tooltip._reference._tooltip.hide({ immediately: true });
}
}
}
};
export const createTooltip = ($el, content, options) => { export const createTooltip = ($el, content, options) => {
options = { options = {
animation: [ animation: [
@@ -152,6 +164,10 @@ export const createTooltip = ($el, content, options) => {
return; return;
} }
clearTimeout(showTimeout);
clearTimeout(hideTimeout);
cancelAnimationFrame(rafId);
$el._tooltip.$tooltip?.remove(); $el._tooltip.$tooltip?.remove();
// Вызываем autoUpdateCleanup только если всплывающая подсказка была видна (иначе вызывать её не имеет смысла) // Вызываем autoUpdateCleanup только если всплывающая подсказка была видна (иначе вызывать её не имеет смысла)
@@ -282,43 +298,37 @@ export const createTooltip = ($el, content, options) => {
showTimeout = setTimeout( showTimeout = setTimeout(
async () => { async () => {
if (!$el._tooltip.isVisible) { // Проверяем $el._tooltip на сущестование
(options.appendTo === 'parent' ? $el.parentElement : options.appendTo).append( if (!$el._tooltip || $el._tooltip.isVisible) {
$el._tooltip.$tooltip, return;
); }
$el._tooltip.isVisible = true;
$el._tooltip.autoUpdateCleanup = autoUpdate(
$el,
$el._tooltip.$tooltip,
$el._tooltip.updatePosition,
);
if ( (options.appendTo === 'parent' ? $el.parentElement : options.appendTo).append($el._tooltip.$tooltip);
options.hideOnClick && $el._tooltip.isVisible = true;
(options.trigger.includes('click') || options.trigger.includes('manual')) $el._tooltip.autoUpdateCleanup = autoUpdate($el, $el._tooltip.$tooltip, $el._tooltip.updatePosition);
) {
document.body.addEventListener('click', $el._tooltip.hideOnClickListener);
listeners.push({
el: document.body,
event: 'click',
listener: $el._tooltip.hideOnClickListener,
});
}
if (options.onShow) { if (options.hideOnClick && (options.trigger.includes('click') || options.trigger.includes('manual'))) {
options.onShow($el._tooltip); document.body.addEventListener('click', $el._tooltip.hideOnClickListener);
} listeners.push({
el: document.body,
event: 'click',
listener: $el._tooltip.hideOnClickListener,
});
}
try { if (options.onShow) {
await $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[0], { options.onShow($el._tooltip);
duration: immediately ? 0 : options.duration[0], }
easing: options.easing[0],
}).finished;
} catch {} // eslint-disable-line no-empty
if (options.onShown) { try {
options.onShown($el._tooltip); await $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[0], {
} duration: immediately ? 0 : options.duration[0],
easing: options.easing[0],
}).finished;
} catch {} // eslint-disable-line no-empty
if (options.onShown) {
options.onShown($el._tooltip);
} }
}, },
immediately ? 0 : options.delay[0], immediately ? 0 : options.delay[0],
@@ -329,27 +339,35 @@ export const createTooltip = ($el, content, options) => {
clearTimeout(showTimeout); clearTimeout(showTimeout);
hideTimeout = setTimeout( hideTimeout = setTimeout(
async () => { async () => {
if ($el._tooltip.isVisible) { // Проверяем $el._tooltip на сущестование
if (options.onHide) { if (!$el._tooltip || !$el._tooltip.isVisible) {
options.onHide($el._tooltip); return;
} }
try { if (options.onHide) {
await $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[1], { options.onHide($el._tooltip);
duration: immediately ? 0 : options.duration[1], }
easing: options.easing[1],
}).finished;
} catch {} // eslint-disable-line no-empty
if ($el._tooltip.$tooltip) { try {
$el._tooltip.$tooltip.remove(); await $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[1], {
} duration: immediately ? 0 : options.duration[1],
$el._tooltip.isVisible = false; easing: options.easing[1],
$el._tooltip.autoUpdateCleanup(); }).finished;
} catch {} // eslint-disable-line no-empty
if (options.onHidden) { // Ещё одна проверка на сущестование $el._tooltip после await
options.onHidden($el._tooltip); if (!$el._tooltip) {
} return;
}
if ($el._tooltip.$tooltip) {
$el._tooltip.$tooltip.remove();
}
$el._tooltip.isVisible = false;
$el._tooltip.autoUpdateCleanup();
if (options.onHidden) {
options.onHidden($el._tooltip);
} }
}, },
immediately ? 0 : options.delay[1], immediately ? 0 : options.delay[1],
@@ -450,4 +468,9 @@ export const createTooltip = ($el, content, options) => {
}; };
registerListeners(); registerListeners();
if (!visibilityListenerRegistered) {
document.addEventListener('visibilitychange', handleVisibilityChange);
visibilityListenerRegistered = true;
}
}; };