let index_notif = 0;
const Notification = {
_container: null,
_timeout: 5000,
_maxVisible: 5,
init({ timeout = 4000, maxVisible = 5, position = 'top-right' } = {}) {
this._timeout = timeout;
this._maxVisible = maxVisible;
// если уже инициализировано — обновляем настройки
if (!this._container) {
this._container = document.createElement('div');
this._container.className = 'app-notification-container';
this._container.setAttribute('data-position', position);
document.body.appendChild(this._container);
// стили, вставляем один раз
const style = document.createElement('style');
style.textContent = `
.app-notification-container {
position: fixed;
z-index: 9999;
pointer-events: none;
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
}
.app-notification-container[data-position="top-right"] { top: 8px; right: 8px; align-items: flex-end; }
.app-notification-container[data-position="top-left"] { top: 8px; left: 8px; align-items: flex-start; }
.app-notification-container[data-position="bottom-right"] { bottom: 8px; right: 8px; align-items: flex-end; }
.app-notification {
pointer-events: auto;
min-width: 220px;
max-width: 360px;
background: #111;
color: #fff;
padding: 10px 12px 10px 12px;
border-radius: var(--border-radius);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
font-size: var(--FontSize2);
line-height: 1.2;
display: flex;
gap: 10px;
align-items: center;
opacity: 0;
transform: translateY(-6px) scale(0.995);
transition: opacity .18s ease, transform .18s ease;
}
.app-notification.show {
opacity: 0.95;
transform: translateY(0) scale(1);
}
.app-notification .icon {
font-size: 18px;
width: 22px;
height: 22px;
display:flex;
align-items:center;
justify-content:center;
border-radius: calc(var(--border-radius) - 5px);
padding: 8px;
}
.app-notification .icon svg{
width: 20px;
height: 20px;
fill: #fff;
}
.app-notification .body { flex:1; }
.app-notification .title { font-weight: 600; margin-bottom: 4px; font-size: 13px; }
.app-notification .blockClose {
width: 20px;
}
.app-notification .blockClose .close {
position: absolute;
right: 10px;
top: 10px;
margin-left: 8px;
background: transparent;
border: none;
color: inherit;
cursor: pointer;
}
.app-notification .blockClose .close svg {
width: 15px;
height: 15px;
fill: #fff;
opacity: 0.8;
}
.app-notification.info {
background: var(--ColorThemes3);
color: var(--ColorThemes0);
}
.app-notification.info .icon { background: var(--ColorThemes0); }
.app-notification.info .icon svg{fill: var(--ColorThemes3);}
.app-notification.info .close svg{fill: var(--ColorThemes0);}
.app-notification.success { background: #52ac56; }
.app-notification.success .icon { background: #6dc450; }
.app-notification.warn { background: #d18515; }
.app-notification.warn .icon { background: #eaad57; }
.app-notification.error { background: #9c2424; }
.app-notification.error .icon { background: #c45050; }
@media (max-width: 700px) {
.app-notification-container {
width: calc(100% - 33px);
}
.app-notification {
width: calc(100% - 30px);
border-radius: var(--border-radius);
}
}
`;
document.head.appendChild(style);
} else {
this._container.setAttribute('data-position', position);
}
},
/**
* show(message, options)
* message: string or { title, text }
* options: { type: 'info'|'success'|'warn'|'error', timeout: ms, title: string }
*/
// show(message, options = {}) {
// if (!this._container) this.init();
// const { type = 'info', timeout = this._timeout, title } = options;
// const content = typeof message === 'string'
// ? { title: title || '', text: message }
// : message;
// // 🔥 УДАЛЯЕМ ЛИШНИЕ СРАЗУ
// while (this._container.children.length >= this._maxVisible) {
// const first = this._container.firstElementChild;
// if (first) first.remove(); // ← никаких переходов и setTimeout
// else break;
// }
// const node = document.createElement('div');
// node.className = `app-notification ${type}`;
// // иконка (можно менять на svg)
// const icons = { info: 'ℹ️', success: '✅', warn: '⚠️', error: '❌' };
// const icon = document.createElement('div');
// icon.className = 'icon';
// icon.innerHTML = `
${icons[type] || icons.info}
`;
// const body = document.createElement('div');
// body.className = 'body';
// if (content.title) {
// const t = document.createElement('div');
// t.className = 'title';
// t.textContent = content.title;
// body.appendChild(t);
// }
// const txt = document.createElement('div');
// txt.className = 'text';
// txt.textContent = content.text || '';
// body.appendChild(txt);
// const closeBtn = document.createElement('button');
// closeBtn.className = 'close';
// closeBtn.setAttribute('aria-label', 'Закрыть уведомление');
// closeBtn.innerHTML = '';
// node.appendChild(icon);
// node.appendChild(body);
// node.appendChild(closeBtn);
// // вставляем в контейнер в конец (новые сверху — ставим в конец, контейнер flex column)
// this._container.appendChild(node);
// requestAnimationFrame(() => node.classList.add('show'));
// let timer = null;
// const startTimer = () => {
// if (timeout === 0) return;
// timer = setTimeout(() => this._removeNode(node), timeout);
// };
// const clearTimer = () => { if (timer) { clearTimeout(timer); timer = null; } };
// node.addEventListener('mouseenter', clearTimer);
// node.addEventListener('mouseleave', startTimer);
// node.querySelector('.close').addEventListener('click', () => {
// this._removeNode(node);
// });
// startTimer();
// return node;
// },
// удалить ноду с анимацией
show(message, options = {}) {
if (!this._container) this.init();
const {
type = 'info',
timeout = this._timeout,
title,
onClick,
lock
} = options;
const content = typeof message === 'string'
? { title: title || '', text: message }
: message;
// Удаляем лишние уведомления
while (this._container.children.length >= this._maxVisible) {
const first = this._container.firstElementChild;
if (first) first.remove();
else break;
}
const node = document.createElement('div');
node.className = `app-notification ${type}`;
if (onClick) node.style.cursor = "pointer"
// Иконка
const icons = {
info: ``,
success: ``,
warn: ``,
error: ``
};
const icon = document.createElement('div');
icon.className = 'icon';
icon.innerHTML = icons[type] || icons.info;
// Тело уведомления
const body = document.createElement('div');
body.className = 'body';
if (content.title) {
const t = document.createElement('div');
t.className = 'title';
t.textContent = content.title;
body.appendChild(t);
}
const txt = document.createElement('div');
txt.className = 'text';
txt.textContent = content.text || '';
body.appendChild(txt);
node.appendChild(icon);
node.appendChild(body);
// Кнопка закрытия, только если onClick не задан
if (!onClick && !lock) {
const closeDiv = document.createElement('div');
closeDiv.className = 'blockClose';
node.appendChild(closeDiv);
const closeBtn = document.createElement('button');
closeBtn.className = 'close';
closeBtn.setAttribute('aria-label', 'Закрыть уведомление');
closeBtn.innerHTML = '';
closeDiv.appendChild(closeBtn);
closeBtn.addEventListener('click', () => this._removeNode(node));
}
this._container.appendChild(node);
requestAnimationFrame(() => node.classList.add('show'));
let timer = null;
const startTimer = () => {
if (timeout === 0) return;
timer = setTimeout(() => this._removeNode(node), timeout);
};
const clearTimer = () => { if (timer) { clearTimeout(timer); timer = null; } };
node.addEventListener('mouseenter', clearTimer);
node.addEventListener('mouseleave', startTimer);
// Обработчик клика для onClick
if (typeof onClick === 'function') {
node.addEventListener('click', () => {
try { onClick(); } catch (e) { }
this._removeNode(node);
});
}
startTimer();
return node;
},
_removeNode(node) {
if (!node || !node.parentElement) return;
node.classList.remove('show');
// дождемся конца transition (примерно 180ms), потом удалим
setTimeout(() => {
if (node && node.parentElement) node.parentElement.removeChild(node);
}, 200);
},
// очистить все уведомления
clearAll() {
if (!this._container) return;
Array.from(this._container.children).forEach(n => this._removeNode(n));
},
// удобные обёртки
info(message, opts = {}) { return this.show(message, { ...opts, type: 'info' }); },
success(message, opts = {}) { return this.show(message, { ...opts, type: 'success' }); },
warn(message, opts = {}) { return this.show(message, { ...opts, type: 'warn' }); },
error(message, opts = {}) { return this.show(message, { ...opts, type: 'error' }); },
click(message, opts = {}) { return this.show(message, { ...opts, onClick: opts.f }); },
// изменить глобальные настройки во время работы
configure({ timeout, maxVisible, position } = {}) {
if (typeof timeout === 'number') this._timeout = timeout;
if (typeof maxVisible === 'number') this._maxVisible = maxVisible;
if (position && this._container) this._container.setAttribute('data-position', position);
}
};
// Пример использования:
// Notification.init({ timeout: 4000, maxVisible: 4 });
// Notification.success('Готово!', { title: 'Успех' }, { timeout: 8000 });
// Notification.error({ title: 'Ошибка', text: 'Не удалось сохранить' }, { timeout: 8000 });
// Notification.info('Информационное сообщение');
// Notification.clearAll();
// Notification.success({ title: `Metrics`, text: `З'єднання встановлено` }, { timeout: 0 });
// Notification.error({ title: `Metrics`, text: `З'єднання встановлено` }, { timeout: 0 });
// Notification.info({ title: `Metrics`, text: `lore` }, { timeout: 0 });
// Notification.warn({ title: `Metrics`, text: `З'єднання встановлено` }, { timeout: 0 });
// Notification.click({ title: `Test`, text: `Натисни, щоб перезавантажити` }, { type: 'info', f: () => Notification.clearAll(), timeout: 0 });