diff --git a/app/assets/javascripts/wardrobe/item-search-keys.js b/app/assets/javascripts/wardrobe/item-search-keys.js new file mode 100644 index 00000000..d66499de --- /dev/null +++ b/app/assets/javascripts/wardrobe/item-search-keys.js @@ -0,0 +1,61 @@ +/** + * Keyboard shortcuts for item search. + * + * - Up/Down arrows move focus between the search field and the item list, + * so keyboard users can quickly browse results without tabbing. + * - Escape exits search mode (clicks the back button). + */ +document.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + const backButton = document.querySelector( + ".item-search-form .back-button", + ); + if (!backButton) return; + + // Only act when focus is on the search input or a search result. + const section = document.querySelector(".outfit-controls-section"); + const searchInput = section?.querySelector( + '.search-form input[type="text"]', + ); + const isSearchFocused = + document.activeElement === searchInput || + document.activeElement?.closest(".search-results-list") != null; + if (!isSearchFocused) return; + + e.preventDefault(); + backButton.click(); + return; + } + + if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return; + + const section = document.querySelector(".outfit-controls-section"); + if (!section) return; + + const searchInput = section.querySelector('.search-form input[type="text"]'); + if (!searchInput) return; + + // Collect all focusable item inputs in the results list. + const itemInputs = [ + ...section.querySelectorAll( + '.search-results-list item-card input[type="checkbox"]', + ), + ]; + if (itemInputs.length === 0) return; + + const allTargets = [searchInput, ...itemInputs]; + const currentIndex = allTargets.indexOf(document.activeElement); + if (currentIndex === -1) return; + + let nextIndex; + if (e.key === "ArrowDown") { + nextIndex = Math.min(currentIndex + 1, allTargets.length - 1); + } else { + nextIndex = Math.max(currentIndex - 1, 0); + } + + if (nextIndex !== currentIndex) { + e.preventDefault(); + allTargets[nextIndex].focus(); + } +}); diff --git a/app/views/wardrobe/show.html.haml b/app/views/wardrobe/show.html.haml index 6ab6865a..22b585df 100644 --- a/app/views/wardrobe/show.html.haml +++ b/app/views/wardrobe/show.html.haml @@ -18,6 +18,7 @@ = javascript_include_tag "tab-panel", async: true = javascript_include_tag "outfit-rename-field", async: true = javascript_include_tag "wardrobe/item-card", async: true + = javascript_include_tag "wardrobe/item-search-keys", async: true = javascript_include_tag "wardrobe/show", async: true = csrf_meta_tags %meta{name: 'outfit-viewer-morph-mode', value: 'full-page'}