Skip to content

Commit

Permalink
Improve fullscreen UX (keep focus on reset, fix reset position, blur …
Browse files Browse the repository at this point in the history
…on Esc)

The demo has a new "fullscreen" version where, if there is any
focus-within inside the typesense-minibar element, it will make the
element cover the entire viewport.

In doing this, a few issues were uncovered:

* When pressing the "reset" button, the fullscreen mode unexpectedly
  exited itself from time "mousedown" happens, even before it is let
  go and turned into a click. This is because the reset icon is
  an unfocusable SVG, so by default as soon as mousedown happens,
  the focus is no longer in the input field, and since the SVG isn't
  focussable, the focus returns briefly to the `<main>` or `<body>`.

  In the default way the element works (i.e. not fullscreen), this
  is fairly harmless because by the time the mouseup happens, the
  cursor is still on the reset button, "click" fires on the icon,
  our event handler is called, and our callback does `input.focus()`
  to bring focus back. The only downside so far was a brief flashing
  of the non-focus styles for the input field between mouse-down and
  mouse-up.

  In the fullscreen demo, after mouse-down on the reset icon, this
  moved focus to the main element, thus defocusing the form, thus
  the fullscreen styles stop applying, thus the whole form is now
  in a different position in the viewport, thus when you mouse-up,
  the click doesn't happen on the icon, it happens on a blank spot
  in the page. This also means our event handler never fired, so
  the input field remained non-empty, the listbox remained open,
  and focus does not return.

  Fix this by setting tabindex="-1" on the reset icon. This means it
  remains outside the sequential keyboard navigation (same as before),
  but when you click it, it is capable of receiving focus. This means
  `:focus-within` remains applying, the fullscreen styles remain active,
  and so when you mouse-up, the cursor is still on the same icon, so
  a "click" will go it it, and our event handler does what it should
  (empty input field, close listbox, move focus to input element).

* The magnify icon is correctly positioned as "half of the input field
  from the top" relative to the `<form>`, which works regarldess of
  how tall the parent element is.
  The reset button, however, was positioned "in the vertical middle"
  of the parent. This is identical in the common case since the `<form>`
  element is exactly as tall as just the input element, there is
  nothing else there. But, in this fullscreen mode, the element becomes
  much taller.

  Fix the CSS of the reset button to calculate its position from the
  top down, without using any "50% - stuff" calculation.

* When pressing Esc, we previously only closed the listbox results,
  but kept focus in the input field. This was fairly harmless, but
  for the fullscreen case, this means it is impossible to get out
  of it with keyboard only, since the entire page is covered. The
  only other way to close the fullscreen mode was to click somewhere
  outside the input field. Now Esc works as well, by explicitly
  de-focussing (blur) the input field, thus returning focus back to
  where it was (probably the `<body>` or `<main>` element).
  • Loading branch information
