From e1904a017b083b4980b360c2a99020757f9ff5d6 Mon Sep 17 00:00:00 2001
From: "transifex-integration[bot]"
<43880903+transifex-integration[bot]@users.noreply.github.com>
Date: Mon, 16 Dec 2024 15:37:52 +0000
Subject: [PATCH 1/5] Updates for file
src/pydata_sphinx_theme/locale/en/LC_MESSAGES/sphinx.po in es (#2079)
The following localization files have been updated:
Parameter | Value
---- | ----
Source File | src/pydata_sphinx_theme/locale/en/LC_MESSAGES/sphinx.po
Translation File |
src/pydata_sphinx_theme/locale/es/LC_MESSAGES/sphinx.po
Language Code | es
Transifex Project |
[pydata-sphinx-theme](https://app.transifex.com/12rambau/pydata-sphinx-theme/)
Transifex Resource | [src..LC_MESSAGES/sphinx.po
(main)](https://app.transifex.com/12rambau/pydata-sphinx-theme/79592a50a377d7a120926189491c3d76/)
Transifex Event | translated
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
.../locale/es/LC_MESSAGES/sphinx.po | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/pydata_sphinx_theme/locale/es/LC_MESSAGES/sphinx.po b/src/pydata_sphinx_theme/locale/es/LC_MESSAGES/sphinx.po
index a47eac62c..a8f632b5c 100644
--- a/src/pydata_sphinx_theme/locale/es/LC_MESSAGES/sphinx.po
+++ b/src/pydata_sphinx_theme/locale/es/LC_MESSAGES/sphinx.po
@@ -8,6 +8,7 @@
# Cristhian Rivera, 2024
# Felipe Moreno, 2024
# Tania Allard, 2024
+#
msgid ""
msgstr ""
@@ -41,7 +42,8 @@ msgstr "Error"
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:9
msgid "Please activate JavaScript to enable the search functionality."
-msgstr "Por favor, active JavaScript para habilitar la funcionalidad de búsqueda."
+msgstr ""
+"Por favor, active JavaScript para habilitar la funcionalidad de búsqueda."
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html:6
msgid "Breadcrumb"
@@ -152,8 +154,7 @@ msgid ""
"Built with the PyData Sphinx Theme "
"%(theme_version)s."
msgstr ""
-"Construido con el Tema PyData Sphinx "
+"Construido con el Tema PyData Sphinx "
"%(theme_version)s."
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/sections/announcement.html:4
@@ -191,3 +192,12 @@ msgstr "Navegación del sitio"
#~ msgid "light/dark"
#~ msgstr "claro/oscuro"
+
+#~ msgid ""
+#~ "Built with the PyData Sphinx Theme "
+#~ "%(theme_version)s."
+#~ msgstr ""
+#~ "Construido con el Tema PyData Sphinx "
+#~ "%(theme_version)s."
From c47786b993c85f0f442cc8d6e6b55e5d4e92b6b9 Mon Sep 17 00:00:00 2001
From: Tania Allard
Date: Tue, 17 Dec 2024 10:38:01 +0000
Subject: [PATCH 2/5] Bump: 0.16.1rc0 -> 0.16.1
---
docs/_static/switcher.json | 12 ++++++------
src/pydata_sphinx_theme/__init__.py | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/_static/switcher.json b/docs/_static/switcher.json
index 089e626b5..184e17ce0 100644
--- a/docs/_static/switcher.json
+++ b/docs/_static/switcher.json
@@ -4,15 +4,15 @@
"url": "https://pydata-sphinx-theme.readthedocs.io/en/latest/"
},
{
- "name": "0.16.1rc0",
- "version": "v0.16.1rc0",
- "url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.16.1rc0/"
+ "name": "0.16.1 (stable)",
+ "version": "v0.16.1",
+ "url": "https://pydata-sphinx-theme.readthedocs.io/en/stable/",
+ "preferred": true
},
{
- "name": "0.16.0 (stable)",
+ "name": "0.16.0",
"version": "v0.16.0",
- "url": "https://pydata-sphinx-theme.readthedocs.io/en/stable/",
- "preferred": true
+ "url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.16.0/"
},
{
"name": "0.15.4",
diff --git a/src/pydata_sphinx_theme/__init__.py b/src/pydata_sphinx_theme/__init__.py
index 219af6aba..71a975ad3 100644
--- a/src/pydata_sphinx_theme/__init__.py
+++ b/src/pydata_sphinx_theme/__init__.py
@@ -17,7 +17,7 @@
from . import edit_this_page, logo, pygments, short_link, toctree, translator, utils
-__version__ = "0.16.1rc0"
+__version__ = "0.16.1"
def update_config(app):
From 9b92ec9e8b834c303b842700acac47c7ef07aad9 Mon Sep 17 00:00:00 2001
From: Tania Allard
Date: Tue, 17 Dec 2024 16:57:55 +0000
Subject: [PATCH 3/5] BUG - Add `--pst-color-heading` fallback (#2082)
Nick pointed in
https://github.com/pydata/pydata-sphinx-theme/pull/2058#issuecomment-2548597450
that the `--pst-heading-color` variable is used by folks to customise
the headings colour (this was the colour variable before
https://github.com/pydata/pydata-sphinx-theme/pull/2058 which brought
this in line with our naming convention `--pst-color-heading`).
This change effectively renders the use of `--pst-heading-color`
useless, so this PR adds a fallback mechanism for this variable.
I also added a note about `--pst-color-heading` being our preferred
variable.
This is not a "big" breaking change, but it will likely affect folks who
do not have their PST version pinned since I just made a release. So, it
might be worth publishing this change in a follow-up.
---
src/pydata_sphinx_theme/assets/styles/base/_base.scss | 11 +++++++----
.../assets/styles/variables/_color.scss | 3 +++
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/pydata_sphinx_theme/assets/styles/base/_base.scss b/src/pydata_sphinx_theme/assets/styles/base/_base.scss
index 5ebd8bd06..82235df44 100644
--- a/src/pydata_sphinx_theme/assets/styles/base/_base.scss
+++ b/src/pydata_sphinx_theme/assets/styles/base/_base.scss
@@ -72,33 +72,36 @@ a {
line-height: 1.15;
}
+// From 0.16.1, the preferred variable for headings is --pst-color-heading
+// if you have --pst-heading-color, this variable will be used, otherwise the default
+// --pst-color-heading will be used.
h1 {
@extend %heading-style;
margin-top: 0;
font-size: var(--pst-font-size-h1);
- color: var(--pst-color-heading);
+ color: var(--pst-heading-color, --pst-color-heading);
}
h2 {
@extend %heading-style;
font-size: var(--pst-font-size-h2);
- color: var(--pst-color-heading);
+ color: var(--pst-heading-color, --pst-color-heading);
}
h3 {
@extend %heading-style;
font-size: var(--pst-font-size-h3);
- color: var(--pst-color-heading);
+ color: var(--pst-heading-color, --pst-color-heading);
}
h4 {
@extend %heading-style;
font-size: var(--pst-font-size-h4);
- color: var(--pst-color-heading);
+ color: var(--pst-heading-color, --pst-color-heading);
}
h5 {
diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
index 2738d775c..6553f0b24 100644
--- a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
+++ b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
@@ -286,6 +286,9 @@ $pst-semantic-colors: (
// assign the "duplicate" colors (ones that just reference other variables)
& {
+ // From 0.16.1, the preferred variable for headings is --pst-color-heading
+ // if you have --pst-heading-color, this variable will be used, otherwise the default
+ // --pst-color-heading will be used
--pst-color-heading: var(--pst-color-text-base);
--pst-color-link: var(--pst-color-primary);
--pst-color-link-hover: var(--pst-color-secondary);
From d76892d437e3c0dc8a059741a84afbd6cc458509 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Wed, 8 Jan 2025 11:51:28 -0600
Subject: [PATCH 4/5] [pre-commit.ci] pre-commit autoupdate hooks (#2091)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
updates:
- [github.com/pycontribs/mirrors-prettier: v3.3.3 →
v3.4.2](https://github.com/pycontribs/mirrors-prettier/compare/v3.3.3...v3.4.2)
- [github.com/astral-sh/ruff-pre-commit: v0.8.1 →
v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.6)
- [github.com/asottile/pyupgrade: v3.19.0 →
v3.19.1](https://github.com/asottile/pyupgrade/compare/v3.19.0...v3.19.1)
- [github.com/Riverside-Healthcare/djLint: v1.36.3 →
v1.36.4](https://github.com/Riverside-Healthcare/djLint/compare/v1.36.3...v1.36.4)
- [github.com/thibaudcolas/pre-commit-stylelint: v16.11.0 →
v16.12.0](https://github.com/thibaudcolas/pre-commit-stylelint/compare/v16.11.0...v16.12.0)
closes #2094
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel McCloy
---
.pre-commit-config.yaml | 10 ++++----
package-lock.json | 9 +++----
.../styles/abstracts/_accessibility.scss | 6 +++--
.../assets/styles/variables/_color.scss | 24 +++++++------------
.../assets/styles/variables/_fonts.scss | 6 ++---
tests/test_build.py | 11 +++++----
6 files changed, 31 insertions(+), 35 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0d7c67c84..209a6d3e2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,7 +13,7 @@ default_language_version:
repos:
- repo: "https://github.com/pycontribs/mirrors-prettier"
- rev: v3.3.3
+ rev: v3.4.2
hooks:
- id: prettier
# Exclude the HTML, since it doesn't understand Jinja2
@@ -22,20 +22,20 @@ repos:
exclude: .+\.html|webpack\.config\.js|tests/test_a11y/
- repo: "https://github.com/astral-sh/ruff-pre-commit"
- rev: "v0.8.1"
+ rev: "v0.8.6"
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
- id: ruff-format
- repo: "https://github.com/asottile/pyupgrade"
- rev: v3.19.0
+ rev: v3.19.1
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: "https://github.com/Riverside-Healthcare/djLint"
- rev: v1.36.3
+ rev: v1.36.4
hooks:
- id: djlint-jinja
types_or: ["html"]
@@ -56,7 +56,7 @@ repos:
- id: remove-metadata
- repo: "https://github.com/thibaudcolas/pre-commit-stylelint"
- rev: v16.11.0
+ rev: v16.12.0
hooks:
- id: stylelint
# automatically fix .scss files where possible
diff --git a/package-lock.json b/package-lock.json
index fef417f7c..5688e1834 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -709,9 +709,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001609",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001609.tgz",
- "integrity": "sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==",
+ "version": "1.0.30001690",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz",
+ "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==",
"dev": true,
"funding": [
{
@@ -726,7 +726,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "4.1.2",
diff --git a/src/pydata_sphinx_theme/assets/styles/abstracts/_accessibility.scss b/src/pydata_sphinx_theme/assets/styles/abstracts/_accessibility.scss
index bc5cf74b7..53aa6aef0 100644
--- a/src/pydata_sphinx_theme/assets/styles/abstracts/_accessibility.scss
+++ b/src/pydata_sphinx_theme/assets/styles/abstracts/_accessibility.scss
@@ -70,8 +70,10 @@
$rgb-col: map.merge(
$rgb-col,
(
- $channel:
- math.pow(math.div((math.div($value, 255) + 0.055), 1.055), 2.4),
+ $channel: math.pow(
+ math.div((math.div($value, 255) + 0.055), 1.055),
+ 2.4
+ ),
)
);
}
diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
index 6553f0b24..5efe04989 100644
--- a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
+++ b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
@@ -26,8 +26,7 @@
/* Assign base colors for the PyData theme */
$color-palette: (
// Primary color
- "teal":
- (
+ "teal": (
"50": #f4fbfc,
"100": #e9f6f8,
"200": #d0ecf1,
@@ -40,8 +39,7 @@ $color-palette: (
"900": #021b1f,
),
// Secondary color
- "violet":
- (
+ "violet": (
"50": #f4eefb,
"100": #e0c7ff,
"200": #d5b4fd,
@@ -54,8 +52,7 @@ $color-palette: (
"900": #1e0e39,
),
// Neutrals
- "gray":
- (
+ "gray": (
"50": #f9f9fa,
"100": #f3f4f5,
"200": #e5e7ea,
@@ -68,8 +65,7 @@ $color-palette: (
"900": #14181e,
),
// Accent color
- "pink":
- (
+ "pink": (
"50": #fcf8fd,
"100": #fcf0fa,
"200": #f8dff5,
@@ -161,8 +157,7 @@ $pst-semantic-colors: (
"bg-dark": #002f17,
),
// This is based on the warning color
- "attention":
- (
+ "attention": (
"light": var(--pst-color-warning),
"bg-light": var(--pst-color-warning-bg),
"dark": var(--pst-color-warning),
@@ -229,15 +224,13 @@ $pst-semantic-colors: (
// DEPTH COLORS - you can see the elevation colours and shades
// in the Figma file https://www.figma.com/file/rUrrHGhUBBIAAjQ82x6pz9/PyData-Design-system---proposal-for-implementation-(2)?node-id=1492%3A922&t=sQeQZehkOzposYEg-1
// background: color of the canvas / the furthest back layer
- "background":
- (
+ "background": (
"light": #{map-deep-get($color-palette, "foundation", "white")},
"dark": #{map-deep-get($color-palette, "foundation", "black")},
),
// on-background: provides slight contrast against background
// (by use of shadows in light theme)
- "on-background":
- (
+ "on-background": (
"light": #{map-deep-get($color-palette, "foundation", "white")},
"dark": #{map-deep-get($color-palette, "gray", "800")},
),
@@ -246,8 +239,7 @@ $pst-semantic-colors: (
"dark": #{map-deep-get($color-palette, "gray", "700")},
),
// on_surface: object on top of surface object (without shadows)
- "on-surface":
- (
+ "on-surface": (
"light": #{map-deep-get($color-palette, "gray", "800")},
"dark": $foundation-light-gray,
),
diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss b/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss
index 5b76b37ed..9a16122ea 100644
--- a/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss
+++ b/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss
@@ -33,9 +33,9 @@ html {
// Font family
// These are adapted from https://systemfontstack.com/ */
- --pst-font-family-base-system: -apple-system, "BlinkMacSystemFont", "Segoe UI",
- "Helvetica Neue", "Arial", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
- "Segoe UI Symbol";
+ --pst-font-family-base-system: -apple-system, "BlinkMacSystemFont",
+ "Segoe UI", "Helvetica Neue", "Arial", sans-serif, "Apple Color Emoji",
+ "Segoe UI Emoji", "Segoe UI Symbol";
--pst-font-family-monospace-system: "SFMono-Regular", "Menlo", "Consolas",
"Monaco", "Liberation Mono", "Lucida Console", monospace;
--pst-font-family-base: var(--pst-font-family-base-system);
diff --git a/tests/test_build.py b/tests/test_build.py
index 7a005fa4a..8444da330 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -891,8 +891,8 @@ def test_pygments_fallbacks(sphinx_build_factory, style_names, keyword_colors) -
# see if our warnings worked
if style_names[0].startswith("fake"):
assert len(warnings) == 2
- re.match(r"Color theme fake_foo.*tango", warnings[0])
- re.match(r"Color theme fake_bar.*monokai", warnings[1])
+ assert re.search(r"Highlighting style fake_foo.*tango", warnings[0])
+ assert re.search(r"Highlighting style fake_bar.*monokai", warnings[1])
else:
assert warnings == [""]
# test that the rendered HTML has highlighting spans
@@ -908,10 +908,11 @@ def test_pygments_fallbacks(sphinx_build_factory, style_names, keyword_colors) -
assert lines[0].startswith('html[data-theme="light"]')
for mode, color in dict(zip(["light", "dark"], keyword_colors)).items():
regexp = re.compile(
- r'html\[data-theme="' + mode + r'"\].*\.k .*color: ' + color
+ r'html\[data-theme="' + mode + r'"\].*\.k .*color:\s?' + color,
+ re.IGNORECASE,
)
- matches = [regexp.match(line) is not None for line in lines]
- assert sum(matches) == 1
+ matches = [regexp.search(line) is not None for line in lines]
+ assert sum(matches) == 1, f"expected {mode}: {color}\n" + "\n".join(lines)
def test_deprecated_build_html(sphinx_build_factory, file_regression) -> None:
From 7c54d4a635dee71855e517cfc21ea2a8b8dcf014 Mon Sep 17 00:00:00 2001
From: Kayce Basques
Date: Wed, 22 Jan 2025 14:33:50 -0800
Subject: [PATCH 5/5] Add search-as-you-type (inline search results) feature
(#2093)
Fixes #1977
---------
Co-authored-by: Kayce Basques
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
docs/conf.py | 1 +
docs/user_guide/search.rst | 11 ++
src/pydata_sphinx_theme/__init__.py | 6 +
.../assets/scripts/pydata-sphinx-theme.js | 166 ++++++++++++++++++
.../assets/styles/components/_search.scss | 25 ++-
.../theme/pydata_sphinx_theme/layout.html | 9 +
.../theme/pydata_sphinx_theme/theme.conf | 1 +
tests/test_a11y.py | 31 ++++
8 files changed, 248 insertions(+), 2 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index afab8132c..a8682b93e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -217,6 +217,7 @@
"version_match": version_match,
},
# "back_to_top_button": False,
+ "search_as_you_type": True,
}
html_sidebars = {
diff --git a/docs/user_guide/search.rst b/docs/user_guide/search.rst
index 3d1d87c4d..9ec837325 100644
--- a/docs/user_guide/search.rst
+++ b/docs/user_guide/search.rst
@@ -63,3 +63,14 @@ following configuration to your ``conf.py`` file:
html_theme_options = {
"search_bar_text": "Your text here..."
}
+
+Configure the inline search results (search-as-you-type) feature
+----------------------------------------------------------------
+
+Set the ``search_as_you_type`` HTML theme option to ``True``.
+
+.. code:: python
+
+ html_theme_options = {
+ "search_as_you_type": True
+ }
diff --git a/src/pydata_sphinx_theme/__init__.py b/src/pydata_sphinx_theme/__init__.py
index 71a975ad3..92944f4c4 100644
--- a/src/pydata_sphinx_theme/__init__.py
+++ b/src/pydata_sphinx_theme/__init__.py
@@ -241,6 +241,12 @@ def update_and_remove_templates(
"""
app.add_js_file(None, body=js)
+ # Specify whether search-as-you-type should be used or not.
+ search_as_you_type = str(context["theme_search_as_you_type"]).lower()
+ app.add_js_file(
+ None, body=f"DOCUMENTATION_OPTIONS.search_as_you_type = {search_as_you_type};"
+ )
+
# Update version number for the "made with version..." component
context["theme_version"] = __version__
diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
index a861cfdb0..ea01bb7b5 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -261,6 +261,7 @@ var addEventListenerForSearchKeyboard = () => {
// also allow Escape key to hide (but not show) the dynamic search field
else if (document.activeElement === input && /Escape/i.test(event.key)) {
toggleSearchField();
+ resetSearchAsYouTypeResults();
}
},
true,
@@ -332,6 +333,170 @@ var setupSearchButtons = () => {
searchDialog.addEventListener("click", closeDialogOnBackdropClick);
};
+/*******************************************************************************
+ * Inline search results (search-as-you-type)
+ *
+ * Immediately displays search results under the search query textbox.
+ *
+ * The search is conducted by Sphinx's built-in search tools (searchtools.js).
+ * Usually searchtools.js is only available on /search.html but
+ * pydata-sphinx-theme (PST) has been modified to load searchtools.js on every
+ * page. After the user types something into PST's search query textbox,
+ * searchtools.js executes the search and populates the results into
+ * the #search-results container. searchtools.js expects the results container
+ * to have that exact ID.
+ */
+var setupSearchAsYouType = () => {
+ if (!DOCUMENTATION_OPTIONS.search_as_you_type) {
+ return;
+ }
+
+ // Don't interfere with the default search UX on /search.html.
+ if (window.location.pathname.endsWith("/search.html")) {
+ return;
+ }
+
+ // Bail if the Search class is not available. Search-as-you-type is
+ // impossible without that class. layout.html should ensure that
+ // searchtools.js loads.
+ //
+ // Search class is defined in upstream Sphinx:
+ // https://github.com/sphinx-doc/sphinx/blob/6678e357048ea1767daaad68e7e0569786f3b458/sphinx/themes/basic/static/searchtools.js#L181
+ if (!Search) {
+ return;
+ }
+
+ // Destroy the previous search container and create a new one.
+ resetSearchAsYouTypeResults();
+ let timeoutId = null;
+ let lastQuery = "";
+ const searchInput = document.querySelector(
+ "#pst-search-dialog input[name=q]",
+ );
+
+ // Initiate searches whenever the user types stuff in the search modal textbox.
+ searchInput.addEventListener("keyup", () => {
+ const query = searchInput.value;
+
+ // Don't search when there's nothing in the query textbox.
+ if (query === "") {
+ resetSearchAsYouTypeResults(); // Remove previous results.
+ return;
+ }
+
+ // Don't search if there is no detectable change between
+ // the last query and the current query. E.g. the user presses
+ // Tab to start navigating the search results.
+ if (query === lastQuery) {
+ return;
+ }
+
+ // The user has changed the search query. Delete the old results
+ // and start setting up the new container.
+ resetSearchAsYouTypeResults();
+
+ // Debounce so that the search only starts when the user stops typing.
+ const delay_ms = 300;
+ lastQuery = query;
+ if (timeoutId) {
+ window.clearTimeout(timeoutId);
+ }
+ timeoutId = window.setTimeout(() => {
+ Search.performSearch(query);
+ document.querySelector("#search-results").classList.remove("empty");
+ timeoutId = null;
+ }, delay_ms);
+ });
+};
+
+// Delete the old search results container (if it exists) and set up a new one.
+//
+// There is some complexity around ensuring that the search results links are
+// correct because we're extending searchtools.js past its assumed usage.
+// Sphinx assumes that searches are only executed from /search.html and
+// therefore it assumes that all search results links should be relative to
+// the root directory of the website. In our case the search can now execute
+// from any page of the website so we must fix the relative URLs that
+// searchtools.js generates.
+var resetSearchAsYouTypeResults = () => {
+ if (!DOCUMENTATION_OPTIONS.search_as_you_type) {
+ return;
+ }
+ // If a search-as-you-type results container was previously added,
+ // remove it now.
+ let results = document.querySelector("#search-results");
+ if (results) {
+ results.remove();
+ }
+
+ // Create a new search-as-you-type results container.
+ results = document.createElement("section");
+ results.classList.add("empty");
+ // Remove the container element from the tab order. Individual search
+ // results are still focusable.
+ results.tabIndex = -1;
+ // When focus is on a search result, make sure that pressing Escape closes
+ // the search modal.
+ results.addEventListener("keydown", (event) => {
+ if (event.key === "Escape") {
+ event.preventDefault();
+ event.stopPropagation();
+ toggleSearchField();
+ resetSearchAsYouTypeResults();
+ }
+ });
+ // IMPORTANT: The search results container MUST have this exact ID.
+ // searchtools.js is hardcoded to populate into the node with this ID.
+ results.id = "search-results";
+ let modal = document.querySelector("#pst-search-dialog");
+ modal.appendChild(results);
+
+ // Get the relative path back to the root of the website.
+ const root =
+ "URL_ROOT" in DOCUMENTATION_OPTIONS
+ ? DOCUMENTATION_OPTIONS.URL_ROOT // Sphinx v6 and earlier
+ : document.documentElement.dataset.content_root; // Sphinx v7 and later
+
+ // As Sphinx populates the search results, this observer makes sure that
+ // each URL is correct (i.e. doesn't 404).
+ const linkObserver = new MutationObserver(() => {
+ const links = Array.from(
+ document.querySelectorAll("#search-results .search a"),
+ );
+ // Check every link every time because the timing of when new results are
+ // added is unpredictable and it's not an expensive operation.
+ links.forEach((link) => {
+ link.tabIndex = 0; // Use natural tab order for search results.
+ // Don't use the link.href getter because the browser computes the href
+ // as a full URL. We need the relative URL that Sphinx generates.
+ const href = link.getAttribute("href");
+ if (href.startsWith(root)) {
+ // No work needed. The root has already been prepended to the href.
+ return;
+ }
+ link.href = `${root}${href}`;
+ });
+ });
+
+ // The node that linkObserver watches doesn't exist until the user types
+ // something into the search textbox. This second observer (resultsObserver)
+ // just waits for #search-results to exist and then registers
+ // linkObserver on it.
+ let isObserved = false;
+ const resultsObserver = new MutationObserver(() => {
+ if (isObserved) {
+ return;
+ }
+ const container = document.querySelector("#search-results .search");
+ if (!container) {
+ return;
+ }
+ linkObserver.observe(container, { childList: true });
+ isObserved = true;
+ });
+ resultsObserver.observe(results, { childList: true });
+};
+
/*******************************************************************************
* Version Switcher
* Note that this depends on two variables existing that are defined in
@@ -857,6 +1022,7 @@ documentReady(addModeListener);
documentReady(scrollToActive);
documentReady(addTOCInteractivity);
documentReady(setupSearchButtons);
+documentReady(setupSearchAsYouType);
documentReady(setupMobileSidebarKeyboardHandlers);
// Determining whether an element has scrollable content depends on stylesheets,
diff --git a/src/pydata_sphinx_theme/assets/styles/components/_search.scss b/src/pydata_sphinx_theme/assets/styles/components/_search.scss
index 630c6cd1f..cf8d035cf 100644
--- a/src/pydata_sphinx_theme/assets/styles/components/_search.scss
+++ b/src/pydata_sphinx_theme/assets/styles/components/_search.scss
@@ -93,14 +93,17 @@
z-index: $zindex-modal;
top: 30%;
left: 50%;
- transform: translate(-50%, -50%);
+ transform: translate(-50%, -30%);
right: 1rem;
+ margin-bottom: 0;
margin-top: 0.5rem;
width: 90%;
max-width: 800px;
background-color: transparent;
padding: $focus-ring-width;
border: none;
+ flex-direction: column;
+ height: 80vh;
&::backdrop {
background-color: black;
@@ -108,7 +111,7 @@
}
form.bd-search {
- flex-grow: 1;
+ flex-grow: 0;
// Font and input text a bit bigger
svg,
@@ -116,6 +119,24 @@
font-size: var(--pst-font-size-icon);
}
}
+
+ /* In pydata-sphinx-theme.js this container is appended below
+ * the query input node after the user types their search query.
+ * Search results are populated into this container using Sphinx's
+ * built-in, JS-powered local search tools. */
+ #search-results {
+ overflow-y: scroll;
+ background-color: var(--pst-color-background);
+ padding: 1em;
+
+ a {
+ color: var(--pst-color-link);
+ }
+
+ &.empty {
+ display: none;
+ }
+ }
}
}
diff --git a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html
index e062c1806..2b286a6b2 100644
--- a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html
+++ b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html
@@ -37,6 +37,15 @@
{%- if last_updated %}
{%- endif %}
+ {% if pagename == 'search' %}
+ {# Search tools are already loaded on search page. Don't load them twice. #}
+ {% else %}
+ {# Load Sphinx's built-in search tools so that our custom inline search
+ experience can work on any page. #}
+
+
+
+ {% endif %}
{%- endblock extrahead %}
{% block body_tag %}
{# set up with scrollspy to update the toc as we scroll #}
diff --git a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf
index 924ec116c..111691ea2 100644
--- a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf
+++ b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf
@@ -36,6 +36,7 @@ logo =
logo_link =
surface_warnings = True
back_to_top_button = True
+search_as_you_type = False
# Template placement in theme layouts
navbar_start = navbar-logo
diff --git a/tests/test_a11y.py b/tests/test_a11y.py
index 713926118..f0ff97450 100644
--- a/tests/test_a11y.py
+++ b/tests/test_a11y.py
@@ -291,3 +291,34 @@ def test_breadcrumb_expansion(page: Page, url_base: str) -> None:
expect(page.get_by_label("Breadcrumb").get_by_role("list")).to_contain_text(
"Update Sphinx configuration during the build"
)
+
+
+@pytest.mark.a11y
+def test_search_as_you_type(page: Page, url_base: str) -> None:
+ """Search-as-you-type feature should support keyboard navigation.
+
+ When the search-as-you-type (inline search results) feature is enabled,
+ pressing Tab after entering a search query should focus the first inline
+ search result.
+ """
+ page.set_viewport_size({"width": 1440, "height": 720})
+ page.goto(urljoin(url_base, "/examples/kitchen-sink/blocks.html"))
+ # Click the search textbox.
+ searchbox = page.locator("css=.navbar-header-items .search-button__default-text")
+ searchbox.click()
+ # Type a search query.
+ query_input = page.locator("css=#pst-search-dialog input[type=search]")
+ expect(query_input).to_be_visible()
+ query_input.type("test")
+ page.wait_for_timeout(301) # Search execution is debounced for 300 ms.
+ search_results = page.locator("css=#search-results")
+ expect(search_results).to_be_visible()
+ # Navigate with the keyboard.
+ query_input.press("Tab")
+ # Make sure that the first inline search result is focused.
+ actual_focused_content = page.evaluate("document.activeElement.textContent")
+ first_result_selector = "#search-results .search li:first-child a"
+ expected_focused_content = page.evaluate(
+ f"document.querySelector('{first_result_selector}').textContent"
+ )
+ assert actual_focused_content == expected_focused_content