feat(usability): update functions for search (#774)

* feat(usability): update functions for search

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* perf: slightly cleaner variables

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
This commit is contained in:
Aaron Pham 2024-01-31 12:38:42 -05:00 committed by GitHub
parent fee3ef9b3a
commit 50bb1ffd8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 80 additions and 32 deletions

View File

@ -86,7 +86,6 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const searchIcon = document.getElementById("search-icon") const searchIcon = document.getElementById("search-icon")
const searchBar = document.getElementById("search-bar") as HTMLInputElement | null const searchBar = document.getElementById("search-bar") as HTMLInputElement | null
const searchLayout = document.getElementById("search-layout") const searchLayout = document.getElementById("search-layout")
const resultCards = document.getElementsByClassName("result-card")
const idDataMap = Object.keys(data) as FullSlug[] const idDataMap = Object.keys(data) as FullSlug[]
const appendLayout = (el: HTMLElement) => { const appendLayout = (el: HTMLElement) => {
@ -151,41 +150,50 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
if (searchBar) searchBar.value = "#" if (searchBar) searchBar.value = "#"
} }
const resultCards = document.getElementsByClassName("result-card")
// If search is active, then we will render the first result and display accordingly
if (!container?.classList.contains("active")) return if (!container?.classList.contains("active")) return
else if (e.key === "Enter") { else if (results?.contains(document.activeElement)) {
// If result has focus, navigate to that one, otherwise pick first result
if (results?.contains(document.activeElement)) {
const active = document.activeElement as HTMLInputElement const active = document.activeElement as HTMLInputElement
await displayPreview(active)
if (e.key === "Enter") {
active.click() active.click()
}
} else { } else {
const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null const anchor = resultCards[0] as HTMLInputElement | null
await displayPreview(anchor)
if (e.key === "Enter") {
anchor?.click() anchor?.click()
} }
} else if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) { }
if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) {
e.preventDefault() e.preventDefault()
if (results?.contains(document.activeElement)) { if (results?.contains(document.activeElement)) {
// If an element in results-container already has focus, focus previous one // If an element in results-container already has focus, focus previous one
const prevResult = document.activeElement?.previousElementSibling as HTMLInputElement | null const currentResult = document.activeElement as HTMLInputElement | null
if (enablePreview && prevResult?.id) { const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null
await displayPreview(prevResult?.id as FullSlug) currentResult?.classList.remove("focus")
} await displayPreview(prevResult)
prevResult?.focus() prevResult?.focus()
} }
} else if (e.key === "ArrowDown" || e.key === "Tab") { } else if (e.key === "ArrowDown" || e.key === "Tab") {
e.preventDefault() e.preventDefault()
// When first pressing ArrowDown, results wont contain the active element, so focus first element // The results should already been focused, so we need to find the next one.
// The activeElement is the search bar, so we need to find the first result and focus it.
if (!results?.contains(document.activeElement)) { if (!results?.contains(document.activeElement)) {
const firstResult = resultCards[0] as HTMLInputElement | null const firstResult = resultCards[0] as HTMLInputElement | null
if (enablePreview && firstResult?.id) { const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null
await displayPreview(firstResult?.id as FullSlug) firstResult?.classList.remove("focus")
} await displayPreview(secondResult)
firstResult?.focus() secondResult?.focus()
} else { } else {
// If an element in results-container already has focus, focus next one // If an element in results-container already has focus, focus next one
const nextResult = document.activeElement?.nextElementSibling as HTMLInputElement | null const active = document.activeElement as HTMLInputElement | null
if (enablePreview && nextResult?.id) { active?.classList.remove("focus")
await displayPreview(nextResult?.id as FullSlug) const nextResult = active?.nextElementSibling as HTMLInputElement | null
} await displayPreview(nextResult)
nextResult?.focus() nextResult?.focus()
} }
} }
@ -262,19 +270,50 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const resultToHTML = ({ slug, title, content, tags }: Item) => { const resultToHTML = ({ slug, title, content, tags }: Item) => {
const htmlTags = tags.length > 0 ? `<ul>${tags.join("")}</ul>` : `` const htmlTags = tags.length > 0 ? `<ul>${tags.join("")}</ul>` : ``
const resultContent = enablePreview && window.innerWidth > 600 ? "" : `<p>${content}</p>`
const itemTile = document.createElement("a") const itemTile = document.createElement("a")
itemTile.classList.add("result-card") itemTile.classList.add("result-card")
itemTile.id = slug Object.assign(itemTile, {
itemTile.href = resolveUrl(slug).toString() id: slug,
itemTile.innerHTML = `<h3>${title}</h3>${htmlTags}${enablePreview && window.innerWidth > 600 ? "" : `<p>${content}</p>`}` href: resolveUrl(slug).toString(),
itemTile.addEventListener("click", (event) => { innerHTML: `<h3>${title}</h3>${htmlTags}${resultContent}`,
})
async function onMouseEnter(ev: MouseEvent) {
// When search is active, the first element is in focus, so we need to remove focus if given target is not the first element
const firstEl = document.getElementsByClassName("result-card")[0] as HTMLAnchorElement | null
const target = ev.target as HTMLAnchorElement
if (firstEl !== target) {
firstEl?.classList.remove("focus")
}
target.classList.add("focus")
await displayPreview(target)
}
async function onMouseLeave(ev: MouseEvent) {
const target = ev.target as HTMLAnchorElement
target.classList.remove("focus")
}
const events = [
["mouseenter", onMouseEnter],
["mouseleave", onMouseLeave],
[
"click",
(event: MouseEvent) => {
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return
hideSearch() hideSearch()
}) },
],
] as [keyof HTMLElementEventMap, (this: HTMLElement) => void][]
events.forEach(([event, handler]) => itemTile.addEventListener(event, handler))
return itemTile return itemTile
} }
function displayResults(finalResults: Item[]) { async function displayResults(finalResults: Item[]) {
if (!results) return if (!results) return
removeAllChildren(results) removeAllChildren(results)
@ -286,6 +325,11 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
} else { } else {
results.append(...finalResults.map(resultToHTML)) results.append(...finalResults.map(resultToHTML))
} }
// focus on first result, then also dispatch preview immediately
if (results?.firstElementChild) {
results?.firstElementChild?.classList.add("focus")
await displayPreview(results?.firstElementChild as HTMLElement)
}
} }
async function fetchContent(slug: FullSlug): Promise<Element[]> { async function fetchContent(slug: FullSlug): Promise<Element[]> {
@ -309,8 +353,11 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
return contents return contents
} }
async function displayPreview(slug: FullSlug) { async function displayPreview(el: HTMLElement | null) {
if (!searchLayout || !enablePreview) return if (!searchLayout || !enablePreview || !el) return
const slug = el.id as FullSlug
el.classList.add("focus")
removeAllChildren(preview as HTMLElement) removeAllChildren(preview as HTMLElement)
const contentDetails = await fetchContent(slug) const contentDetails = await fetchContent(slug)
@ -366,7 +413,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
...getByField("tags"), ...getByField("tags"),
]) ])
const finalResults = [...allIds].map((id) => formatForDisplay(term, id)) const finalResults = [...allIds].map((id) => formatForDisplay(term, id))
displayResults(finalResults) await displayResults(finalResults)
} }
if (prevShortcutHandler) { if (prevShortcutHandler) {

View File

@ -162,7 +162,8 @@
} }
&:hover, &:hover,
&:focus { &:focus,
&.focus {
background: var(--lightgray); background: var(--lightgray);
} }