Krinkle committed Aug 6, 2024
1 parent f328919 commit 85f7b1a
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
```

```html
<script defer type="module" src="typesense-minibar.js"></script>
<link rel="stylesheet" href="typesense-minibar.css">
<script defer type="module" src="typesense-minibar.js"></script>
```

Distribution:
Expand Down
2 changes: 1 addition & 1 deletion demo/compare--docsearch-3.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<body>
<header>
<h2>DocSearch.js v3</h2>
<div id="searchbar">
<div id="searchbar"></div>
</header>


Expand Down
4 changes: 2 additions & 2 deletions demo/demo-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

header {
background: #390f39;
color: #fff;
color: #fbdbfb;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);

display: flex;
Expand All @@ -19,6 +19,7 @@ header > * {

.demo-logo {
background-image: url(../assets/logo-text-dark.svg);
color: #fff;
}

header .tsmb-form,
Expand All @@ -27,7 +28,6 @@ header typesense-minibar {
--tsmb-color-base30: var(--tsmb-color-primary90);
--tsmb-color-base50: #c090c0;
--tsmb-color-base90: #c090c0;
--tsmb-size-listbox-width: calc(min(50rem, 80vw));
}
header .tsmb-form,
header typesense-minibar form {
Expand Down
38 changes: 35 additions & 3 deletions demo/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ main,
text-indent: -9999px;
overflow: hidden;
}
.demo-caption {
flex-grow: 1;
}
.demo-head-right .demo-caption {
text-align: right;
}

/* Layout: Push demo-stylecontrol to the bottom. */
body {
Expand All @@ -53,18 +59,44 @@ main {
flex-grow: 1;
}

.demo-custom-form {
.tsmb-form.demo-form-size {
--tsmb-size-input: 2rem;
--tsmb-size-sm: 0.5rem;
--tsmb-size-radius: 0px;
--tsmb-size-listbox-width: calc(min(50rem, 80vw));
width: 30rem;
}

html:has(.demo-form-overlay:focus-within) {
/* disable scrollbar */
overflow: hidden;
}
typesense-minibar.demo-form-overlay:focus-within {
z-index: 1;
position: fixed;
inset: 0px;
background: #f4f4f4;
padding: var(--tsmb-size-sm) 0;
}
typesense-minibar.demo-form-overlay:focus-within form {
width: 30rem;
margin: 0 auto;
}

.demo-right-form {

.demo-head-right typesense-minibar,
.demo-head-right .tsmb-form {
margin-left: auto;
--tsmb-size-listbox-right: 0;
}

.demo-stylecontrol fieldset {
background: #fff;
}
@media (min-height: 600px) {
.demo-stylecontrol {
position: sticky;
bottom: 0;
background: #f4f4f4;
box-shadow: 0 -1px 2px rgba(0,0,0,0.24)
}
}
47 changes: 28 additions & 19 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>minibar | Demo</title>
<script defer type="module" src="../typesense-minibar.js"></script>
<link rel="icon" href="../assets/logo.svg">
<link rel="stylesheet" href="../typesense-minibar.css">
<link rel="stylesheet" href="../typesense-minibar-foot.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="demo-theme.css">
<script defer type="module" src="../typesense-minibar.js"></script>
<body>
<header>
<a href="../" class="demo-logo">Minibar</a>
Expand All @@ -19,42 +19,51 @@
<input type="hidden" name="sites" value="qunitjs.com">
</form>
</typesense-minibar>
<span class="demo-caption">Themed (Web Component)</span>
</header>
<header>
<span class="demo-logo"></span>
<!-- HTML class, with data-group=true, and right aligned. -->
<form role="search" class="tsmb-form demo-right-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</header>
<header>
<span class="demo-logo"></span>
<!-- Web Component class, with data-group=true, and right aligned. -->
<typesense-minibar class="demo-right-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true">
<header class="demo-head-right">
<a href="../" class="demo-logo"></a>
<span class="demo-caption">Themed (Web Component, group=true, right aligned)</span>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</typesense-minibar>
</header>
<header class="demo-head-right">
<a href="../" class="demo-logo"></a>
<span class="demo-caption">Themed (HTML class, group=true, right aligned)</span>
<form role="search" class="tsmb-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</header>
<main>
<h2>Web Component</h2>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true">
<h2>Web Component: Default</h2>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="qunitjs.com">
</form>
</typesense-minibar>

<h2>HTML class</h2>
<form role="search" class="tsmb-form" data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" action="https://duckduckgo.com">
<h2>HTML class: Default</h2>
<form role="search" class="tsmb-form" data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="qunitjs.com">
</form>

<h2>Style API (customize --tsmb-size-* variables)</h2>
<form role="search" class="tsmb-form demo-custom-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<h2>Style API (fullscreen overlay, group=true, Web Component)</h2>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" class="demo-form-overlay">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</typesense-minibar>

<h2>Style API (customized --tsmb-size-* variables)</h2>
<form role="search" class="tsmb-form demo-form-size" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
Expand Down
21 changes: 18 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,30 @@
<title>typesense-minibar</title>
<link rel="icon" href="./assets/logo.svg">
<link rel="stylesheet" href="./demo/demo.css">
<link rel="stylesheet" href="../typesense-minibar.css">
<script defer type="module" src="../typesense-minibar.js"></script>
<body>
<main>
<p align="center"><a href="https://github.com/jquery/typesense-minibar/" title="Browse repository"><img src="./assets/logo-text.svg" height="100" alt="minibar"></a></p>
<p><strong>minibar</strong> is a fast 2kB autocomplete search bar. Alternative to Algolia DocSearch, InstantSearch, autocomplete-js, and typesense-js.</p>
<h2>See also</h2>

<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-group="true" data-foot="true">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</typesense-minibar>

<h2>Pages</h2>
<ul>
<li><a href="https://github.com/jquery/typesense-minibar/">Browse repository</a></li>
<li><a href="./demo/">Demos & Comparisons</a></li>
<li><a href="./test/">QUnit test suite</a></li>
<li><a href="./coverage/">Test coverage report</a></li>
<li><a href="./demo/">Demo</a></li>
</ul>

<h2>External link</h2>
<ul>
<li><a href="https://github.com/jquery/typesense-minibar/">Documentation and view source</a></li>
</ul>
</main>
</body>
4 changes: 2 additions & 2 deletions typesense-minibar.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ typesense-minibar form::before {
content: '';
background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='654 -372 1664 1664' width='20' height='20'><path d='M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5 C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5 C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342 c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332 s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225 S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z'/></svg>") 0 50% / contain no-repeat;
position: absolute;
top: calc(var(--tsmb-size-sm) + var(--tsmb-size-edge));
top: calc(var(--tsmb-size-edge) + var(--tsmb-size-sm));
left: var(--tsmb-size-sm);
width: var(--tsmb-size-base);
height: var(--tsmb-size-input);
Expand All @@ -106,7 +106,7 @@ typesense-minibar form::before {
.tsmb-icon-close {
box-sizing: border-box;
position: absolute;
top: calc(50% - var(--tsmb-size-base)/2);
top: calc(var(--tsmb-size-edge) + var(--tsmb-size-sm) + (var(--tsmb-size-input)/2) - (var(--tsmb-size-base)/2));
right: var(--tsmb-size-base);
width: var(--tsmb-size-base);
height: var(--tsmb-size-base);
Expand Down
7 changes: 5 additions & 2 deletions typesense-minibar.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ globalThis.tsminibar = function tsminibar (form, dataset = form.dataset) {
if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
if (e.code === 'ArrowDown') moveCursor(1);
if (e.code === 'ArrowUp') moveCursor(-1);
if (e.code === 'Escape') close();
if (e.code === 'Escape') {
close();
input.blur();
}
if (e.code === 'Enter') {
const url = state.hits[state.cursor]?.url;
if (url) location.href = url;
Expand All @@ -71,7 +74,7 @@ globalThis.tsminibar = function tsminibar (form, dataset = form.dataset) {
form.addEventListener('submit', function (e) {
e.preventDefault();
});
form.insertAdjacentHTML('beforeend', '<svg viewBox="0 0 12 12" width="20" height="20" aria-hidden="true" class="tsmb-icon-close" style="display: none;"><path d="M9 3L3 9M3 3L9 9"/></svg>');
form.insertAdjacentHTML('beforeend', '<svg viewBox="0 0 12 12" width="20" height="20" aria-hidden="true" tabindex="-1" class="tsmb-icon-close" style="display: none;"><path d="M9 3L3 9M3 3L9 9"/></svg>');
form.querySelector('.tsmb-icon-close').addEventListener('click', function () {
input.value = '';
state.hits = [];
Expand Down

0 comments on commit 85f7b1a

Please sign in to comment.