diff --git a/base.css b/base.css index d86aa71..8803585 100644 --- a/base.css +++ b/base.css @@ -487,6 +487,7 @@ hr { background: var(--accent-cyan-dim); } .lt-nav-link:hover::after { left: 0; right: 0; box-shadow: var(--glow-cyan); } +.lt-nav-link:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; color: var(--accent-cyan); } .lt-nav-link.active { color: var(--accent-orange); @@ -3884,7 +3885,9 @@ html[data-theme="light"] .lt-empty-state-title { color: var(--text-secondary); } html[data-theme="light"] .lt-combobox-dropdown, html[data-theme="light"] .lt-typeahead-dropdown { background: var(--bg-card); border-color: var(--border-color); box-shadow: 0 4px 16px rgba(0,0,0,0.1); } html[data-theme="light"] .lt-combobox-option:hover, -html[data-theme="light"] .lt-typeahead-option:hover { background: var(--accent-cyan-dim); } +html[data-theme="light"] .lt-typeahead-item:hover, +html[data-theme="light"] .lt-typeahead-item.is-focused, +html[data-theme="light"] .lt-typeahead-item:focus-visible { background: var(--accent-cyan-dim); color: var(--accent-cyan); } html[data-theme="light"] .lt-combobox-tag { background: var(--accent-cyan-dim); color: var(--accent-cyan); border-color: var(--accent-cyan-border); } /* — Sortable ghost — */ diff --git a/base.html b/base.html index 4a95eb8..bcaae90 100644 --- a/base.html +++ b/base.html @@ -159,7 +159,7 @@ @@ -225,7 +225,7 @@
Add Comment
- +
+ @@ -370,8 +370,8 @@
- - + +
@@ -388,7 +388,7 @@
- +
diff --git a/base.js b/base.js index 5244fc6..8ecb0ec 100644 --- a/base.js +++ b/base.js @@ -232,9 +232,14 @@ _unlockScroll(); // Remove trap handler if (el._ltTrapHandler) { el.removeEventListener('keydown', el._ltTrapHandler); delete el._ltTrapHandler; } - // Return focus to trigger + // Return focus to trigger (only if no other modal remains open) const trigger = _modalTriggers.get(el); - if (trigger) { trigger.focus(); _modalTriggers.delete(el); } + if (trigger) { + _modalTriggers.delete(el); + if (!document.querySelector('.lt-modal-overlay.is-open') && document.contains(trigger)) { + trigger.focus(); + } + } } function closeAllModals() { @@ -1672,9 +1677,10 @@ lt.contextMenu.register(selector, items) items = [{ label, icon, kbd, danger, divider, action }] ================================================================ */ - let _ctxMenu = null; + let _ctxMenu = null, _ctxTrigger = null; const _ctxItems = {}; - function _ctxShow(x, y, items) { + function _ctxShow(x, y, items, trigger) { + _ctxTrigger = trigger || (document.activeElement !== document.body ? document.activeElement : null); if (!_ctxMenu) { _ctxMenu = document.createElement('div'); _ctxMenu.className = 'lt-context-menu'; @@ -1706,6 +1712,8 @@ } function _ctxHide() { if (_ctxMenu) _ctxMenu.classList.remove('is-open'); + if (_ctxTrigger && document.contains(_ctxTrigger)) { _ctxTrigger.focus(); } + _ctxTrigger = null; } document.addEventListener('click', () => _ctxHide()); document.addEventListener('contextmenu', e => { @@ -1714,7 +1722,7 @@ e.preventDefault(); const menuId = target.dataset.contextMenu; const items = _ctxItems[menuId]; - if (items) _ctxShow(e.clientX, e.clientY, items); + if (items) _ctxShow(e.clientX, e.clientY, items, target); }); document.addEventListener('keydown', e => { if (e.key === 'Escape') _ctxHide(); }); const contextMenu = { @@ -2724,12 +2732,12 @@ // Links — block javascript: and data: URIs .replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => { const safeUrl = /^(https?:\/\/|\/|#|\.\.?\/)/i.test(url) ? url : '#'; - return `${text}`; + return `${escHtml(text)}`; }) // Images — block javascript: and data: URIs .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => { const safeSrc = /^(https?:\/\/|\/|\.\.?\/)/i.test(src) ? src : ''; - return `${alt}`; + return `${escHtml(alt)}`; }) // Blockquote .replace(/^>\s(.+)$/gm, '
$1
') @@ -2775,7 +2783,7 @@ const pages = _pages(); let html = ''; // Prev - html += ``; + html += ``; // Page buttons with ellipsis const half = Math.floor((maxBtns - 2) / 2); let start = Math.max(2, page - half); @@ -2784,15 +2792,17 @@ if (start === 2) end = Math.min(pages - 1, start + maxBtns - 3); else start = Math.max(2, end - maxBtns + 3); } - html += ``; - if (start > 2) html += ``; + html += ``; + if (start > 2) html += ``; for (let i = start; i <= end; i++) { - html += ``; + html += ``; } - if (end < pages - 1) html += ``; - if (pages > 1) html += ``; + if (end < pages - 1) html += ``; + if (pages > 1) html += ``; // Next - html += ``; + html += ``; + if (!nav.getAttribute('role')) nav.setAttribute('role', 'navigation'); + if (!nav.getAttribute('aria-label')) nav.setAttribute('aria-label', 'Pagination'); nav.innerHTML = html; }