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:
parent
fee3ef9b3a
commit
50bb1ffd8a
@ -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) {
|
||||||
|
@ -162,7 +162,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus,
|
||||||
|
&.focus {
|
||||||
background: var(--lightgray);
|
background: var(--lightgray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user