Ran prettier

This commit is contained in:
Simon Willison 2025-10-20 16:03:22 -07:00
commit 9e5c64c3de

View file

@ -1,7 +1,7 @@
class NavigationSearch extends HTMLElement { class NavigationSearch extends HTMLElement {
constructor() { constructor() {
super(); super();
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: "open" });
this.selectedIndex = -1; this.selectedIndex = -1;
this.matches = []; this.matches = [];
this.debounceTimer = null; this.debounceTimer = null;
@ -186,42 +186,43 @@ class NavigationSearch extends HTMLElement {
} }
setupEventListeners() { setupEventListeners() {
const dialog = this.shadowRoot.querySelector('dialog'); const dialog = this.shadowRoot.querySelector("dialog");
const input = this.shadowRoot.querySelector('.search-input'); const input = this.shadowRoot.querySelector(".search-input");
const resultsContainer = this.shadowRoot.querySelector('.results-container'); const resultsContainer =
this.shadowRoot.querySelector(".results-container");
// Global keyboard listener for "/" // Global keyboard listener for "/"
document.addEventListener('keydown', (e) => { document.addEventListener("keydown", (e) => {
if (e.key === '/' && !this.isInputFocused() && !dialog.open) { if (e.key === "/" && !this.isInputFocused() && !dialog.open) {
e.preventDefault(); e.preventDefault();
this.openMenu(); this.openMenu();
} }
}); });
// Input event // Input event
input.addEventListener('input', (e) => { input.addEventListener("input", (e) => {
this.handleSearch(e.target.value); this.handleSearch(e.target.value);
}); });
// Keyboard navigation // Keyboard navigation
input.addEventListener('keydown', (e) => { input.addEventListener("keydown", (e) => {
if (e.key === 'ArrowDown') { if (e.key === "ArrowDown") {
e.preventDefault(); e.preventDefault();
this.moveSelection(1); this.moveSelection(1);
} else if (e.key === 'ArrowUp') { } else if (e.key === "ArrowUp") {
e.preventDefault(); e.preventDefault();
this.moveSelection(-1); this.moveSelection(-1);
} else if (e.key === 'Enter') { } else if (e.key === "Enter") {
e.preventDefault(); e.preventDefault();
this.selectCurrentItem(); this.selectCurrentItem();
} else if (e.key === 'Escape') { } else if (e.key === "Escape") {
this.closeMenu(); this.closeMenu();
} }
}); });
// Click on result item // Click on result item
resultsContainer.addEventListener('click', (e) => { resultsContainer.addEventListener("click", (e) => {
const item = e.target.closest('.result-item'); const item = e.target.closest(".result-item");
if (item) { if (item) {
const index = parseInt(item.dataset.index); const index = parseInt(item.dataset.index);
this.selectItem(index); this.selectItem(index);
@ -229,7 +230,7 @@ class NavigationSearch extends HTMLElement {
}); });
// Close on backdrop click // Close on backdrop click
dialog.addEventListener('click', (e) => { dialog.addEventListener("click", (e) => {
if (e.target === dialog) { if (e.target === dialog) {
this.closeMenu(); this.closeMenu();
} }
@ -241,21 +242,22 @@ class NavigationSearch extends HTMLElement {
isInputFocused() { isInputFocused() {
const activeElement = document.activeElement; const activeElement = document.activeElement;
return activeElement && ( return (
activeElement.tagName === 'INPUT' || activeElement &&
activeElement.tagName === 'TEXTAREA' || (activeElement.tagName === "INPUT" ||
activeElement.isContentEditable activeElement.tagName === "TEXTAREA" ||
activeElement.isContentEditable)
); );
} }
loadInitialData() { loadInitialData() {
const itemsAttr = this.getAttribute('items'); const itemsAttr = this.getAttribute("items");
if (itemsAttr) { if (itemsAttr) {
try { try {
this.allItems = JSON.parse(itemsAttr); this.allItems = JSON.parse(itemsAttr);
this.matches = this.allItems; this.matches = this.allItems;
} catch (e) { } catch (e) {
console.error('Failed to parse items attribute:', e); console.error("Failed to parse items attribute:", e);
this.allItems = []; this.allItems = [];
this.matches = []; this.matches = [];
} }
@ -266,7 +268,7 @@ class NavigationSearch extends HTMLElement {
clearTimeout(this.debounceTimer); clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => { this.debounceTimer = setTimeout(() => {
const url = this.getAttribute('url'); const url = this.getAttribute("url");
if (url) { if (url) {
// Fetch from API // Fetch from API
@ -287,7 +289,7 @@ class NavigationSearch extends HTMLElement {
this.selectedIndex = this.matches.length > 0 ? 0 : -1; this.selectedIndex = this.matches.length > 0 ? 0 : -1;
this.renderResults(); this.renderResults();
} catch (e) { } catch (e) {
console.error('Failed to fetch search results:', e); console.error("Failed to fetch search results:", e);
this.matches = []; this.matches = [];
this.renderResults(); this.renderResults();
} }
@ -298,9 +300,10 @@ class NavigationSearch extends HTMLElement {
this.matches = []; this.matches = [];
} else { } else {
const lowerQuery = query.toLowerCase(); const lowerQuery = query.toLowerCase();
this.matches = (this.allItems || []).filter(item => this.matches = (this.allItems || []).filter(
(item) =>
item.name.toLowerCase().includes(lowerQuery) || item.name.toLowerCase().includes(lowerQuery) ||
item.url.toLowerCase().includes(lowerQuery) item.url.toLowerCase().includes(lowerQuery),
); );
} }
this.selectedIndex = this.matches.length > 0 ? 0 : -1; this.selectedIndex = this.matches.length > 0 ? 0 : -1;
@ -308,18 +311,22 @@ class NavigationSearch extends HTMLElement {
} }
renderResults() { renderResults() {
const container = this.shadowRoot.querySelector('.results-container'); const container = this.shadowRoot.querySelector(".results-container");
const input = this.shadowRoot.querySelector('.search-input'); const input = this.shadowRoot.querySelector(".search-input");
if (this.matches.length === 0) { if (this.matches.length === 0) {
const message = input.value.trim() ? 'No results found' : 'Start typing to search...'; const message = input.value.trim()
? "No results found"
: "Start typing to search...";
container.innerHTML = `<div class="no-results">${message}</div>`; container.innerHTML = `<div class="no-results">${message}</div>`;
return; return;
} }
container.innerHTML = this.matches.map((match, index) => ` container.innerHTML = this.matches
.map(
(match, index) => `
<div <div
class="result-item ${index === this.selectedIndex ? 'selected' : ''}" class="result-item ${index === this.selectedIndex ? "selected" : ""}"
data-index="${index}" data-index="${index}"
role="option" role="option"
aria-selected="${index === this.selectedIndex}" aria-selected="${index === this.selectedIndex}"
@ -329,13 +336,15 @@ class NavigationSearch extends HTMLElement {
<div class="result-url">${this.escapeHtml(match.url)}</div> <div class="result-url">${this.escapeHtml(match.url)}</div>
</div> </div>
</div> </div>
`).join(''); `,
)
.join("");
// Scroll selected item into view // Scroll selected item into view
if (this.selectedIndex >= 0) { if (this.selectedIndex >= 0) {
const selectedItem = container.children[this.selectedIndex]; const selectedItem = container.children[this.selectedIndex];
if (selectedItem) { if (selectedItem) {
selectedItem.scrollIntoView({ block: 'nearest' }); selectedItem.scrollIntoView({ block: "nearest" });
} }
} }
} }
@ -358,11 +367,13 @@ class NavigationSearch extends HTMLElement {
const match = this.matches[index]; const match = this.matches[index];
if (match) { if (match) {
// Dispatch custom event // Dispatch custom event
this.dispatchEvent(new CustomEvent('select', { this.dispatchEvent(
new CustomEvent("select", {
detail: match, detail: match,
bubbles: true, bubbles: true,
composed: true composed: true,
})); }),
);
// Navigate to URL // Navigate to URL
window.location.href = match.url; window.location.href = match.url;
@ -372,11 +383,11 @@ class NavigationSearch extends HTMLElement {
} }
openMenu() { openMenu() {
const dialog = this.shadowRoot.querySelector('dialog'); const dialog = this.shadowRoot.querySelector("dialog");
const input = this.shadowRoot.querySelector('.search-input'); const input = this.shadowRoot.querySelector(".search-input");
dialog.showModal(); dialog.showModal();
input.value = ''; input.value = "";
input.focus(); input.focus();
// Reset state - start with no items shown // Reset state - start with no items shown
@ -386,16 +397,16 @@ class NavigationSearch extends HTMLElement {
} }
closeMenu() { closeMenu() {
const dialog = this.shadowRoot.querySelector('dialog'); const dialog = this.shadowRoot.querySelector("dialog");
dialog.close(); dialog.close();
} }
escapeHtml(text) { escapeHtml(text) {
const div = document.createElement('div'); const div = document.createElement("div");
div.textContent = text; div.textContent = text;
return div.innerHTML; return div.innerHTML;
} }
} }
// Register the custom element // Register the custom element
customElements.define('navigation-search', NavigationSearch); customElements.define("navigation-search", NavigationSearch);