diff --git a/frontend/index.html b/frontend/index.html index 24db269..fa2ad66 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -774,7 +774,7 @@
Sommaire
diff --git a/frontend/js/config.js b/frontend/js/config.js index a8d8caa..ea1bbde 100644 --- a/frontend/js/config.js +++ b/frontend/js/config.js @@ -189,11 +189,21 @@ function initHelpModal() { const modal = document.getElementById("help-modal"); if (!openBtn || !closeBtn || !modal) return; + let helpNavInitialized = false; + openBtn.addEventListener("click", () => { modal.classList.add("active"); closeHeaderMenu(); safeCreateIcons(); - initHelpNavigation(); + if (!helpNavInitialized) { + initHelpNavigation(); + helpNavInitialized = true; + } + // Focus search on open + const searchInput = document.getElementById("help-nav-search"); + if (searchInput) { + setTimeout(() => searchInput.focus(), 100); + } }); closeBtn.addEventListener("click", closeHelpModal); @@ -216,24 +226,57 @@ function initHelpNavigation() { const nav = document.getElementById("help-nav"); const searchInput = document.getElementById("help-nav-search"); const toggleBtn = document.getElementById("help-nav-toggle"); + const helpContent = document.querySelector(".help-content"); if (!helpBody || !navList || !nav) return; const allLinks = [...navList.querySelectorAll(".help-nav-link")]; + const allSections = [...document.querySelectorAll(".help-section")]; + const sectionMap = {}; + allSections.forEach(s => { sectionMap[s.id] = s; }); - // ── Scroll to section and focus it ── + // Build text index for content search (section title + text content) + const sectionTextCache = {}; + allSections.forEach(s => { + sectionTextCache[s.id] = (s.textContent || '').toLowerCase(); + }); + + // ── Scroll to section ── function scrollToSection(targetId) { - const section = document.getElementById(targetId); + const section = sectionMap[targetId]; if (!section) return; section.scrollIntoView({ behavior: "smooth", block: "start" }); section.setAttribute("tabindex", "-1"); - section.focus({ preventScroll: true }); // On mobile, collapse nav after selection if (window.innerWidth <= 768 && nav) { nav.classList.add("collapsed"); } } + // ── Focus the content pane (first heading in active section) ── + function focusContent() { + // Find the active section and focus its first heading + const activeLink = navList.querySelector(".help-nav-link.active:not(.hidden)"); + if (activeLink) { + const targetId = activeLink.getAttribute("href").substring(1); + const section = sectionMap[targetId]; + if (section) { + const h2 = section.querySelector("h2, h3"); + if (h2) { h2.setAttribute("tabindex", "-1"); h2.focus({ preventScroll: true }); return; } + } + } + // Fallback: focus the content area itself + if (helpContent) { helpContent.setAttribute("tabindex", "-1"); helpContent.focus({ preventScroll: true }); } + } + + // ── Focus TOC active link ── + function focusTOC() { + const activeLink = navList.querySelector(".help-nav-link.active:not(.hidden)"); + if (activeLink) { activeLink.focus(); return; } + const firstLink = navList.querySelector(".help-nav-link:not(.hidden)"); + if (firstLink) firstLink.focus(); + } + // ── Nav link click handler ── allLinks.forEach((link) => { link.addEventListener("click", (e) => { @@ -249,11 +292,20 @@ function initHelpNavigation() { if (!items.length) return; const currentIdx = items.indexOf(document.activeElement); - if (e.key === "ArrowDown" || e.key === "ArrowRight") { + if (e.key === "ArrowDown") { e.preventDefault(); const next = (currentIdx + 1) % items.length; items[next].focus(); - } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") { + } else if (e.key === "ArrowUp") { + e.preventDefault(); + const prev = (currentIdx - 1 + items.length) % items.length; + items[prev].focus(); + } else if (e.key === "ArrowRight") { + // Right arrow: go to content pane + e.preventDefault(); + focusContent(); + } else if (e.key === "ArrowLeft") { + // Left arrow in TOC: stay (prev item) e.preventDefault(); const prev = (currentIdx - 1 + items.length) % items.length; items[prev].focus(); @@ -267,45 +319,114 @@ function initHelpNavigation() { e.preventDefault(); const targetId = items[currentIdx].getAttribute("href").substring(1); scrollToSection(targetId); + // After scrolling, move focus to content + setTimeout(() => focusContent(), 400); } }); // Make nav items focusable navList.querySelectorAll("li").forEach((li) => li.setAttribute("role", "none")); - // ── Search filter ── - if (searchInput) { - searchInput.addEventListener("input", () => { - const q = searchInput.value.toLowerCase().trim(); - allLinks.forEach((link) => { - const text = link.textContent.toLowerCase(); - if (!q || text.includes(q)) { - link.classList.remove("hidden"); - } else { - link.classList.add("hidden"); - } - }); - // Update active state for visible links - updateActiveLink(); - }); - // Clear search on Escape - searchInput.addEventListener("keydown", (e) => { - if (e.key === "Escape") { - searchInput.value = ""; - searchInput.dispatchEvent(new Event("input")); + // ── Keyboard navigation in content pane ── + if (helpContent) { + helpContent.setAttribute("tabindex", "-1"); + helpContent.addEventListener("keydown", (e) => { + if (e.key === "ArrowLeft") { + // Left arrow in content: go back to TOC + e.preventDefault(); + focusTOC(); + } else if (e.key === "ArrowUp" && document.activeElement === helpContent) { + e.preventDefault(); + focusTOC(); } }); } + // ── Keyboard navigation in search input ── + if (searchInput) { + searchInput.addEventListener("keydown", (e) => { + if (e.key === "ArrowDown") { + e.preventDefault(); + const firstLink = navList.querySelector(".help-nav-link:not(.hidden)"); + if (firstLink) firstLink.focus(); + } + }); + } + + // ── Search: filter TOC + hide non-matching content sections ── + let currentQuery = ""; + if (searchInput) { + searchInput.addEventListener("input", () => { + currentQuery = searchInput.value.toLowerCase().trim(); + applySearchFilter(); + }); + searchInput.addEventListener("keydown", (e) => { + if (e.key === "Escape" && currentQuery) { + searchInput.value = ""; + currentQuery = ""; + applySearchFilter(); + } + }); + } + + function applySearchFilter() { + const q = currentQuery; + // Filter TOC links + allLinks.forEach((link) => { + const text = link.textContent.toLowerCase(); + if (!q || text.includes(q)) { + link.classList.remove("hidden"); + } else { + link.classList.add("hidden"); + } + }); + // Filter content sections + allSections.forEach((section) => { + if (!q) { + section.classList.remove("help-hidden"); + return; + } + const sectionText = sectionTextCache[section.id] || ''; + const sectionTitle = (section.querySelector("h2, h3")?.textContent || '').toLowerCase(); + if (sectionTitle.includes(q) || sectionText.includes(q)) { + section.classList.remove("help-hidden"); + } else { + section.classList.add("help-hidden"); + } + }); + updateActiveLink(); + // If no visible results, show a message + ensureNoResultsMessage(q); + } + + function ensureNoResultsMessage(q) { + const existing = document.getElementById("help-no-results"); + const visibleSections = allSections.filter(s => !s.classList.contains("help-hidden")); + if (q && visibleSections.length === 0) { + if (!existing) { + const msg = document.createElement("div"); + msg.id = "help-no-results"; + msg.style.cssText = "padding:40px;text-align:center;color:var(--text-muted);font-size:0.9rem"; + msg.innerHTML = `
🔍

Aucun résultat pour "${q}"

Essayez un autre terme

`; + if (helpContent) helpContent.appendChild(msg); + } + } else if (existing) { + existing.remove(); + } + } + // ── Mobile toggle ── if (toggleBtn) { toggleBtn.addEventListener("click", () => { + const wasCollapsed = nav.classList.contains("collapsed"); nav.classList.toggle("collapsed"); + // If expanding, focus search + if (wasCollapsed && searchInput) { + setTimeout(() => searchInput.focus(), 150); + } }); - // Start collapsed on mobile - if (window.innerWidth <= 768) { - nav.classList.add("collapsed"); - } + // Start expanded on mobile so user sees the TOC immediately + // (we use collapsed only after user taps a section or explicitly toggles) window.addEventListener("resize", () => { if (window.innerWidth > 768) { nav.classList.remove("collapsed"); @@ -314,31 +435,25 @@ function initHelpNavigation() { } // ── Scroll spy (IntersectionObserver) ── - let observer; function updateActiveLink() { - // Find the first visible section and highlight its link - const sections = [...document.querySelectorAll(".help-section")]; + const visibleLinks = allLinks.filter(l => !l.classList.contains("hidden")); + const visibleSections = allSections.filter(s => !s.classList.contains("help-hidden")); let activeId = null; - for (const section of sections) { + for (const section of visibleSections) { const rect = section.getBoundingClientRect(); - const helpContent = document.querySelector(".help-content"); const contentRect = helpContent ? helpContent.getBoundingClientRect() : null; - // Section top is in the upper half of the viewport (or within content) if (rect.top < (contentRect ? contentRect.top + 120 : 200)) { activeId = section.id; } } - if (activeId) { - allLinks.forEach((link) => { - const isMatch = link.getAttribute("href") === `#${activeId}`; - link.classList.toggle("active", isMatch); - }); - } + visibleLinks.forEach((link) => { + const isMatch = link.getAttribute("href") === `#${activeId}`; + link.classList.toggle("active", isMatch); + }); } - observer = new IntersectionObserver( + const observer = new IntersectionObserver( (entries) => { - // Find the entry most visible near the top let bestEntry = null; for (const entry of entries) { if (entry.isIntersecting) { @@ -362,27 +477,24 @@ function initHelpNavigation() { } ); - document.querySelectorAll(".help-section").forEach((section) => { - observer.observe(section); - }); + allSections.forEach((section) => observer.observe(section)); - // ── Keyboard: Ctrl+↑/↓ inside help body to jump between sections ── + // ── Keyboard: Ctrl+↑/↓ to jump between sections ── helpBody.addEventListener("keydown", (e) => { if ((e.ctrlKey || e.metaKey) && (e.key === "ArrowDown" || e.key === "ArrowUp")) { e.preventDefault(); - const sections = [...document.querySelectorAll(".help-section")]; - const currentSection = sections.find((s) => { + const visibleSections = allSections.filter(s => !s.classList.contains("help-hidden")); + const currentSection = visibleSections.find((s) => { const rect = s.getBoundingClientRect(); - const helpContent = document.querySelector(".help-content"); const contentRect = helpContent ? helpContent.getBoundingClientRect() : null; return rect.top >= (contentRect ? contentRect.top : 0) && rect.top < (contentRect ? contentRect.top + 120 : 120); }); if (!currentSection) return; - const currentIdx = sections.indexOf(currentSection); - if (e.key === "ArrowDown" && currentIdx < sections.length - 1) { - sections[currentIdx + 1].scrollIntoView({ behavior: "smooth", block: "start" }); + const currentIdx = visibleSections.indexOf(currentSection); + if (e.key === "ArrowDown" && currentIdx < visibleSections.length - 1) { + visibleSections[currentIdx + 1].scrollIntoView({ behavior: "smooth", block: "start" }); } else if (e.key === "ArrowUp" && currentIdx > 0) { - sections[currentIdx - 1].scrollIntoView({ behavior: "smooth", block: "start" }); + visibleSections[currentIdx - 1].scrollIntoView({ behavior: "smooth", block: "start" }); } } }); diff --git a/frontend/style.css b/frontend/style.css index a84d5dd..cece3f7 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -2287,6 +2287,9 @@ select { .help-nav-link.hidden { display: none; } +.help-section.help-hidden { + display: none; +} .help-content { flex: 1; @@ -6410,29 +6413,49 @@ body.popup-mode .content-area { border-right: none; border-bottom: 1px solid var(--border); flex-shrink: 0; + transition: max-height 0.25s ease; + overflow: hidden; } .help-nav-header { - padding: 12px 16px 8px; + padding: 10px 16px 6px; + cursor: pointer; } .help-nav-toggle { - display: inline-flex; /* visible on mobile */ + display: inline-flex; + transition: transform 0.2s; + } + .help-nav:not(.collapsed) .help-nav-toggle { + transform: rotate(180deg); } .help-nav.collapsed .help-nav-search-wrap, .help-nav.collapsed .help-nav-list { display: none; } + .help-nav-search-wrap { + padding: 0 12px 6px; + } .help-nav-list { - max-height: 40vh; + max-height: 45vh; flex: none; - padding-bottom: 12px; + padding-bottom: 14px; } .help-content { - padding: 16px 12px 40px; + padding: 14px 12px 40px; + flex: 1; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + .help-content:focus { + outline: none; } .help-hero { flex-direction: column; gap: 16px; } .help-hero-visual { display: none; } - .help-section { padding: 24px 0 20px; } + .help-section { padding: 20px 0 16px; } + .help-nav-link { + padding: 10px 12px; + font-size: 0.88rem; + } }