let _ttDiv = null;
function _getTooltip() {
if (!_ttDiv) {
_ttDiv = document.createElement('div');
_ttDiv.id = 'nm-tooltip';
document.body.appendChild(_ttDiv);
}
return _ttDiv;
}
function _positionTooltip(tt, anchor) {
const r = anchor.getBoundingClientRect();
const tw = tt.offsetWidth, th = tt.offsetHeight;
let left = r.right + 8;
let top = r.top - 4;
if (left + tw > window.innerWidth - 10) left = r.left - tw - 8;
if (top + th > window.innerHeight - 10) top = window.innerHeight - th - 10;
if (top < 10) top = 10;
tt.style.left = left + 'px';
tt.style.top = top + 'px';
}
document.addEventListener('click', e => {
const btn = e.target.closest('.nm-copy-btn');
if (!btn) return;
navigator.clipboard.writeText(btn.dataset.copy ?? '').then(() => {
const prev = btn.textContent;
btn.textContent = '✓';
setTimeout(() => { btn.textContent = prev; }, 1200);
});
});
document.addEventListener('click', e => {
const card = e.target.closest('.mn-card');
if (!card) return;
const shift = e.shiftKey, src = window.isOpenSrcClick?.(e);
if (shift || src) {
if (!card.classList.contains('diag-ext')) {
if (src) {
const level = document.querySelector('.view.active')?.dataset.view;
const node = card.dataset.nodeId && level
? window.activeGraph?.(level)?.nodes?.find(n => n.id === card.dataset.nodeId) : null;
const url = node && window.nodeSourceUrl?.(node, level);
if (url) window.open(url, '_blank', 'noopener');
} else { const cb = document.getElementById('node-modal-cb');
if (cb) { cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); }
}
}
return; }
const lbl = e.target.closest('.mn-copy');
if (!lbl || lbl.dataset.copy == null) return;
const val = lbl.dataset.copy;
navigator.clipboard.writeText(val).then(() => {
const preview = card.querySelector('.mn-copied-val');
if (preview) preview.textContent = val.length > 48 ? val.slice(0, 47) + '…' : val;
card.classList.add('copied');
setTimeout(() => card.classList.remove('copied'), 1000);
});
});
document.addEventListener('mouseover', e => {
const h = e.target.closest('.nm-has-hint');
if (h && h.dataset.nmHint) {
const tt = _getTooltip();
tt.textContent = `${h.textContent.trim()}: ${h.dataset.nmHint}`;
tt.style.display = 'block';
requestAnimationFrame(() => _positionTooltip(tt, h));
} else if (!e.target.closest('#nm-tooltip')) {
if (_ttDiv) _ttDiv.style.display = 'none';
}
});
function getModal() {
let overlay = document.getElementById('node-modal-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'node-modal-overlay';
overlay.innerHTML =
'<div id="node-modal">' +
'<div id="node-modal-hdr">' +
'<div id="node-modal-hdr-title"></div>' +
'<button id="node-modal-close" title="Close">✕</button>' +
'</div>' +
'<div id="node-modal-main">' +
'<div id="node-modal-body"></div>' +
'<div id="node-modal-diagram"></div>' +
'</div>' +
'</div>';
document.body.appendChild(overlay);
document.getElementById('node-modal-close').addEventListener('click', closeModal);
overlay.addEventListener('mousedown', e => { if (e.target === overlay) closeModal(); });
document.addEventListener('keydown', e => {
if (overlay.style.display === 'none') return; if (e.key === 'Escape') { closeModal(); return; }
if (e.key === ' ' || e.code === 'Space') {
const cb = document.getElementById('node-modal-cb');
if (cb) {
e.preventDefault();
cb.checked = !cb.checked;
cb.dispatchEvent(new Event('change'));
}
}
});
document.getElementById('node-modal-diagram').addEventListener('click', e => {
const g = e.target.closest('[data-diag-node]');
if (!g) return;
const nodeId = g.dataset.diagNode;
const level = document.querySelector('.view.active')?.dataset.view;
if (!nodeId || !level) return;
const isExt = g.classList.contains('diag-ext');
const node = window.activeGraph?.(level)?.nodes?.find(n => n.id === nodeId);
if (!isExt && node && window.isOpenSrcClick?.(e)) {
const centralId = window._modalNode?.id;
const line = centralId ? window.connSourceLine?.(nodeId, centralId, level) : null;
const url = window.nodeSourceUrl?.(node, level, line);
if (url) window.open(url, '_blank', 'noopener');
return;
}
if (!isExt && node && e.shiftKey) {
const section = document.querySelector('.view.active');
window.toggleNodeSelected?.(node, level, section);
return;
}
if (isExt && (e.shiftKey || window.isOpenSrcClick?.(e))) return;
if (window.openModalForNode?.(nodeId, level)) window.navPush?.(level, nodeId);
});
}
return overlay;
}
function setModalDiagram(html) {
const d = document.getElementById('node-modal-diagram');
if (!d) return;
d.innerHTML = `<div class="nm-diagram-scroll">${html}</div>`;
const hints = document.createElement('div');
hints.className = 'kbd-hints';
hints.id = 'node-modal-hints';
hints.innerHTML = window.kbdHintsHtml?.() ?? '';
d.appendChild(hints);
centerDiagramNode(d);
}
function centerDiagramNode(d) {
requestAnimationFrame(() => {
const sc = d.querySelector('.nm-diagram-scroll') || d; const svg = sc.querySelector('svg');
const frac = svg && parseFloat(svg.dataset.nodeCy || '');
if (!svg || !isFinite(frac)) return;
const svgRect = svg.getBoundingClientRect();
const contRect = sc.getBoundingClientRect();
const nodeInContent = (svgRect.top - contRect.top) + sc.scrollTop + frac * svgRect.height;
sc.scrollTop = Math.max(0, nodeInContent - sc.clientHeight / 2);
});
}
window.setModalDiagram = setModalDiagram;
function closeModal() {
window.hideMetricTooltip?.();
window._modalNode = null;
window.flyoutHeader?.unmount('modal');
const overlay = document.getElementById('node-modal-overlay');
if (overlay) overlay.style.display = 'none';
document.body.style.overflow = '';
window.navPush?.(document.querySelector('.view.active')?.dataset.view ?? null, null);
}
function closeModalSilent() {
window.hideMetricTooltip?.();
window.flyoutHeader?.unmount('modal');
const overlay = document.getElementById('node-modal-overlay');
if (overlay) overlay.style.display = 'none';
document.body.style.overflow = '';
}