Skip to content

Commit 8539bcc

Browse files
committed
fix merge conflict w/ upstream/main
2 parents ec46d15 + 7c54d4a commit 8539bcc

File tree

17 files changed

+306
-51
lines changed

17 files changed

+306
-51
lines changed

.pre-commit-config.yaml

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ default_language_version:
1313

1414
repos:
1515
- repo: "https://github.com/pycontribs/mirrors-prettier"
16-
rev: v3.3.3
16+
rev: v3.4.2
1717
hooks:
1818
- id: prettier
1919
# Exclude the HTML, since it doesn't understand Jinja2
@@ -22,20 +22,20 @@ repos:
2222
exclude: .+\.html|webpack\.config\.js|tests/test_a11y/
2323

2424
- repo: "https://github.com/astral-sh/ruff-pre-commit"
25-
rev: "v0.8.1"
25+
rev: "v0.8.6"
2626
hooks:
2727
- id: ruff
2828
args: [--exit-non-zero-on-fix]
2929
- id: ruff-format
3030

3131
- repo: "https://github.com/asottile/pyupgrade"
32-
rev: v3.19.0
32+
rev: v3.19.1
3333
hooks:
3434
- id: pyupgrade
3535
args: [--py37-plus]
3636

3737
- repo: "https://github.com/Riverside-Healthcare/djLint"
38-
rev: v1.36.3
38+
rev: v1.36.4
3939
hooks:
4040
- id: djlint-jinja
4141
types_or: ["html"]
@@ -56,7 +56,7 @@ repos:
5656
- id: remove-metadata
5757

5858
- repo: "https://github.com/thibaudcolas/pre-commit-stylelint"
59-
rev: v16.11.0
59+
rev: v16.12.0
6060
hooks:
6161
- id: stylelint
6262
# automatically fix .scss files where possible

docs/_static/switcher.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
"url": "https://pydata-sphinx-theme.readthedocs.io/en/latest/"
55
},
66
{
7-
"name": "0.16.1rc0",
8-
"version": "v0.16.1rc0",
9-
"url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.16.1rc0/"
7+
"name": "0.16.1 (stable)",
8+
"version": "v0.16.1",
9+
"url": "https://pydata-sphinx-theme.readthedocs.io/en/stable/",
10+
"preferred": true
1011
},
1112
{
12-
"name": "0.16.0 (stable)",
13+
"name": "0.16.0",
1314
"version": "v0.16.0",
14-
"url": "https://pydata-sphinx-theme.readthedocs.io/en/stable/",
15-
"preferred": true
15+
"url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.16.0/"
1616
},
1717
{
1818
"name": "0.15.4",

docs/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@
217217
"version_match": version_match,
218218
},
219219
# "back_to_top_button": False,
220+
"search_as_you_type": True,
220221
}
221222

