Files
webawesome/docs/assets/scripts/search.js
Cory LaViska b96c3f318b Simplify native styles (#993)
* move print styles

* begin native styles split

* unsplit button styles 😓

* unsplit callout; remove .wa-callout

* merge forms

* remove unused

* remove unused

* unsplit checkbox

* remove old astro config

* remove overflow

* unsplit slider

* fix tooltip position in RTL

* unsplit radio

* move required light DOM styles to <wa-page>

* remove unused file

* remove unused import

* remove

* goodbye

* fix examples

* unsplit dialog

* unsplit select

* remove select

* unsplit input

* unsplit details

* update

* update comment

* update textarea

* combine native docs; improvements

* update

* reorg and fix headings

* fix details summary padding; fixes #684

* update

* fix native details summary padding; fixes #684

* #684

* remove passthrough style nonsense

* it's CSS not JS

* fix details in sidebar

* add spacing in native buttons for icons

* whitespace

* update docs

* remove button group util

* remove shadow folder, add component folder

* layerize

* default border radius

* remove color contrast script from dist

* add term

* layerize themes + color folders

* reorder

* remove radio button; #504

* remove visual tests

* remove visual tests

* remove unused stylesheet

* make search smarter

* add radio styles

* Fix filled textareas

* re-introduce visual tests (with adjustments)

* fix button appearances

* fix textarea focus styles

* re-introduce appearance classes

* remove 'native styles' note from component pages

* fix checked radio styles

* touch up callout styles

* remove errant `.wa-tag`

* scope appearance classes to relevant elements

* more visual test cases

* fix details borders

* minor visual tests reorg

* add `--box-shadow` to buttons

* fix Awesome theme

* use same layer for all themes (allows unset properties to inherit from Default theme)

* fix box-shadow in wa-textarea

* set button box shadow to `initial`

* fix Active theme

* fix Brutalist theme

* fix Glossy theme

* fix Matter theme (mostly)

* fix Playful theme

* fix Premium theme

* fix Shoelac theme

* fix Tailspin theme

* fix custom radio button styles

* fix links to native styles doc

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-05-29 13:10:53 -04:00

227 lines
6.6 KiB
JavaScript

// Search data
const res = await Promise.all([import('https://cdn.jsdelivr.net/npm/lunr/+esm'), fetch('/search.json')]);
const lunr = res[0].default;
const searchData = await res[1].json();
const searchIndex = lunr.Index.load(searchData.searchIndex);
const map = searchData.map;
const searchDebounce = 200;
let searchTimeout;
// We're using Turbo, so references to these elements aren't guaranteed to remain intact
function getElements() {
return {
dialog: document.getElementById('site-search'),
input: document.getElementById('site-search-input'),
results: document.getElementById('site-search-listbox'),
};
}
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
document.addEventListener('keydown', event => {
if (
(event.key === 'k' && (event.metaKey || event.ctrlKey)) ||
(event.key === '/' &&
!event.composedPath().some(el => {
const tag = el?.tagName?.toLowerCase();
return tag === 'textarea' || (tag === 'input' && !['checkbox', 'radio'].includes(el.type));
}))
) {
event.preventDefault();
show();
}
});
// Show the search dialog when clicking on elements with the `data-search` attribute
document.addEventListener('click', event => {
const searchButton = event.target.closest('[data-search]');
if (searchButton) {
show();
}
});
function show() {
const { dialog, input, results } = getElements();
input.addEventListener('input', handleInput);
results.addEventListener('click', handleSelection);
dialog.addEventListener('keydown', handleKeyDown);
dialog.addEventListener('wa-hide', handleClose);
dialog.open = true;
}
function hide() {
const { dialog, input, results } = getElements();
input.removeEventListener('input', handleInput);
results.removeEventListener('click', handleSelection);
dialog.removeEventListener('keydown', handleKeyDown);
dialog.removeEventListener('wa-hide', handleClose);
dialog.open = false;
}
function handleClose() {
const { input } = getElements();
input.value = '';
updateResults();
}
function handleInput() {
const { input } = getElements();
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
}
function handleKeyDown(event) {
const { input, results } = getElements();
// Handle keyboard selections
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
event.preventDefault();
const currentEl = results.querySelector('[data-selected="true"]');
const items = [...results.querySelectorAll('li')];
const index = items.indexOf(currentEl);
let nextEl;
if (items.length === 0) {
return;
}
switch (event.key) {
case 'ArrowUp':
nextEl = items[Math.max(0, index - 1)];
break;
case 'ArrowDown':
nextEl = items[Math.min(items.length - 1, index + 1)];
break;
case 'Home':
nextEl = items[0];
break;
case 'End':
nextEl = items[items.length - 1];
break;
case 'Enter':
currentEl?.querySelector('a')?.click();
break;
}
// Update the selected item
items.forEach(item => {
if (item === nextEl) {
input.setAttribute('aria-activedescendant', item.id);
item.setAttribute('data-selected', 'true');
nextEl.scrollIntoView({ block: 'nearest' });
} else {
item.setAttribute('data-selected', 'false');
}
});
}
}
function handleSelection(event) {
const link = event.target.closest('a');
if (link) {
event.preventDefault();
hide();
if (window.Turbo) {
Turbo.visit(link.href);
} else {
location.href = link.href;
}
}
}
// Queries the search index and updates the results
async function updateResults(query = '') {
const { dialog, input, results } = getElements();
try {
const hasQuery = query.length > 0;
let matches = [];
if (hasQuery) {
// Track seen refs to avoid duplicates
const seenRefs = new Set();
// Start with a standard search to get the best "exact match" result
searchIndex.search(`${query}`).forEach(match => {
matches.push(match);
seenRefs.add(match.ref);
});
// Add wildcard matches if not already included
searchIndex.search(`${query}*`).forEach(match => {
if (!seenRefs.has(match.ref)) {
matches.push(match);
seenRefs.add(match.ref);
}
});
// Add fuzzy search matches last
const fuzzyTokens = query
.split(' ')
.map(term => `${term}~1`)
.join(' ');
searchIndex.search(fuzzyTokens).forEach(match => {
if (!seenRefs.has(match.ref)) {
matches.push(match);
seenRefs.add(match.ref);
}
});
}
const hasResults = hasQuery && matches.length > 0;
dialog.classList.toggle('has-results', hasQuery && hasResults);
dialog.classList.toggle('no-results', hasQuery && !hasResults);
input.setAttribute('aria-activedescendant', '');
results.innerHTML = '';
matches.forEach((match, index) => {
const page = map[match.ref];
const li = document.createElement('li');
const a = document.createElement('a');
const displayTitle = page.title ?? '';
const displayDescription = page.description ?? '';
const displayUrl = page.url.replace(/^\//, '');
let icon = 'file-text';
li.classList.add('site-search-result');
li.setAttribute('role', 'option');
li.setAttribute('id', `search-result-item-${match.ref}`);
li.setAttribute('data-selected', index === 0 ? 'true' : 'false');
if (page.url === '/') icon = 'home';
if (page.url.startsWith('/docs/utilities/native')) icon = 'code';
if (page.url.startsWith('/docs/components')) icon = 'puzzle-piece';
if (page.url.startsWith('/docs/theme') || page.url.startsWith('/docs/restyle')) icon = 'palette';
a.href = page.url;
a.innerHTML = `
<div class="site-search-result-icon" aria-hidden="true">
<wa-icon name="${icon}"></wa-icon>
</div>
<div class="site-search-result-details">
<div class="site-search-result-title"></div>
<div class="site-search-result-description"></div>
<div class="site-search-result-url"></div>
</div>
`;
a.querySelector('.site-search-result-title').textContent = displayTitle;
a.querySelector('.site-search-result-description').textContent = displayDescription;
a.querySelector('.site-search-result-url').textContent = displayUrl;
li.appendChild(a);
results.appendChild(li);
});
} catch {
// Ignore query errors as the user types
}
}