From e90b6bdb875315de6b962e2c7d36606d9a593eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Mon, 10 Mar 2025 13:28:42 +0000 Subject: [PATCH] 8350638: Make keyboard navigation more usable in API docs Reviewed-by: liach, nbenalla --- .../doclets/formats/html/HelpWriter.java | 22 ++ .../doclets/formats/html/HtmlIds.java | 1 + .../doclets/formats/html/Navigation.java | 3 +- .../doclets/formats/html/SearchWriter.java | 11 +- .../doclets/formats/html/TableOfContents.java | 10 +- .../formats/html/resources/script.js.template | 238 +++++++++++++----- .../formats/html/resources/search.js.template | 13 +- .../html/resources/standard.properties | 17 +- .../formats/html/resources/stylesheet.css | 42 +++- .../toolkit/resources/doclets.properties | 4 +- .../jdk/javadoc/internal/html/HtmlTree.java | 11 + .../TestAnnotationTypes.java | 4 +- .../TestConstantValuesPage.java | 14 +- .../testMarkdown/TestMarkdownHeadings.java | 9 +- .../testModules/TestModuleServicesLink.java | 10 +- .../doclet/testModules/TestModules.java | 14 +- .../doclet/testNavigation/TestNavigation.java | 14 +- .../TestPackageSummary.java | 10 +- .../javadoc/doclet/testSearch/TestSearch.java | 6 +- .../doclet/testSpecTag/TestSpecTag.java | 6 +- .../doclet/testStylesheet/TestStylesheet.java | 4 +- 21 files changed, 320 insertions(+), 143 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HelpWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HelpWriter.java index 8e5920eef63..bc10c223a15 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HelpWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HelpWriter.java @@ -35,6 +35,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocPath; import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; import jdk.javadoc.internal.html.Content; import jdk.javadoc.internal.html.ContentBuilder; +import jdk.javadoc.internal.html.Entity; import jdk.javadoc.internal.html.HtmlId; import jdk.javadoc.internal.html.HtmlTree; import jdk.javadoc.internal.html.Text; @@ -172,6 +173,27 @@ public class HelpWriter extends HtmlDocletWriter { .add(searchRefer); navSection.add(section); } + + // Keyboard Navigation + section = newHelpSection(contents.getContent("doclet.help.keyboard_navigation.title"), + HtmlIds.HELP_KEYBOARD_NAVIGATION); + var keyboardPara = HtmlTree.P(contents.getContent("doclet.help.keyboard_navigation.intro")); + var keyboardList = HtmlTree.UL(); + if (options.createIndex()) { + keyboardList.add(HtmlTree.LI(contents.getContent("doclet.help.keyboard_navigation.index", + HtmlTree.KBD(Text.of("/"))))); + } + keyboardList.add(HtmlTree.LI(contents.getContent("doclet.help.keyboard_navigation.filter", + HtmlTree.KBD(Text.of("."))))); + keyboardList.add(HtmlTree.LI(contents.getContent("doclet.help.keyboard_navigation.escape", + HtmlTree.KBD(Text.of("Esc"))))); + keyboardList.add(HtmlTree.LI(contents.getContent("doclet.help.keyboard_navigation.search", + HtmlTree.KBD(Text.of("Tab")), HtmlTree.KBD(Entity.of("downarrow")), + HtmlTree.KBD(Entity.of("uparrow"))))); + keyboardList.add(HtmlTree.LI(contents.getContent("doclet.help.keyboard_navigation.tabs", + HtmlTree.KBD(Entity.of("leftarrow")), HtmlTree.KBD(Entity.of("rightarrow"))))); + navSection.add(section.add(keyboardPara.add(keyboardList))); + tableOfContents.popNestedList(); return content; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java index 53dd622b2b0..42d93c41e11 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java @@ -92,6 +92,7 @@ public class HtmlIds { static final HtmlId FIELD_SUMMARY = HtmlId.of("field-summary"); static final HtmlId FOR_REMOVAL = HtmlId.of("for-removal"); static final HtmlId HELP_NAVIGATION = HtmlId.of("help-navigation"); + static final HtmlId HELP_KEYBOARD_NAVIGATION = HtmlId.of("help-keyboard-navigation"); static final HtmlId HELP_PAGES = HtmlId.of("help-pages"); static final HtmlId HELP_RELEASES = HtmlId.of("help-releases"); static final HtmlId METHOD_DETAIL = HtmlId.of("method-detail"); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.java index 7c63a581a4f..045eeb0f7a7 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.java @@ -498,7 +498,8 @@ public class Navigation { var inputText = HtmlTree.INPUT(HtmlAttr.InputType.TEXT, HtmlIds.SEARCH_INPUT) .put(HtmlAttr.PLACEHOLDER, resources.getText("doclet.search_placeholder")) .put(HtmlAttr.ARIA_LABEL, resources.getText("doclet.search_in_documentation")) - .put(HtmlAttr.AUTOCOMPLETE, "off"); + .put(HtmlAttr.AUTOCOMPLETE, "off") + .put(HtmlAttr.SPELLCHECK, "false"); var inputReset = HtmlTree.INPUT(HtmlAttr.InputType.RESET, HtmlIds.RESET_SEARCH) .put(HtmlAttr.VALUE, resources.getText("doclet.search_reset")); var searchDiv = HtmlTree.DIV(HtmlStyles.navListSearch) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java index c30dae6bc65..673bd35049f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException; import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; import jdk.javadoc.internal.html.Content; import jdk.javadoc.internal.html.ContentBuilder; +import jdk.javadoc.internal.html.Entity; import jdk.javadoc.internal.html.HtmlAttr; import jdk.javadoc.internal.html.HtmlId; import jdk.javadoc.internal.html.HtmlTag; @@ -85,7 +86,8 @@ public class SearchWriter extends HtmlDocletWriter { .add(HtmlTree.DIV(HtmlTree.INPUT(HtmlAttr.InputType.TEXT, HtmlId.of("page-search-input")) .put(HtmlAttr.PLACEHOLDER, resources.getText("doclet.search_placeholder")) .put(HtmlAttr.ARIA_LABEL, resources.getText("doclet.search_in_documentation")) - .put(HtmlAttr.AUTOCOMPLETE, "off")) + .put(HtmlAttr.AUTOCOMPLETE, "off") + .put(HtmlAttr.SPELLCHECK, "false")) .add(HtmlTree.INPUT(HtmlAttr.InputType.RESET, HtmlId.of("page-search-reset")) .put(HtmlAttr.VALUE, resources.getText("doclet.search_reset")) .put(HtmlAttr.STYLE, "margin: 6px;")) @@ -93,7 +95,10 @@ public class SearchWriter extends HtmlDocletWriter { .add(HtmlTree.SUMMARY(contents.getContent("doclet.search.show_more")) .setId(HtmlId.of("page-search-expand"))))) .add(HtmlTree.DIV(HtmlStyles.pageSearchInfo, helpSection) - .add(HtmlTree.P(contents.getContent("doclet.search.keyboard_info"))) + .add(HtmlTree.P(contents.getContent("doclet.search.keyboard_info", + HtmlTree.KBD(Text.of("Ctrl")), HtmlTree.KBD(Text.of("Cmd")), + new ContentBuilder(HtmlTree.KBD(Entity.of("leftarrow")), Text.of("/"), + HtmlTree.KBD(Entity.of("rightarrow")))))) .add(HtmlTree.P(contents.getContent("doclet.search.browser_info"))) .add(HtmlTree.SPAN(Text.of("link")) .setId(HtmlId.of("page-search-link"))) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java index cfb8f7260d0..6bb1961c232 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java @@ -47,7 +47,8 @@ public class TableOfContents { */ public TableOfContents(HtmlDocletWriter writer) { this.writer = writer; - listBuilder = new ListBuilder(HtmlTree.OL(HtmlStyles.tocList)); + listBuilder = new ListBuilder(HtmlTree.OL(HtmlStyles.tocList) + .put(HtmlAttr.TABINDEX, "-1")); } /** @@ -96,18 +97,21 @@ public class TableOfContents { .add(HtmlTree.INPUT(HtmlAttr.InputType.TEXT, HtmlStyles.filterInput) .put(HtmlAttr.PLACEHOLDER, writer.resources.getText("doclet.filter_label")) .put(HtmlAttr.ARIA_LABEL, writer.resources.getText("doclet.filter_table_of_contents")) - .put(HtmlAttr.AUTOCOMPLETE, "off")) + .put(HtmlAttr.AUTOCOMPLETE, "off") + .put(HtmlAttr.SPELLCHECK, "false")) .add(HtmlTree.INPUT(HtmlAttr.InputType.RESET, HtmlStyles.resetFilter) + .put(HtmlAttr.TABINDEX, "-1") .put(HtmlAttr.VALUE, writer.resources.getText("doclet.filter_reset"))); } content.add(header); + content.add(listBuilder); content.add(HtmlTree.BUTTON(HtmlStyles.hideSidebar) .add(HtmlTree.SPAN(writer.contents.hideSidebar).add(Entity.NO_BREAK_SPACE)) .add(Entity.LEFT_POINTING_ANGLE)); content.add(HtmlTree.BUTTON(HtmlStyles.showSidebar) .add(Entity.RIGHT_POINTING_ANGLE) .add(HtmlTree.SPAN(Entity.NO_BREAK_SPACE).add(writer.contents.showSidebar))); - return content.add(listBuilder); + return content; } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template index f9119833175..01b26c4ad06 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template @@ -201,6 +201,18 @@ function copyToClipboard(content) { document.execCommand("copy"); document.body.removeChild(textarea); } +function resetInput(input, event, blur) { + if (input.value) { + input.value = ""; + input.dispatchEvent(new InputEvent("input")); + } else if (blur) { + input.blur(); + } + event.preventDefault(); +} +function isInput(elem) { + return elem instanceof HTMLInputElement && elem.type === "text"; +} function switchCopyLabel(button, span) { var copied = span.getAttribute("data-copied"); button.classList.add("visible"); @@ -215,6 +227,82 @@ function switchCopyLabel(button, span) { }, 100); }, 1900); } +function makeFilterWidget(sidebar, updateToc) { + if (!sidebar) { + return null; + } + const filterInput = sidebar.querySelector("input.filter-input"); + const resetInput = sidebar.querySelector("input.reset-filter"); + sidebar.addEventListener("keydown", e => { + if (e.ctrlKey || e.altKey || e.metaKey) { + return; + } + if (e.key === "ArrowUp" || e.key === "ArrowDown") { + handleTocFocus(e); + } else if (filterInput && e.target !== filterInput) { + if (e.key === "Enter" && isTocLink(sidebar, e.target)) { + filterInput.value = ""; + filterInput.dispatchEvent(new InputEvent("input")); + } else if (e.key.length === 1 || e.key === "Backspace") { + filterInput.focus(); + } + } + }); + if (filterInput) { + filterInput.removeAttribute("disabled"); + filterInput.setAttribute("autocapitalize", "off"); + filterInput.value = ""; + filterInput.addEventListener("input", function(e) { + resetInput.style.visibility = filterInput.value ? "visible" : "hidden"; + const pattern = filterInput.value ? filterInput.value.trim() + .replace(/[\[\]{}()*+?.\\^$|]/g, '\\$&') + .replace(/\s+/g, ".*") : ""; + const filter = new RegExp(pattern, "i"); + sidebar.querySelectorAll("ol.toc-list li").forEach((li) => { + if (filter.test(li.innerText)) { + // li.removeAttribute("style"); + const selfMatch = filter.test(li.firstElementChild.innerText); + li.style.display = "block"; + li.firstElementChild.style.opacity = selfMatch ? "100%" : "70%"; + li.firstElementChild.tabIndex = selfMatch ? 0 : -1; + } else { + li.style.display = "none"; + } + }); + updateToc(); + }); + } + if (resetInput) { + resetInput.removeAttribute("disabled"); + resetInput.addEventListener("click", (e) => { + filterInput.value = ""; + filterInput.focus(); + filterInput.dispatchEvent(new InputEvent("input")); + }); + } + function handleTocFocus(event) { + let links = Array.from(sidebar.querySelectorAll("ol > li > a")) + .filter(link => link.offsetParent && link.tabIndex === 0); + let current = links.indexOf(document.activeElement); + if (event.key === "ArrowUp") { + if (current > 0) { + links[current - 1].focus({focusVisible: true}); + } else if (filterInput) { + filterInput.focus(); + } + } else if (event.key === "ArrowDown" && current < links.length - 1) { + links[current + 1].focus({focusVisible: true}); + } + event.preventDefault(); + } + function isTocLink(sidebar, elem) { + let links = Array.from(sidebar.querySelectorAll("ol > li > a")) + .filter(link => link.offsetParent && link.tabIndex === 0); + return links.indexOf(elem) > -1; + } + return sidebar; +} + function setTopMargin() { // Dynamically set scroll margin to accomodate for draft header var headerHeight = Math.ceil(document.querySelector("header").offsetHeight); @@ -237,61 +325,69 @@ document.addEventListener("DOMContentLoaded", function(e) { if (subnav && subnav.lastElementChild) { subnav.lastElementChild.scrollIntoView({ behavior: "instant", inline: "start", block: "nearest" }); } + const keymap = new Map(); + const searchInput = document.getElementById("search-input") + || document.getElementById("page-search-input"); + if (searchInput) { + searchInput.addEventListener("focus", collapse); + keymap.set("/", searchInput); + } + const filterInput = document.querySelector("input.filter-input"); + if (filterInput) { + keymap.set(".", filterInput); + } // Clone TOC sidebar to header for mobile navigation const navbar = document.querySelector("div#navbar-top"); const sidebar = document.querySelector(".main-grid nav.toc"); const main = document.querySelector(".main-grid main"); const mainnav = navbar.querySelector("ul.nav-list"); const toggleButton = document.querySelector("button#navbar-toggle-button"); - const toc = sidebar ? sidebar.cloneNode(true) : null; - if (toc) { - navbar.appendChild(toc); + const tocMenu = sidebar ? sidebar.cloneNode(true) : null; + makeFilterWidget(sidebar, updateToc); + if (tocMenu) { + navbar.appendChild(tocMenu); + makeFilterWidget(tocMenu, updateToc); + var menuInput = tocMenu.querySelector("input.filter-input"); } - document.querySelectorAll("input.filter-input").forEach(function(input) { - input.removeAttribute("disabled"); - input.setAttribute("autocapitalize", "off"); - input.value = ""; - input.addEventListener("input", function(e) { - const pattern = input.value ? input.value.trim() - .replace(/[\[\]{}()*+?.\\^$|]/g, '\\$&') - .replace(/\s+/g, ".*") : ""; - input.nextElementSibling.style.display = pattern ? "inline" : "none"; - const filter = new RegExp(pattern, "i"); - input.parentNode.parentNode.querySelectorAll("ol.toc-list li").forEach((li) => { - if (filter.test(li.innerText)) { - li.removeAttribute("style"); - } else { - li.style.display = "none"; - } - }); - if (expanded) { - expand(); + document.addEventListener("keydown", (e) => { + if (e.ctrlKey || e.altKey || e.metaKey) { + return; + } + if (!isInput(e.target) && keymap.has(e.key)) { + var elem = keymap.get(e.key); + if (elem === filterInput && !elem.offsetParent) { + elem = getVisibleFilterInput(true); } - }); - }); - document.querySelectorAll("input.reset-filter").forEach((button) => { - button.removeAttribute("disabled"); - button.addEventListener("click", (e) => { - const input = button.previousElementSibling; - input.value = ""; - input.dispatchEvent(new InputEvent("input")); - input.focus(); + elem.focus(); + elem.select(); + e.preventDefault(); + } else if (e.key === "Escape") { if (expanded) { - expand(); + collapse(); + e.preventDefault(); + } else if (e.target.id === "page-search-input") { + resetInput(e.target, e, false); + } else if (isInput(e.target)) { + resetInput(e.target, e, true); } else { - prevHash = null; - handleScroll(); + var filter = getVisibleFilterInput(false); + if (filter && filter.value) { + resetInput(filterInput, e, true); + } } - }) + } }); var expanded = false; var windowWidth; var bodyHeight; - function collapse(e) { + function collapse() { if (expanded) { mainnav.removeAttribute("style"); - if (toc) { - toc.removeAttribute("style"); + if (tocMenu) { + tocMenu.removeAttribute("style"); + if (filterInput) { + keymap.set(".", filterInput); + } } toggleButton.classList.remove("expanded") toggleButton.setAttribute("aria-expanded", "false"); @@ -304,18 +400,42 @@ document.addEventListener("DOMContentLoaded", function(e) { mainnav.style.removeProperty("height"); var maxHeight = window.innerHeight - subnav.offsetTop + 4; var expandedHeight = Math.min(maxHeight, mainnav.scrollHeight + 10); - if (toc) { - toc.style.display = "flex"; + if (tocMenu) { + tocMenu.style.display = "flex"; expandedHeight = Math.min(maxHeight, - Math.max(expandedHeight, toc.querySelector("div.toc-header").offsetHeight - + toc.querySelector("ol.toc-list").scrollHeight + 10)); - toc.style.height = expandedHeight + "px"; + Math.max(expandedHeight, tocMenu.querySelector("div.toc-header").offsetHeight + + tocMenu.querySelector("ol.toc-list").scrollHeight + 10)); + tocMenu.style.height = expandedHeight + "px"; + if (menuInput) { + keymap.set(".", menuInput); + } } mainnav.style.height = expandedHeight + "px"; toggleButton.classList.add("expanded"); toggleButton.setAttribute("aria-expanded", "true"); windowWidth = window.innerWidth; } + function updateToc() { + if (expanded) { + expand(); + } else { + prevHash = null; + handleScroll(); + } + } + function getVisibleFilterInput(show) { + if (sidebar && sidebar.offsetParent) { + if (show) { + showSidebar(); + } + return filterInput; + } else { + if (show) { + expand(); + } + return menuInput; + } + } toggleButton.addEventListener("click", (e) => { if (expanded) { collapse(); @@ -323,17 +443,12 @@ document.addEventListener("DOMContentLoaded", function(e) { expand(); } }); - if (toc) { - toc.querySelectorAll("a").forEach((link) => { + if (tocMenu) { + tocMenu.querySelectorAll("a").forEach((link) => { link.addEventListener("click", collapse); }); } - document.addEventListener('keydown', (e) => { - if (e.key === "Escape") collapse(); - }); document.querySelector("main").addEventListener("click", collapse); - const searchInput = document.getElementById("search-input"); - if (searchInput) searchInput.addEventListener("focus", collapse); document.querySelectorAll("h1, h2, h3, h4, h5, h6") .forEach((hdr, idx) => { // Create anchor links for headers with an associated id attribute @@ -412,24 +527,27 @@ document.addEventListener("DOMContentLoaded", function(e) { } } } + function hideSidebar() { + sidebar.classList.add("hide-sidebar"); + sessionStorage.setItem("sidebar", "hidden"); + } + function showSidebar() { + sidebar.classList.remove("hide-sidebar"); + sessionStorage.removeItem("sidebar"); + initSectionData(); + handleScroll(); + } if (sidebar) { initSectionData(); document.querySelectorAll("a[href^='#']").forEach((link) => { link.addEventListener("click", (e) => { + link.blur(); scrollTimeoutNeeded = true; setSelected(link.getAttribute("href")); }) }); - sidebar.querySelector("button.hide-sidebar").addEventListener("click", () => { - sidebar.classList.add("hide-sidebar"); - sessionStorage.setItem("sidebar", "hidden"); - }); - sidebar.querySelector("button.show-sidebar").addEventListener("click", () => { - sidebar.classList.remove("hide-sidebar"); - sessionStorage.removeItem("sidebar"); - initSectionData(); - handleScroll(); - }); + sidebar.querySelector("button.hide-sidebar").addEventListener("click", hideSidebar); + sidebar.querySelector("button.show-sidebar").addEventListener("click", showSidebar); window.addEventListener("hashchange", (e) => { scrollTimeoutNeeded = true; }); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template index c82964fa685..a84fadf90d7 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ @@ -390,7 +390,6 @@ $(function() { minLength: 1, delay: 200, source: function(request, response) { - reset.css("display", "inline"); if (request.term.trim() === "") { return this.close(); } @@ -403,12 +402,6 @@ $(function() { $("#search-input").empty(); } }, - close: function(event, ui) { - reset.css("display", search.val() ? "inline" : "none"); - }, - change: function(event, ui) { - reset.css("display", search.val() ? "inline" : "none"); - }, autoFocus: true, focus: function(event, ui) { return false; @@ -420,17 +413,15 @@ $(function() { if (ui.item.indexItem) { var url = getURL(ui.item.indexItem, ui.item.category); window.location.href = pathtoroot + url; - $("#search-input").focus(); } } }); search.val(''); + search.on("input", () => reset.css("visibility", search.val() ? "visible" : "hidden")) search.prop("disabled", false); search.attr("autocapitalize", "off"); reset.prop("disabled", false); reset.click(function() { search.val('').focus(); - reset.css("display", "none"); }); - search.focus(); }); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties index 66302785ee6..18c16894e56 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties @@ -198,9 +198,10 @@ doclet.search.help_page_link=help page # 0: a link to the help page with text above doclet.search.help_page_info= \ The {0} provides an introduction to the scope and syntax of JavaDoc search. +# 0: [Ctrl] 1: [Cmd] 2: [<]/[>] (left and right arrow keys) doclet.search.keyboard_info= \ - You can use the or keys in combination with the left and right arrow \ - keys to switch between result tabs in this page. + You can use the {0} or {1} key in combination with the {2} arrow \ + keys to switch between result tabs in this page while searching. doclet.search.browser_info= \ The URL template below may be used to configure this page as a search engine \ in browsers that support this feature. It has been tested to work in Google \ @@ -385,6 +386,18 @@ doclet.help.search.refer=Refer to the {0} for a full description of search featu doclet.help.search.spec.url=https://docs.oracle.com/en/java/javase/{0}/docs/specs/javadoc/javadoc-search-spec.html # The title for the Javadoc Search Specification doclet.help.search.spec.title=Javadoc Search Specification +doclet.help.keyboard_navigation.title=Keyboard Navigation +doclet.help.keyboard_navigation.intro=Documentation pages provide keyboard shortcuts to facilitate \ + access to common navigation tasks. +# Arguments in the messages below are elements representing various keyboard keys +doclet.help.keyboard_navigation.index=Type {0} to access the search input field in any page. +doclet.help.keyboard_navigation.filter=Type {0} to access the filter input field in the sidebar \ + of class pages. +doclet.help.keyboard_navigation.escape=Type {0} to clear the input and release keyboard focus in \ + any input field. +doclet.help.keyboard_navigation.search=Type {0}/{1}/{2} to select list items after entering a \ + search term in a search or filter input field. +doclet.help.keyboard_navigation.tabs=Type {0}/{1} to switch between tabs in tabbed summary tables. doclet.help.releases.head=Release Details doclet.help.releases.body.specify.top-level=\ The details for each module, package, class or interface \ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css index dafe74dab0d..725de35ec28 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css @@ -465,7 +465,8 @@ dl.name-value > dd { .main-grid nav.toc button span { display: none; } -.main-grid nav.toc button:hover { +.main-grid nav.toc button:hover, +.main-grid nav.toc button:focus { color: var(--body-text-color); border: 1px solid var(--subnav-background-color); } @@ -474,10 +475,11 @@ dl.name-value > dd { color: var(--link-color-active); } .main-grid nav.toc button:hover span, -.main-grid nav.toc button:active span { +.main-grid nav.toc button:focus span { display: inline; } -.main-grid nav.toc button:hover { +.main-grid nav.toc button:hover, +.main-grid nav.toc button:focus { box-shadow: 1px 1px 5px rgba(0,0,0,0.2); } .main-grid nav.toc.hide-sidebar { @@ -511,13 +513,10 @@ nav.toc > ol.toc-list { } nav.toc ol.toc-list { list-style: none; + font-size: var(--nav-font-size); padding-left: 0; margin: 0; } -nav.toc ol.toc-list li { - margin: 0; - font-size: var(--nav-font-size); -} a.current-selection { font-weight: bold; } @@ -536,6 +535,11 @@ nav.toc a:hover { nav.toc a.current-selection { background-color: var(--toc-highlight-color); } +nav.toc a:focus-visible { + background-color: var(--selected-background-color); + color: var(--selected-text-color); + outline: none; +} /* * Styles for lists. */ @@ -1015,9 +1019,9 @@ input#search-input { margin: 0; } input.filter-input { - width: 44%; - max-width: 140px; - margin: 0 4px; + min-width: 40px; + width: 180px; + margin: 0 -8px 0 5px; padding-right: 18px; } input#reset-search, input.reset-filter { @@ -1029,8 +1033,10 @@ input#reset-search, input.reset-filter { border-radius:0; width:12px; height:12px; + min-width:12px; + min-height:12px; font-size:0; - display:none; + visibility:hidden; } input#reset-search { position:absolute; @@ -1039,13 +1045,25 @@ input#reset-search { } input.reset-filter { position: relative; - right: 20px; + right: 10px; top: 0; } input::placeholder { color:var(--search-input-placeholder-color); opacity: 1; } +input:focus::placeholder { + color: transparent; +} +kbd { + background-color: #eeeeee; + border: 1px solid #b0b0b0; + border-radius: 3px; + padding: 0 4px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 0 0 rgba(255, 255, 255, 0.6) inset; + font-size: 0.9em; + font-weight: bold; +} .search-tag-desc-result { font-style:italic; font-size:11px; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties index 6b1cd9b0fc8..8ce2c9c6125 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties @@ -233,7 +233,7 @@ doclet.Type=Type doclet.Modifier_and_Type=Modifier and Type doclet.Implementation=Implementation(s): doclet.search=Search -doclet.search_placeholder=Search +doclet.search_placeholder=Search documentation (type /) doclet.search_in_documentation=Search in documentation doclet.search_reset=Reset doclet.Field=Field @@ -251,7 +251,7 @@ doclet.Value=Value doclet.table_of_contents=Table of contents doclet.hide_sidebar=Hide sidebar doclet.show_sidebar=Show sidebar -doclet.filter_label=Filter +doclet.filter_label=Filter contents (type .) doclet.filter_table_of_contents=Filter table of contents doclet.filter_reset=Reset doclet.linkMismatch_PackagedLinkedtoModule=The code being documented uses packages in the unnamed module, \ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/HtmlTree.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/HtmlTree.java index d6642390335..2cdafcd9c5e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/HtmlTree.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/HtmlTree.java @@ -716,6 +716,17 @@ public class HtmlTree extends Content { .setStyle(style) .put(HtmlAttr.DISABLED, ""); } + + /** + * Creates a {@code KBD} element with the given content. + * + * @param body the content + * @return the element + */ + public static HtmlTree KBD(Content body) { + return new HtmlTree(HtmlTag.KBD).add(body); + } + /** * Creates an HTML {@code LABEL} element with the given content. * diff --git a/test/langtools/jdk/javadoc/doclet/testAnnotationTypes/TestAnnotationTypes.java b/test/langtools/jdk/javadoc/doclet/testAnnotationTypes/TestAnnotationTypes.java index 9235fcd8215..fd17f8d7f50 100644 --- a/test/langtools/jdk/javadoc/doclet/testAnnotationTypes/TestAnnotationTypes.java +++ b/test/langtools/jdk/javadoc/doclet/testAnnotationTypes/TestAnnotationTypes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -73,7 +73,7 @@ public class TestAnnotationTypes extends JavadocTester { checkOutput("pkg/AnnotationType.html", true, """ -
    +
    1. Description
    2. Required Element Summary
    3. Optional Element Summary
    4. diff --git a/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java b/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java index d078c19644a..db93ce1d623 100644 --- a/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java +++ b/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -116,8 +116,7 @@ public class TestConstantValuesPage extends JavadocTester {
    5. p1.p2b.*
    -
- """); + """); } /** @@ -152,8 +151,7 @@ public class TestConstantValuesPage extends JavadocTester {
  • Unnamed Package
  • - - """); + """); } /** @@ -196,8 +194,7 @@ public class TestConstantValuesPage extends JavadocTester {
  • p1.p2a.*
  • - - """); + """); } /** @@ -267,7 +264,6 @@ public class TestConstantValuesPage extends JavadocTester {
  • p.q.*
  • - - """); + """); } } diff --git a/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownHeadings.java b/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownHeadings.java index 8d5c782a66b..b10db5d5619 100644 --- a/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownHeadings.java +++ b/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownHeadings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -314,12 +314,7 @@ public class TestMarkdownHeadings extends JavadocTester { checkOutput("p/C.html", true, // note only the level 1 headings in the class description """ -
    Contents  - - -
    - -
      +
      1. Description
        1. ATX heading code underline text
        2. diff --git a/test/langtools/jdk/javadoc/doclet/testModules/TestModuleServicesLink.java b/test/langtools/jdk/javadoc/doclet/testModules/TestModuleServicesLink.java index 04745e93d0e..5fd6480f680 100644 --- a/test/langtools/jdk/javadoc/doclet/testModules/TestModuleServicesLink.java +++ b/test/langtools/jdk/javadoc/doclet/testModules/TestModuleServicesLink.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8185151 8196200 8261976 + * @bug 8185151 8196200 8261976 8350638 * @summary test that navigation summary links are not linked when there are no dependencies * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main @@ -72,7 +72,7 @@ public class TestModuleServicesLink extends JavadocTester { checkOutput("m/module-summary.html", true, """ -
            +
            1. Description
            2. Packages
            3. Services
            4. @@ -98,7 +98,7 @@ public class TestModuleServicesLink extends JavadocTester { checkOutput("m/module-summary.html", true, """ -
                +
                1. Description
                2. Packages
                3. Services
                4. @@ -122,7 +122,7 @@ public class TestModuleServicesLink extends JavadocTester { checkOutput("m/module-summary.html", true, """ -
                    +
                    1. Description
                    2. Packages
                    """); diff --git a/test/langtools/jdk/javadoc/doclet/testModules/TestModules.java b/test/langtools/jdk/javadoc/doclet/testModules/TestModules.java index 88ccac31580..73afa9839f4 100644 --- a/test/langtools/jdk/javadoc/doclet/testModules/TestModules.java +++ b/test/langtools/jdk/javadoc/doclet/testModules/TestModules.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -722,7 +722,7 @@ public class TestModules extends JavadocTester { void checkModuleSummary() { checkOutput("moduleA/module-summary.html", true, """ -
                      +
                      1. Description
                      2. Packages
                      3. Modules
                      4. @@ -748,7 +748,7 @@ public class TestModules extends JavadocTester { """); checkOutput("moduleB/module-summary.html", true, """ -
                          +
                          1. Description
                          2. Packages
                          3. Services
                          4. @@ -897,7 +897,7 @@ public class TestModules extends JavadocTester { """); checkOutput("moduleA/module-summary.html", true, """ -
                              +
                              1. Description
                              2. Packages
                              3. Modules
                              4. @@ -912,7 +912,7 @@ public class TestModules extends JavadocTester { -tab1">testpkgmdltags
                                 
                                """, """ -
                                  +
                                  1. Description
                                  2. Packages
                                  3. Modules
                                  4. @@ -960,7 +960,7 @@ public class TestModules extends JavadocTester {
                                     
                                    """); checkOutput("moduleB/module-summary.html", found, """ -
                                      +
                                      1. Description
                                      2. Packages
                                      3. Services
                                      4. @@ -1037,7 +1037,7 @@ public class TestModules extends JavadocTester {
                                         
                                        """); checkOutput("moduleB/module-summary.html", found, """ -
                                          +
                                          1. Description
                                          2. Packages
                                          3. Modules
                                          4. diff --git a/test/langtools/jdk/javadoc/doclet/testNavigation/TestNavigation.java b/test/langtools/jdk/javadoc/doclet/testNavigation/TestNavigation.java index cbda0bc5c3c..f7cbb78fbeb 100644 --- a/test/langtools/jdk/javadoc/doclet/testNavigation/TestNavigation.java +++ b/test/langtools/jdk/javadoc/doclet/testNavigation/TestNavigation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,7 @@ /* * @test * @bug 7025314 8023700 7198273 8025633 8026567 8081854 8196027 8182765 - * 8196200 8196202 8223378 8258659 8261976 8320458 8329537 + * 8196200 8196202 8223378 8258659 8261976 8320458 8329537 8350638 * @summary Make sure the Next/Prev Class links iterate through all types. * Make sure the navagation is 2 columns, not 3. * @library /tools/lib ../../lib @@ -185,7 +185,7 @@ public class TestNavigation extends JavadocTester {
                                          5. X
                                          """, """ -
                                            +
                                            1. Description
                                            2. Nested Class Summary
                                            3. Field Summary
                                            4. @@ -216,7 +216,7 @@ public class TestNavigation extends JavadocTester {
                                            5. Y
                                            """, """ -
                                              +
                                              1. Description
                                              2. Nested Class Summary
                                              3. Field Summary
                                              4. @@ -238,7 +238,7 @@ public class TestNavigation extends JavadocTester {
                                              5. IC
                                              """, """ -
                                                +
                                                1. Description
                                                2. Constructor Summary
                                                3. Method Summary
                                                4. @@ -251,7 +251,7 @@ public class TestNavigation extends JavadocTester { checkOrder("pkg1/C.html", """ -
                                                    +
                                                    1. Description
                                                    2. Constructor Summary
                                                    3. Method Summary
                                                    4. @@ -264,7 +264,7 @@ public class TestNavigation extends JavadocTester { checkOrder("pkg1/InterfaceWithNoMembers.html", """ -
                                                        +
                                                        1. Description
                                                        """); } diff --git a/test/langtools/jdk/javadoc/doclet/testPackageSummary/TestPackageSummary.java b/test/langtools/jdk/javadoc/doclet/testPackageSummary/TestPackageSummary.java index 47db4c22d07..8f077f78912 100644 --- a/test/langtools/jdk/javadoc/doclet/testPackageSummary/TestPackageSummary.java +++ b/test/langtools/jdk/javadoc/doclet/testPackageSummary/TestPackageSummary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,7 @@ /* * @test - * @bug 8189841 8253117 8263507 8320458 + * @bug 8189841 8253117 8263507 8320458 8350638 * @summary Error in alternate row coloring in package-summary files * @summary Improve structure of package summary pages * @library ../../lib/ @@ -51,14 +51,14 @@ public class TestPackageSummary extends JavadocTester { checkOutput("pkg/package-summary.html", true, """ -
                                                          +
                                                          1. Description
                                                          2. Classes and Interfaces
                                                          """); checkOutput("pkg1/package-summary.html", true, """ -
                                                            +
                                                            1. Description
                                                            2. Related Packages
                                                            3. Classes and Interfaces
                                                            4. @@ -66,7 +66,7 @@ public class TestPackageSummary extends JavadocTester { """); checkOutput("pkg1/sub/package-summary.html", true, """ -
                                                                +
                                                                1. Description
                                                                2. Related Packages
                                                                3. Classes and Interfaces
                                                                4. diff --git a/test/langtools/jdk/javadoc/doclet/testSearch/TestSearch.java b/test/langtools/jdk/javadoc/doclet/testSearch/TestSearch.java index ba3f64cd8b2..9a2bddb3cc4 100644 --- a/test/langtools/jdk/javadoc/doclet/testSearch/TestSearch.java +++ b/test/langtools/jdk/javadoc/doclet/testSearch/TestSearch.java @@ -26,7 +26,7 @@ * @bug 8141492 8071982 8141636 8147890 8166175 8168965 8176794 8175218 8147881 * 8181622 8182263 8074407 8187521 8198522 8182765 8199278 8196201 8196202 * 8184205 8214468 8222548 8223378 8234746 8241219 8254627 8247994 8263528 - * 8266808 8248863 8305710 8318082 8347058 + * 8266808 8248863 8305710 8318082 8347058 8350638 * @summary Test the search feature of javadoc. * @library ../../lib * @modules jdk.javadoc/jdk.javadoc.internal.tool @@ -433,8 +433,8 @@ public class TestSearch extends JavadocTester {
                                                                5. Search
                                                                6. """, """ """); } diff --git a/test/langtools/jdk/javadoc/doclet/testSpecTag/TestSpecTag.java b/test/langtools/jdk/javadoc/doclet/testSpecTag/TestSpecTag.java index cd92bbe0364..491f8a5d8aa 100644 --- a/test/langtools/jdk/javadoc/doclet/testSpecTag/TestSpecTag.java +++ b/test/langtools/jdk/javadoc/doclet/testSpecTag/TestSpecTag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -133,8 +133,8 @@ public class TestSpecTag extends JavadocTester { diff --git a/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java b/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java index 0ee5aafa063..c2dd0d92b0b 100644 --- a/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java +++ b/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java @@ -126,8 +126,10 @@ public class TestStylesheet extends JavadocTester { border-radius:0; width:12px; height:12px; + min-width:12px; + min-height:12px; font-size:0; - display:none; + visibility:hidden; }""", """ ::placeholder {