222223
html_sidebars = {

docs/user_guide/search.rst

+11
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,14 @@ following configuration to your ``conf.py`` file:
6363
html_theme_options = {
6464
"search_bar_text": "Your text here..."
6565
}
66+
67+
Configure the inline search results (search-as-you-type) feature
68+
----------------------------------------------------------------
69+
70+
Set the ``search_as_you_type`` HTML theme option to ``True``.
71+
72+
.. code:: python
73+
74+
html_theme_options = {
75+
"search_as_you_type": True
76+
}

package-lock.json

+5-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/pydata_sphinx_theme/__init__.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from . import edit_this_page, logo, pygments, short_link, toctree, translator, utils
1818

1919

20-
__version__ = "0.16.1rc0"
20+
__version__ = "0.16.1"
2121

2222

2323
def update_config(app):
@@ -241,6 +241,12 @@ def update_and_remove_templates(
241241
"""
242242
app.add_js_file(None, body=js)
243243

244+
# Specify whether search-as-you-type should be used or not.
245+
search_as_you_type = str(context["theme_search_as_you_type"]).lower()
246+
app.add_js_file(
247+
None, body=f"DOCUMENTATION_OPTIONS.search_as_you_type = {search_as_you_type};"
248+
)
249+
244250
# Update version number for the "made with version..." component
245251
context["theme_version"] = __version__
246252

src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js

+166
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ var addEventListenerForSearchKeyboard = () => {
261261
// also allow Escape key to hide (but not show) the dynamic search field
262262
else if (document.activeElement === input && /Escape/i.test(event.key)) {
263263
toggleSearchField();
264+
resetSearchAsYouTypeResults();
264265
}
265266
},
266267
true,
@@ -332,6 +333,170 @@ var setupSearchButtons = () => {
332333
searchDialog.addEventListener("click", closeDialogOnBackdropClick);
333334
};
334335

336+
/*******************************************************************************
337+
* Inline search results (search-as-you-type)
338+
*
339+
* Immediately displays search results under the search query textbox.
340+
*
341+
* The search is conducted by Sphinx's built-in search tools (searchtools.js).
342+
* Usually searchtools.js is only available on /search.html but
343+
* pydata-sphinx-theme (PST) has been modified to load searchtools.js on every
344+
* page. After the user types something into PST's search query textbox,
345+
* searchtools.js executes the search and populates the results into
346+
* the #search-results container. searchtools.js expects the results container
347+
* to have that exact ID.
348+
*/
349+
var setupSearchAsYouType = () => {
350+
if (!DOCUMENTATION_OPTIONS.search_as_you_type) {
351+
return;
352+
}
353+
354+
// Don't interfere with the default search UX on /search.html.
355+
if (window.location.pathname.endsWith("/search.html")) {
356+
return;
357+
}
358+
359+
// Bail if the Search class is not available. Search-as-you-type is
360+
// impossible without that class. layout.html should ensure that
361+
// searchtools.js loads.
362+
//
363+
// Search class is defined in upstream Sphinx:
364+
// https://github.com/sphinx-doc/sphinx/blob/6678e357048ea1767daaad68e7e0569786f3b458/sphinx/themes/basic/static/searchtools.js#L181
365+
if (!Search) {
366+
return;
367+
}
368+
369+
// Destroy the previous search container and create a new one.
370+
resetSearchAsYouTypeResults();
371+
let timeoutId = null;
372+
let lastQuery = "";
373+
const searchInput = document.querySelector(
374+
"#pst-search-dialog input[name=q]",
375+
);
376+
377+
// Initiate searches whenever the user types stuff in the search modal textbox.
378+
searchInput.addEventListener("keyup", () => {
379+
const query = searchInput.value;
380+
381+
// Don't search when there's nothing in the query textbox.
382+
if (query === "") {
383+
resetSearchAsYouTypeResults(); // Remove previous results.
384+
return;
385+
}
386+
387+
// Don't search if there is no detectable change between
388+
// the last query and the current query. E.g. the user presses
389+
// Tab to start navigating the search results.
390+
if (query === lastQuery) {
391+
return;
392+
}
393+
394+
// The user has changed the search query. Delete the old results
395+
// and start setting up the new container.
396+
resetSearchAsYouTypeResults();
397+
398+
// Debounce so that the search only starts when the user stops typing.
399+
const delay_ms = 300;
400+
lastQuery = query;
401+
if (timeoutId) {
402+
window.clearTimeout(timeoutId);
403+
}
404+
timeoutId = window.setTimeout(() => {
405+
Search.performSearch(query);
406+
document.querySelector("#search-results").classList.remove("empty");
407+
timeoutId = null;
408+
}, delay_ms);
409+
});
410+
};
411+
412+
// Delete the old search results container (if it exists) and set up a new one.
413+
//
414+
// There is some complexity around ensuring that the search results links are
415+
// correct because we're extending searchtools.js past its assumed usage.
416+
// Sphinx assumes that searches are only executed from /search.html and
417+
// therefore it assumes that all search results links should be relative to
418+
// the root directory of the website. In our case the search can now execute
419+
// from any page of the website so we must fix the relative URLs that
420+
// searchtools.js generates.
421+
var resetSearchAsYouTypeResults = () => {
422+
if (!DOCUMENTATION_OPTIONS.search_as_you_type) {
423+
return;
424+
}
425+
// If a search-as-you-type results container was previously added,
426+
// remove it now.
427+
let results = document.querySelector("#search-results");
428+
if (results) {
429+
results.remove();
430+
}
431+
432+
// Create a new search-as-you-type results container.
433+
results = document.createElement("section");
434+
results.classList.add("empty");
435+
// Remove the container element from the tab order. Individual search
436+
// results are still focusable.
437+
results.tabIndex = -1;
438+
// When focus is on a search result, make sure that pressing Escape closes
439+
// the search modal.
440+
results.addEventListener("keydown", (event) => {
441+
if (event.key === "Escape") {
442+
event.preventDefault();
443+
event.stopPropagation();
444+
toggleSearchField();
445+
resetSearchAsYouTypeResults();
446+
}
447+
});
448+
// IMPORTANT: The search results container MUST have this exact ID.
449+
// searchtools.js is hardcoded to populate into the node with this ID.
450+
results.id = "search-results";
451+
let modal = document.querySelector("#pst-search-dialog");
452+
modal.appendChild(results);
453+
454+
// Get the relative path back to the root of the website.
455+
const root =
456+
"URL_ROOT" in DOCUMENTATION_OPTIONS
457+
? DOCUMENTATION_OPTIONS.URL_ROOT // Sphinx v6 and earlier
458+
: document.documentElement.dataset.content_root; // Sphinx v7 and later
459+
460+
// As Sphinx populates the search results, this observer makes sure that
461+
// each URL is correct (i.e. doesn't 404).
462+
const linkObserver = new MutationObserver(() => {
463+
const links = Array.from(
464+
document.querySelectorAll("#search-results .search a"),
465+
);
466+
// Check every link every time because the timing of when new results are
467+
// added is unpredictable and it's not an expensive operation.
468+
links.forEach((link) => {
469+
link.tabIndex = 0; // Use natural tab order for search results.
470+
// Don't use the link.href getter because the browser computes the href
471+
// as a full URL. We need the relative URL that Sphinx generates.
472+
const href = link.getAttribute("href");
473+
if (href.startsWith(root)) {
474+
// No work needed. The root has already been prepended to the href.
475+
return;
476+
}
477+
link.href = `${root}${href}`;
478+
});
479+
});
480+
481+
// The node that linkObserver watches doesn't exist until the user types
482+
// something into the search textbox. This second observer (resultsObserver)
483+
// just waits for #search-results to exist and then registers
484+
// linkObserver on it.
485+
let isObserved = false;
486+
const resultsObserver = new MutationObserver(() => {
487+
if (isObserved) {
488+
return;
489+
}
490+
const container = document.querySelector("#search-results .search");
491+
if (!container) {
492+
return;
493+
}
494+
linkObserver.observe(container, { childList: true });
495+
isObserved = true;
496+
});
497+
resultsObserver.observe(results, { childList: true });
498+
};
499+
335500
/*******************************************************************************
336501
* Version Switcher
337502
* Note that this depends on two variables existing that are defined in
@@ -857,6 +1022,7 @@ documentReady(addModeListener);
8571022
documentReady(scrollToActive);
8581023
documentReady(addTOCInteractivity);
8591024
documentReady(setupSearchButtons);
1025+
documentReady(setupSearchAsYouType);
8601026
documentReady(setupMobileSidebarKeyboardHandlers);
8611027

8621028
// Determining whether an element has scrollable content depends on stylesheets,

src/pydata_sphinx_theme/assets/styles/abstracts/_accessibility.scss

+4-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@
7070
$rgb-col: map.merge(
7171
$rgb-col,
7272
(
73-
$channel:
74-
math.pow(math.div((math.div($value, 255) + 0.055), 1.055), 2.4),
73+
$channel: math.pow(
74+
math.div((math.div($value, 255) + 0.055), 1.055),
75+
2.4
76+
),
7577
)
7678
);
7779
}

src/pydata_sphinx_theme/assets/styles/base/_base.scss

+7-4
Original file line numberDiff line numberDiff line change
@@ -72,33 +72,36 @@ a {
7272
line-height: 1.15;
7373
}
7474

75+
// From 0.16.1, the preferred variable for headings is --pst-color-heading
76+
// if you have --pst-heading-color, this variable will be used, otherwise the default
77+
// --pst-color-heading will be used.
7578
h1 {
7679
@extend %heading-style;
7780

7881
margin-top: 0;
7982
font-size: var(--pst-font-size-h1);
80-
color: var(--pst-color-heading);
83+
color: var(--pst-heading-color, --pst-color-heading);
8184
}
8285

8386
h2 {
8487
@extend %heading-style;
8588

8689
font-size: var(--pst-font-size-h2);
87-
color: var(--pst-color-heading);
90+
color: var(--pst-heading-color, --pst-color-heading);
8891
}
8992

9093
h3 {
9194
@extend %heading-style;
9295

9396
font-size: var(--pst-font-size-h3);
94-
color: var(--pst-color-heading);
97+
color: var(--pst-heading-color, --pst-color-heading);
9598
}
9699

97100
h4 {
98101
@extend %heading-style;
99102

100103
font-size: var(--pst-font-size-h4);
101-
color: var(--pst-color-heading);
104+
color: var(--pst-heading-color, --pst-color-heading);
102105
}
103106

104107
h5 {

0 commit comments

Comments
 (0)