Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
9bbc4502fc
|
|||
|
9eb2ea5811
|
|||
|
9d5ed83f5b
|
|||
|
|
ccd4fc13a9 | ||
|
|
0fb2638b59 | ||
|
|
eba67fe74f | ||
|
|
690412dc33 | ||
|
|
db9df24786 | ||
|
|
dde1b4f94e |
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,22 +1,39 @@
|
||||
## v6.0.0
|
||||
|
||||
- Корректная работа сообытий mouseenter/mouseleave на тач-устройствах
|
||||
- Предовтращаем лишние показы всплывающей подсказки
|
||||
|
||||
## v5.0.0
|
||||
|
||||
- Регистрируем сообытия mouseenter/mouseleave на тач-устройствах
|
||||
|
||||
## v4.1.1
|
||||
|
||||
- Исправление зависаний (в некоторых случаях) всплывающих подсказок
|
||||
|
||||
## v4.1.0
|
||||
|
||||
- Отключаем события mouseenter/mouseleave для toch-устройств
|
||||
|
||||
## v4.0.0
|
||||
|
||||
- Переработана логика инициализации и обновления тултипа
|
||||
- Добавлены новые триггеры: `focus`, `blur`
|
||||
- Опция `hideOnClick` теперь поддерживает значения: `true`, `'all'`, `'toggle'`
|
||||
- Исправлены ошибки с позиционированием, анимацией и стилями
|
||||
- Оптимизировано управление событиями и очистка ресурсов
|
||||
- Переработана логика инициализации и обновления тултипа
|
||||
- Добавлены новые триггеры: `focus`, `blur`
|
||||
- Опция `hideOnClick` теперь поддерживает значения: `true`, `'all'`, `'toggle'`
|
||||
- Исправлены ошибки с позиционированием, анимацией и стилями
|
||||
- Оптимизировано управление событиями и очистка ресурсов
|
||||
|
||||
## v3.0.1
|
||||
|
||||
- Исправлена работа событий
|
||||
- Исправлена работа событий
|
||||
|
||||
## v3.0.0
|
||||
|
||||
- Исправлено название опции с `virtialReference` на `virtualReference`
|
||||
- Исправлено название опции с `virtialReference` на `virtualReference`
|
||||
|
||||
## v2.1.0
|
||||
|
||||
- Для опции `appendTo` добавлена возможность указать значение `parent`
|
||||
- Для опции `appendTo` добавлена возможность указать значение `parent`
|
||||
|
||||
## v2.0.0
|
||||
|
||||
|
||||
190
index.js
190
index.js
@@ -8,6 +8,23 @@ const attributeToOption = (attribute) => {
|
||||
return attribute.charAt(0).toLowerCase() + attribute.slice(1);
|
||||
};
|
||||
|
||||
let visibilityListenerRegistered = false;
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.hidden) {
|
||||
// Скрываем все активные всплывающие подсказки
|
||||
for (const $tooltip of document.querySelectorAll('.tooltip')) {
|
||||
const ref = $tooltip._reference;
|
||||
if (ref?._tooltip) {
|
||||
// Очищаем таймеры появления при скрытии страницы
|
||||
clearTimeout(ref._tooltip._showTimeout);
|
||||
if (ref._tooltip.isVisible) {
|
||||
ref._tooltip.hide({ immediately: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const createTooltip = ($el, content, options) => {
|
||||
options = {
|
||||
animation: [
|
||||
@@ -41,8 +58,6 @@ export const createTooltip = ($el, content, options) => {
|
||||
}
|
||||
}
|
||||
|
||||
let showTimeout;
|
||||
let hideTimeout;
|
||||
let rafId;
|
||||
const listeners = [];
|
||||
|
||||
@@ -53,6 +68,8 @@ export const createTooltip = ($el, content, options) => {
|
||||
$container: undefined,
|
||||
$arrow: undefined,
|
||||
$interactive: undefined,
|
||||
_showTimeout: undefined,
|
||||
_hideTimeout: undefined,
|
||||
autoUpdateCleanup: () => {},
|
||||
|
||||
setContent(updatedContent) {
|
||||
@@ -150,6 +167,10 @@ export const createTooltip = ($el, content, options) => {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout($el._tooltip._showTimeout);
|
||||
clearTimeout($el._tooltip._hideTimeout);
|
||||
cancelAnimationFrame(rafId);
|
||||
|
||||
$el._tooltip.$tooltip?.remove();
|
||||
|
||||
// Вызываем autoUpdateCleanup только если всплывающая подсказка была видна (иначе вызывать её не имеет смысла)
|
||||
@@ -171,7 +192,7 @@ export const createTooltip = ($el, content, options) => {
|
||||
}
|
||||
|
||||
$el._tooltip.show = async ({ immediately } = {}) => {
|
||||
clearTimeout(hideTimeout);
|
||||
clearTimeout($el._tooltip._hideTimeout);
|
||||
|
||||
if (!$el._tooltip.$tooltip) {
|
||||
const { computePosition, offset, flip, shift, arrow } = await import('@floating-ui/dom');
|
||||
@@ -278,45 +299,47 @@ export const createTooltip = ($el, content, options) => {
|
||||
|
||||
const { autoUpdate } = await import('@floating-ui/dom');
|
||||
|
||||
showTimeout = setTimeout(
|
||||
$el._tooltip._showTimeout = setTimeout(
|
||||
async () => {
|
||||
if (!$el._tooltip.isVisible) {
|
||||
(options.appendTo === 'parent' ? $el.parentElement : options.appendTo).append(
|
||||
$el._tooltip.$tooltip,
|
||||
);
|
||||
$el._tooltip.isVisible = true;
|
||||
$el._tooltip.autoUpdateCleanup = autoUpdate(
|
||||
$el,
|
||||
$el._tooltip.$tooltip,
|
||||
$el._tooltip.updatePosition,
|
||||
);
|
||||
const isMouseEnterTrigger = options.trigger.includes('mouseenter');
|
||||
const isHovering = $el.matches(':hover');
|
||||
|
||||
if (
|
||||
options.hideOnClick &&
|
||||
(options.trigger.includes('click') || options.trigger.includes('manual'))
|
||||
) {
|
||||
document.body.addEventListener('click', $el._tooltip.hideOnClickListener);
|
||||
listeners.push({
|
||||
el: document.body,
|
||||
event: 'click',
|
||||
listener: $el._tooltip.hideOnClickListener,
|
||||
});
|
||||
}
|
||||
// Проверяем $el._tooltip на сущестование и актуальность показа всплывающей подсказки
|
||||
if (
|
||||
!$el._tooltip ||
|
||||
$el._tooltip.isVisible ||
|
||||
(isMouseEnterTrigger && !isHovering) ||
|
||||
document.hidden
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.onShow) {
|
||||
options.onShow($el._tooltip);
|
||||
}
|
||||
(options.appendTo === 'parent' ? $el.parentElement : options.appendTo).append($el._tooltip.$tooltip);
|
||||
$el._tooltip.isVisible = true;
|
||||
$el._tooltip.autoUpdateCleanup = autoUpdate($el, $el._tooltip.$tooltip, $el._tooltip.updatePosition);
|
||||
|
||||
try {
|
||||
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.hideOnClick && (options.trigger.includes('click') || options.trigger.includes('manual'))) {
|
||||
document.body.addEventListener('click', $el._tooltip.hideOnClickListener);
|
||||
listeners.push({
|
||||
el: document.body,
|
||||
event: 'click',
|
||||
listener: $el._tooltip.hideOnClickListener,
|
||||
});
|
||||
}
|
||||
|
||||
if (options.onShown) {
|
||||
options.onShown($el._tooltip);
|
||||
}
|
||||
if (options.onShow) {
|
||||
options.onShow($el._tooltip);
|
||||
}
|
||||
|
||||
try {
|
||||
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],
|
||||
@@ -324,30 +347,38 @@ export const createTooltip = ($el, content, options) => {
|
||||
};
|
||||
|
||||
$el._tooltip.hide = ({ immediately } = {}) => {
|
||||
clearTimeout(showTimeout);
|
||||
hideTimeout = setTimeout(
|
||||
clearTimeout($el._tooltip._showTimeout);
|
||||
$el._tooltip._hideTimeout = setTimeout(
|
||||
async () => {
|
||||
if ($el._tooltip.isVisible) {
|
||||
if (options.onHide) {
|
||||
options.onHide($el._tooltip);
|
||||
}
|
||||
// Проверяем $el._tooltip на сущестование
|
||||
if (!$el._tooltip || !$el._tooltip.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[1], {
|
||||
duration: immediately ? 0 : options.duration[1],
|
||||
easing: options.easing[1],
|
||||
}).finished;
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
if (options.onHide) {
|
||||
options.onHide($el._tooltip);
|
||||
}
|
||||
|
||||
if ($el._tooltip.$tooltip) {
|
||||
$el._tooltip.$tooltip.remove();
|
||||
}
|
||||
$el._tooltip.isVisible = false;
|
||||
$el._tooltip.autoUpdateCleanup();
|
||||
try {
|
||||
await $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[1], {
|
||||
duration: immediately ? 0 : options.duration[1],
|
||||
easing: options.easing[1],
|
||||
}).finished;
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
|
||||
if (options.onHidden) {
|
||||
options.onHidden($el._tooltip);
|
||||
}
|
||||
// Ещё одна проверка на сущестование $el._tooltip после await
|
||||
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],
|
||||
@@ -377,21 +408,34 @@ export const createTooltip = ($el, content, options) => {
|
||||
};
|
||||
|
||||
$el._tooltip.clickListener = () => {
|
||||
if (document.hidden) {
|
||||
return;
|
||||
}
|
||||
if (!$el._tooltip.isVisible) {
|
||||
$el._tooltip.show();
|
||||
}
|
||||
};
|
||||
|
||||
$el._tooltip.mouseEnterListener = () => {
|
||||
$el._tooltip.mouseEnterListener = (event) => {
|
||||
if (event.pointerType !== 'mouse' || document.hidden) {
|
||||
return;
|
||||
}
|
||||
$el._tooltip.show();
|
||||
};
|
||||
|
||||
$el._tooltip.mouseLeaveListener = ({ relatedTarget }) => {
|
||||
if (options.interactive && relatedTarget && $el._tooltip.$tooltip?.contains(relatedTarget)) {
|
||||
$el._tooltip.mouseLeaveListener = (event) => {
|
||||
if (event.pointerType !== 'mouse') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.interactive && event.relatedTarget && $el._tooltip.$tooltip?.contains(event.relatedTarget)) {
|
||||
$el._tooltip.$tooltip.addEventListener(
|
||||
'mouseleave',
|
||||
({ relatedTarget }) => {
|
||||
if (!relatedTarget || !$el.contains(relatedTarget)) {
|
||||
'pointerleave',
|
||||
(event) => {
|
||||
if (event.pointerType !== 'mouse') {
|
||||
return;
|
||||
}
|
||||
if (!event.relatedTarget || !$el.contains(event.relatedTarget)) {
|
||||
$el._tooltip.hide();
|
||||
}
|
||||
},
|
||||
@@ -403,6 +447,9 @@ export const createTooltip = ($el, content, options) => {
|
||||
};
|
||||
|
||||
$el._tooltip.focusListener = () => {
|
||||
if (document.hidden) {
|
||||
return;
|
||||
}
|
||||
$el._tooltip.show();
|
||||
};
|
||||
|
||||
@@ -414,11 +461,11 @@ export const createTooltip = ($el, content, options) => {
|
||||
for (const trigger of options.trigger.split(' ')) {
|
||||
switch (trigger) {
|
||||
case 'mouseenter': {
|
||||
$el.addEventListener('mouseenter', $el._tooltip.mouseEnterListener);
|
||||
listeners.push({ el: $el, event: 'mouseenter', listener: $el._tooltip.mouseEnterListener });
|
||||
$el.addEventListener('pointerenter', $el._tooltip.mouseEnterListener);
|
||||
listeners.push({ el: $el, event: 'pointerenter', listener: $el._tooltip.mouseEnterListener });
|
||||
|
||||
$el.addEventListener('mouseleave', $el._tooltip.mouseLeaveListener);
|
||||
listeners.push({ el: $el, event: 'mouseleave', listener: $el._tooltip.mouseLeaveListener });
|
||||
$el.addEventListener('pointerleave', $el._tooltip.mouseLeaveListener);
|
||||
listeners.push({ el: $el, event: 'pointerleave', listener: $el._tooltip.mouseLeaveListener });
|
||||
break;
|
||||
}
|
||||
case 'click': {
|
||||
@@ -426,8 +473,8 @@ export const createTooltip = ($el, content, options) => {
|
||||
listeners.push({ el: $el, event: 'click', listener: $el._tooltip.clickListener });
|
||||
|
||||
if (!options.interactive) {
|
||||
$el.addEventListener('mouseleave', $el._tooltip.mouseLeaveListener);
|
||||
listeners.push({ el: $el, event: 'mouseleave', listener: $el._tooltip.mouseLeaveListener });
|
||||
$el.addEventListener('pointerleave', $el._tooltip.mouseLeaveListener);
|
||||
listeners.push({ el: $el, event: 'pointerleave', listener: $el._tooltip.mouseLeaveListener });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -446,4 +493,9 @@ export const createTooltip = ($el, content, options) => {
|
||||
};
|
||||
|
||||
registerListeners();
|
||||
|
||||
if (!visibilityListenerRegistered) {
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
visibilityListenerRegistered = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@advdominion/tooltip",
|
||||
"version": "4.0.0",
|
||||
"version": "6.0.0",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@4.9.4",
|
||||
"main": "index.js",
|
||||
|
||||
Reference in New Issue
Block a user