+
diff --git a/lib/rdoc/generator/template/aliki/css/rdoc.css b/lib/rdoc/generator/template/aliki/css/rdoc.css
index 5474bad68e..61a8329e26 100644
--- a/lib/rdoc/generator/template/aliki/css/rdoc.css
+++ b/lib/rdoc/generator/template/aliki/css/rdoc.css
@@ -483,10 +483,17 @@ header.top-navbar .navbar-search {
padding: 0 var(--space-8);
}
-header.top-navbar .navbar-search form {
+header.top-navbar .search-surface {
position: relative;
width: min(100%, var(--layout-search-width));
margin: 0;
+}
+
+header.top-navbar .navbar-search form {
+ position: relative;
+ box-sizing: border-box;
+ width: 100%;
+ margin: 0;
padding: 0;
}
@@ -558,11 +565,15 @@ header.top-navbar .navbar-search form {
gap: var(--space-4);
}
- /* Hide desktop search bar on mobile */
- header.top-navbar .navbar-search-desktop {
+ header.top-navbar .search-surface {
display: none;
}
+ header.top-navbar .navbar-search {
+ width: auto;
+ padding: 0;
+ }
+
/* Show mobile search icon */
.navbar-search-mobile {
display: flex;
@@ -1625,181 +1636,14 @@ main .attribute-access-type {
-/* 9. Search Modal (Mobile) */
-.search-modal {
- position: fixed;
- inset: 0;
- z-index: var(--z-modal);
+/* 9. Search */
+.search-backdrop,
+.search-icon,
+.search-close,
+.search-empty {
display: none;
}
-.search-modal:not([hidden]) {
- display: flex;
- align-items: flex-start;
- justify-content: center;
- padding: var(--space-16) var(--space-4);
-}
-
-/* Reduce padding on very small screens */
-@media (width <= 420px) {
- .search-modal:not([hidden]) {
- padding: var(--space-4) var(--space-3);
- }
-
- .search-modal-content {
- border-radius: var(--radius-md);
- }
-
- .search-modal-header {
- padding: var(--space-3);
- }
-
- .search-modal-body {
- padding: var(--space-3);
- }
-
- .search-modal-form input {
- font-size: var(--font-size-base);
- min-width: 0; /* Allow input to shrink */
- }
-
- .search-modal-form {
- gap: var(--space-2);
- }
-
- .search-modal-close {
- padding: var(--space-1) var(--space-3);
- font-size: 0.75rem;
- }
-}
-
-.search-modal-backdrop {
- position: absolute;
- inset: 0;
- background: var(--color-overlay);
- z-index: 1;
-}
-
-.search-modal-content {
- position: relative;
- z-index: 2;
- background: var(--color-background-primary);
- border-radius: var(--radius-lg);
- box-shadow: var(--shadow-xl);
- width: 100%;
- max-width: 600px;
- max-height: 80vh;
- display: flex;
- flex-direction: column;
-}
-
-.search-modal-header {
- padding: var(--space-6);
- border-bottom: 1px solid var(--color-border-default);
-}
-
-.search-modal-form {
- display: flex;
- align-items: center;
- gap: var(--space-3);
-}
-
-.search-modal-icon {
- font-size: 1.5rem;
- color: var(--color-text-secondary);
- flex-shrink: 0;
-}
-
-.search-modal-form input {
- flex: 1;
- border: none;
- outline: none;
- background: transparent;
- font-size: var(--font-size-lg);
- color: var(--color-text-primary);
- padding: 0;
-}
-
-.search-modal-form input::placeholder {
- color: var(--color-text-tertiary);
-}
-
-.search-modal-close {
- padding: var(--space-2) var(--space-4);
- background: var(--color-background-secondary);
- border: 1px solid var(--color-border-default);
- border-radius: var(--radius-md);
- font-size: var(--font-size-sm);
- color: var(--color-text-secondary);
- cursor: pointer;
- transition: background var(--transition-fast), border-color var(--transition-fast);
- flex-shrink: 0;
-}
-
-.search-modal-close:hover {
- background: var(--color-background-tertiary);
- border-color: var(--color-border-default);
-}
-
-.search-modal-body {
- padding: var(--space-6);
- overflow-y: auto;
- flex: 1;
-}
-
-.search-modal-empty {
- text-align: center;
- color: var(--color-text-tertiary);
- padding: var(--space-12) 0;
-}
-
-.search-modal-results {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-.search-modal-results.initially-hidden {
- display: block !important; /* Override initially-hidden */
-}
-
-.search-modal-results li {
- padding: var(--space-3) var(--space-4);
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: background var(--transition-fast);
- margin-bottom: var(--space-2);
-}
-
-.search-modal-results li:hover {
- background: var(--color-background-secondary);
-}
-
-.search-modal-results a {
- color: var(--color-text-primary);
-}
-
-.search-modal-results .search-match {
- margin: 0;
- font-size: var(--font-size-base);
-}
-
-.search-modal-results .search-match a {
- text-decoration: none;
-}
-
-.search-modal-results .search-namespace {
- margin: var(--space-1) 0 0 0;
- font-size: var(--font-size-sm);
- color: var(--color-text-secondary);
-}
-
-.search-modal-results .search-snippet {
- margin: var(--space-1) 0 0 0;
- font-size: var(--font-size-sm);
- color: var(--color-text-tertiary);
-}
-
/* 10. Right Sidebar - Table of Contents */
#table-of-contents {
grid-area: toc;
@@ -2069,9 +1913,11 @@ footer.site-footer .footer-bottom:first-child {
}
.search-results li {
+ position: relative;
list-style: none;
border-bottom: 1px solid var(--color-border-default);
margin-bottom: 0.5em;
+ cursor: pointer;
}
.search-results li:last-child {
@@ -2084,6 +1930,16 @@ footer.site-footer .footer-bottom:first-child {
margin: 0.5em;
}
+.search-results .search-match a::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+}
+
+.search-results li:focus-within {
+ background: var(--color-code-bg);
+}
+
.search-results .search-namespace {
font-weight: bold;
}
@@ -2155,7 +2011,7 @@ header.top-navbar #search-field::placeholder {
}
/* Search results dropdown in navbar */
-header.top-navbar #search-results-desktop {
+header.top-navbar #search-results {
box-sizing: border-box;
position: absolute;
top: calc(100% + var(--space-2));
@@ -2172,10 +2028,174 @@ header.top-navbar #search-results-desktop {
padding: 0;
}
-header.top-navbar #search-results-desktop.initially-hidden {
+header.top-navbar #search-results.initially-hidden {
display: none;
}
-header.top-navbar #search-results-desktop[aria-expanded="false"] {
+header.top-navbar #search-results[aria-expanded="false"] {
display: none;
}
+
+@media (width <= 1023px) {
+ body:has(header.top-navbar .navbar-search.is-open) {
+ overflow: hidden;
+ }
+
+ body:has(header.top-navbar .navbar-search.is-open) header.top-navbar {
+ z-index: var(--z-modal);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-backdrop {
+ position: fixed;
+ inset: 0;
+ z-index: 1;
+ display: block;
+ background: var(--color-overlay);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-surface {
+ position: fixed;
+ top: var(--space-16);
+ left: 50%;
+ z-index: 2;
+ display: flex;
+ flex-direction: column;
+ width: calc(100% - var(--space-8));
+ max-width: 600px;
+ max-height: 80vh;
+ margin: 0;
+ overflow: hidden;
+ background: var(--color-background-primary);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-xl);
+ transform: translateX(-50%);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-form {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+ padding: var(--space-6);
+ border-bottom: 1px solid var(--color-border-default);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-icon {
+ display: inline;
+ flex-shrink: 0;
+ color: var(--color-text-secondary);
+ font-size: 1.5rem;
+ }
+
+ header.top-navbar .navbar-search.is-open #search-field {
+ flex: 1;
+ min-width: 0;
+ padding: 0;
+ border: none;
+ outline: none;
+ background: transparent;
+ box-shadow: none;
+ font-size: var(--font-size-lg);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-close {
+ display: block;
+ flex-shrink: 0;
+ padding: var(--space-2) var(--space-4);
+ border: 1px solid var(--color-border-default);
+ border-radius: var(--radius-md);
+ background: var(--color-background-secondary);
+ color: var(--color-text-secondary);
+ cursor: pointer;
+ font-size: var(--font-size-sm);
+ transition: background var(--transition-fast), border-color var(--transition-fast);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-close:hover {
+ border-color: var(--color-border-default);
+ background: var(--color-background-tertiary);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-body {
+ flex: 1;
+ padding: var(--space-6);
+ overflow-y: auto;
+ }
+
+ header.top-navbar .navbar-search.is-open .search-empty {
+ display: block;
+ padding: var(--space-12) 0;
+ color: var(--color-text-tertiary);
+ text-align: center;
+ }
+
+ header.top-navbar .navbar-search.is-open #search-results {
+ position: static;
+ max-height: none;
+ margin: 0;
+ padding: 0;
+ overflow: visible;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ }
+
+ header.top-navbar .navbar-search.is-open #search-results.initially-hidden {
+ display: block;
+ }
+
+ header.top-navbar .navbar-search.is-open #search-results[aria-expanded="false"] {
+ display: none;
+ }
+
+ header.top-navbar .navbar-search.is-open .search-results li {
+ margin-bottom: var(--space-2);
+ padding: var(--space-3) var(--space-4);
+ border-radius: var(--radius-md);
+ transition: background var(--transition-fast);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-results li:hover {
+ background: var(--color-background-secondary);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-match {
+ margin: 0;
+ font-size: var(--font-size-base);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-match a {
+ text-decoration: none;
+ }
+
+ header.top-navbar .navbar-search.is-open .search-snippet {
+ margin: var(--space-1) 0 0 0;
+ color: var(--color-text-tertiary);
+ font-size: var(--font-size-sm);
+ }
+}
+
+@media (width <= 420px) {
+ header.top-navbar .navbar-search.is-open .search-surface {
+ top: var(--space-4);
+ width: calc(100% - var(--space-6));
+ border-radius: var(--radius-md);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-form,
+ header.top-navbar .navbar-search.is-open .search-body {
+ padding: var(--space-3);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-form {
+ gap: var(--space-2);
+ }
+
+ header.top-navbar .navbar-search.is-open #search-field {
+ font-size: var(--font-size-base);
+ }
+
+ header.top-navbar .navbar-search.is-open .search-close {
+ padding: var(--space-1) var(--space-3);
+ font-size: 0.75rem;
+ }
+}
diff --git a/lib/rdoc/generator/template/aliki/js/aliki.js b/lib/rdoc/generator/template/aliki/js/aliki.js
index 59cbe69ab2..2855cabb75 100644
--- a/lib/rdoc/generator/template/aliki/js/aliki.js
+++ b/lib/rdoc/generator/template/aliki/js/aliki.js
@@ -47,6 +47,8 @@ function createSearchInstance(input, result) {
html += `
${typeLabel}`;
}
+ html += '';
+
if (result.snippet)
html += `
${result.snippet}
`;
@@ -75,10 +77,15 @@ function createSearchInstance(input, result) {
function hookSearch() {
const input = document.querySelector('#search-field');
- const result = document.querySelector('#search-results-desktop');
+ const result = document.querySelector('#search-results');
if (!input || !result) return; // Exit if search elements not found
+ const container = input.closest('[data-search-container]');
+ const searchToggle = document.querySelector('#search-toggle');
+ const searchEmpty = container && container.querySelector('.search-empty');
+ let focusBeforeOpen = null;
+
const search_section = document.querySelector('#search-section');
if (search_section) {
search_section.classList.remove("initially-hidden");
@@ -87,23 +94,65 @@ function hookSearch() {
const search = createSearchInstance(input, result);
if (!search) return;
+ if (searchEmpty) {
+ const originalRenderItem = search.renderItem;
+ search.renderItem = function(result) {
+ searchEmpty.style.display = 'none';
+ return originalRenderItem.call(this, result);
+ };
+ }
+
+ const showSearchSurface = () => {
+ if (container) container.classList.add('is-open');
+ if (searchToggle) searchToggle.setAttribute('aria-expanded', 'true');
+ };
+
+ const openSearch = () => {
+ focusBeforeOpen = document.activeElement;
+ showSearchSurface();
+ input.focus({ preventScroll: true });
+ };
+
+ const closeSearch = (restoreFocus = true) => {
+ if (container) container.classList.remove('is-open');
+ if (searchToggle) searchToggle.setAttribute('aria-expanded', 'false');
+ search.hide();
+ input.blur();
+
+ if (restoreFocus && focusBeforeOpen && typeof focusBeforeOpen.focus === 'function') {
+ focusBeforeOpen.focus({ preventScroll: true });
+ }
+ focusBeforeOpen = null;
+ };
+
+ if (searchToggle) searchToggle.addEventListener('click', openSearch);
+ if (container) {
+ container.querySelectorAll('[data-search-close]').forEach((control) => {
+ control.addEventListener('click', () => closeSearch());
+ });
+ }
+
+ hookFocus(openSearch);
+
// Hide search results when clicking outside the search area
document.addEventListener('click', (e) => {
- if (!e.target.closest('.navbar-search-desktop')) {
- search.hide();
- }
+ if (container && container.contains(e.target)) return;
+
+ search.hide();
+ if (container && container.classList.contains('is-open')) closeSearch(false);
});
- // Hide search results on Escape key on desktop too
+ // Close the search surface on Escape
document.addEventListener('keydown', (e) => {
- if (e.key === 'Escape' && input.matches(":focus")) {
- search.hide();
- input.blur();
+ const isOpen = container && container.classList.contains('is-open');
+ if (e.key === 'Escape' && (isOpen || input.matches(":focus"))) {
+ closeSearch();
}
});
// Show search results when focusing on input (if there's a query)
input.addEventListener('focus', () => {
+ showSearchSurface();
if (input.value.trim()) {
search.show();
}
@@ -114,6 +163,7 @@ function hookSearch() {
const urlParams = new URLSearchParams(window.location.search);
const queryParam = urlParams.get('q');
if (queryParam) {
+ openSearch();
input.value = queryParam;
search.search(queryParam, false);
}
@@ -122,14 +172,14 @@ function hookSearch() {
/* ===== Keyboard Shortcuts ===== */
-function hookFocus() {
+function hookFocus(openSearch) {
document.addEventListener("keydown", (event) => {
if (document.activeElement.tagName === 'INPUT') {
return;
}
if (event.key === "/") {
event.preventDefault();
- document.querySelector('#search-field').focus();
+ openSearch();
}
});
}
@@ -328,78 +378,6 @@ function setHeadingSelfLinkScrollHandlers() {
})
}
-/* ===== Mobile Search Modal ===== */
-
-function hookSearchModal() {
- const searchToggle = document.querySelector('#search-toggle');
- const searchModal = document.querySelector('#search-modal');
- const searchModalClose = document.querySelector('#search-modal-close');
- const searchModalBackdrop = document.querySelector('.search-modal-backdrop');
- const searchInput = document.querySelector('#search-field-mobile');
- const searchResults = document.querySelector('#search-results-mobile');
- const searchEmpty = document.querySelector('.search-modal-empty');
-
- if (!searchToggle || !searchModal) return;
-
- // Initialize search for mobile modal
- const mobileSearch = createSearchInstance(searchInput, searchResults);
- if (!mobileSearch) return;
-
- // Hide empty state when there are results
- const originalRenderItem = mobileSearch.renderItem;
- mobileSearch.renderItem = function(result) {
- if (searchEmpty) searchEmpty.style.display = 'none';
- return originalRenderItem.call(this, result);
- };
-
- const openSearchModal = () => {
- searchModal.hidden = false;
- document.body.style.overflow = 'hidden';
- // Focus input after animation
- setTimeout(() => {
- if (searchInput) searchInput.focus();
- }, 100);
- };
-
- const closeSearchModal = () => {
- searchModal.hidden = true;
- document.body.style.overflow = '';
- };
-
- // Open on button click
- searchToggle.addEventListener('click', openSearchModal);
-
- // Close on close button click
- if (searchModalClose) {
- searchModalClose.addEventListener('click', closeSearchModal);
- }
-
- // Close on backdrop click
- if (searchModalBackdrop) {
- searchModalBackdrop.addEventListener('click', closeSearchModal);
- }
-
- // Close on Escape key
- document.addEventListener('keydown', (e) => {
- if (e.key === 'Escape' && !searchModal.hidden) {
- closeSearchModal();
- }
- });
-
- // Check for ?q= URL parameter on mobile and open modal
- if (typeof URLSearchParams !== 'undefined') {
- const urlParams = new URLSearchParams(window.location.search);
- const queryParam = urlParams.get('q');
- const isSmallViewport = window.matchMedia("(max-width: 1023px)").matches;
-
- if (queryParam && isSmallViewport) {
- openSearchModal();
- searchInput.value = queryParam;
- mobileSearch.search(queryParam, false);
- }
- }
-}
-
/* ===== Code Block Copy Functionality ===== */
function createCopyButton() {
@@ -503,11 +481,9 @@ function showCopySuccess(button) {
document.addEventListener('DOMContentLoaded', () => {
hookSourceViews();
hookSearch();
- hookFocus();
hookSidebar();
generateToc();
setHeadingSelfLinkScrollHandlers();
hookTocActiveHighlighting();
- hookSearchModal();
wrapCodeBlocksWithCopyButton();
});