fix(assistant): тур не залипает на нижних шагах

Подсказка тура уезжала за край на нижних пунктах сайдбара (Питомец), а оверлей
ловил клики → «ничего не сделать». Теперь: scrollIntoView ДО замера позиции,
подсказка жёстко клампится в пределах экрана (меряем реальный размер), и клик
по затемнённому фону закрывает тур (escape-hatch вдобавок к Esc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-04 16:53:33 +03:00
parent aff07647ec
commit 2f3fd7475b
+27 -17
View File
@@ -337,6 +337,7 @@
document.body.appendChild(ov); document.body.appendChild(ov);
var ring = ov.querySelector('.asst-tour-ring'); var ring = ov.querySelector('.asst-tour-ring');
var tip = ov.querySelector('.asst-tour-tip'); var tip = ov.querySelector('.asst-tour-tip');
ov.addEventListener('click', function (e) { if (e.target === ov) finish(); }); // клик по фону — выход
function finish() { function finish() {
ov.remove(); ov.remove();
window.removeEventListener('resize', render); window.removeEventListener('resize', render);
@@ -347,23 +348,11 @@
function render() { function render() {
var st = steps[i]; var st = steps[i];
var el = st.sel ? document.querySelector(st.sel) : null; var el = st.sel ? document.querySelector(st.sel) : null;
// сперва проматываем элемент в зону видимости, ПОТОМ меряем (иначе позиция стухшая)
if (el && vis(el) && el.scrollIntoView) { try { el.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch (e) {} }
var r = el && vis(el) ? el.getBoundingClientRect() : null; var r = el && vis(el) ? el.getBoundingClientRect() : null;
if (r) {
var pad = 6; // контент сначала — чтобы измерить реальный размер подсказки и привязать кнопки
ring.style.display = 'block';
ring.style.left = (r.left - pad) + 'px'; ring.style.top = (r.top - pad) + 'px';
ring.style.width = (r.width + pad * 2) + 'px'; ring.style.height = (r.height + pad * 2) + 'px';
ov.style.background = '';
var tx = r.right + 14, ty = Math.max(12, r.top);
if (tx + 290 > window.innerWidth) tx = Math.max(12, r.left - 294); // показать слева, если справа не влезает
tip.style.left = Math.min(tx, window.innerWidth - 290) + 'px';
tip.style.top = Math.min(ty, window.innerHeight - 160) + 'px';
tip.style.transform = '';
} else {
ring.style.display = 'none';
ov.style.background = 'rgba(15,12,30,.55)';
tip.style.left = '50%'; tip.style.top = '50%'; tip.style.transform = 'translate(-50%,-50%)';
}
tip.innerHTML = tip.innerHTML =
'<div class="asst-tour-h">' + esc(st.title) + '</div>' + '<div class="asst-tour-h">' + esc(st.title) + '</div>' +
'<div class="asst-tour-t">' + esc(st.text) + '</div>' + '<div class="asst-tour-t">' + esc(st.text) + '</div>' +
@@ -375,7 +364,28 @@
tip.querySelector('[data-a="next"]').onclick = function () { if (i === steps.length - 1) finish(); else { i++; render(); } }; tip.querySelector('[data-a="next"]').onclick = function () { if (i === steps.length - 1) finish(); else { i++; render(); } };
var b = tip.querySelector('[data-a="back"]'); if (b) b.onclick = function () { i--; render(); }; var b = tip.querySelector('[data-a="back"]'); if (b) b.onclick = function () { i--; render(); };
tip.querySelector('[data-a="skip"]').onclick = finish; tip.querySelector('[data-a="skip"]').onclick = finish;
if (el && el.scrollIntoView) { try { el.scrollIntoView({ block: 'nearest' }); } catch (e) {} }
// позиционирование: подсказка ВСЕГДА целиком в пределах экрана
var W = window.innerWidth, H = window.innerHeight;
var tw = tip.offsetWidth || 280, th = tip.offsetHeight || 150;
tip.style.transform = '';
if (r) {
var pad = 6;
ring.style.display = 'block';
ring.style.left = (r.left - pad) + 'px'; ring.style.top = (r.top - pad) + 'px';
ring.style.width = (r.width + pad * 2) + 'px'; ring.style.height = (r.height + pad * 2) + 'px';
ov.style.background = '';
var tx = r.right + 14;
if (tx + tw > W - 12) tx = r.left - tw - 14; // не влезает справа — показать слева
tx = Math.max(12, Math.min(tx, W - tw - 12));
var ty = Math.max(12, Math.min(r.top, H - th - 12));
tip.style.left = tx + 'px'; tip.style.top = ty + 'px';
} else {
ring.style.display = 'none';
ov.style.background = 'rgba(15,12,30,.55)';
tip.style.left = Math.max(12, (W - tw) / 2) + 'px';
tip.style.top = Math.max(12, (H - th) / 2) + 'px';
}
} }
window.addEventListener('resize', render); window.addEventListener('resize', render);
document.addEventListener('keydown', onKey); document.addEventListener('keydown', onKey);