feat: adds arrow keys navigation on powersearch
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
v-for="(shortcut, shortcutIndex) in searchResults"
|
||||
:key="`shortcut-${shortcutIndex}`"
|
||||
:ref="`item-${shortcutIndex}`"
|
||||
:active="shortcutIndex === selectedEntry"
|
||||
:shortcut="shortcut.item"
|
||||
@action="$emit('action', shortcut.item.action)"
|
||||
/>
|
||||
@@ -20,8 +21,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
computed,
|
||||
onUnmounted,
|
||||
onMounted,
|
||||
getCurrentInstance,
|
||||
} from "@nuxtjs/composition-api"
|
||||
import Fuse from "fuse.js"
|
||||
import { useArrowKeysNavigation } from "~/helpers/powerSearchNavigation"
|
||||
import { HoppAction } from "~/helpers/actions"
|
||||
|
||||
const props = defineProps<{
|
||||
input: Record<string, any>[]
|
||||
@@ -35,4 +43,23 @@ const options = {
|
||||
const fuse = new Fuse(props.input, options)
|
||||
|
||||
const searchResults = computed(() => fuse.search(props.search))
|
||||
|
||||
const searchResultsItems = computed(() =>
|
||||
searchResults.value.map((searchResult: any) => searchResult.item)
|
||||
)
|
||||
|
||||
const currentInstance = getCurrentInstance()
|
||||
|
||||
const emitSearchAction = (action: HoppAction) =>
|
||||
currentInstance.emit("action", action)
|
||||
|
||||
const { bindArrowKeysListerners, unbindArrowKeysListerners, selectedEntry } =
|
||||
useArrowKeysNavigation(searchResultsItems, {
|
||||
onEnter: emitSearchAction,
|
||||
stopPropagation: true,
|
||||
})
|
||||
|
||||
onMounted(bindArrowKeysListerners)
|
||||
|
||||
onUnmounted(unbindArrowKeysListerners)
|
||||
</script>
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
v-for="(shortcut, shortcutIndex) in map.shortcuts"
|
||||
:key="`map-${mapIndex}-shortcut-${shortcutIndex}`"
|
||||
:shortcut="shortcut"
|
||||
:active="shortcutsItems.indexOf(shortcut) === selectedEntry"
|
||||
@action="runAction"
|
||||
/>
|
||||
</div>
|
||||
@@ -56,9 +57,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "@nuxtjs/composition-api"
|
||||
import { ref, computed, onUnmounted, onMounted } from "@nuxtjs/composition-api"
|
||||
import { HoppAction, invokeAction } from "~/helpers/actions"
|
||||
import { spotlight as mappings, fuse } from "~/helpers/shortcuts"
|
||||
import { useArrowKeysNavigation } from "~/helpers/powerSearchNavigation"
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
@@ -79,4 +81,20 @@ const runAction = (command: HoppAction) => {
|
||||
invokeAction(command)
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const shortcutsItems = computed(() =>
|
||||
mappings.reduce(
|
||||
(shortcuts, section) => [...shortcuts, ...section.shortcuts],
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
const { bindArrowKeysListerners, unbindArrowKeysListerners, selectedEntry } =
|
||||
useArrowKeysNavigation(shortcutsItems, {
|
||||
onEnter: runAction,
|
||||
})
|
||||
|
||||
onMounted(bindArrowKeysListerners)
|
||||
|
||||
onUnmounted(unbindArrowKeysListerners)
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
focus-visible:bg-primaryLight
|
||||
search-entry
|
||||
"
|
||||
:class="{ active, 'outline-none': active, 'focus-visible': active }"
|
||||
tabindex="0"
|
||||
@click="$emit('action', shortcut.action)"
|
||||
@keydown.enter="$emit('action', shortcut.action)"
|
||||
@@ -26,6 +27,7 @@
|
||||
group-hover:text-secondaryDark group-hover:opacity-100
|
||||
group-focus:opacity-100
|
||||
"
|
||||
:class="{ 'opacity-100': active, 'text-secondaryDark': active }"
|
||||
:name="shortcut.icon"
|
||||
/>
|
||||
<span
|
||||
@@ -36,6 +38,7 @@
|
||||
group-hover:text-secondaryDark
|
||||
group-focus:text-secondaryDark
|
||||
"
|
||||
:class="{ 'text-secondaryDark': active }"
|
||||
>
|
||||
{{ $t(shortcut.label) }}
|
||||
</span>
|
||||
@@ -52,6 +55,7 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
shortcut: Object
|
||||
active: Boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -73,7 +77,8 @@ defineProps<{
|
||||
}
|
||||
|
||||
&:hover::after,
|
||||
&:focus::after {
|
||||
&:focus::after,
|
||||
&.active::after {
|
||||
@apply bg-accentLight;
|
||||
}
|
||||
}
|
||||
|
||||
54
packages/hoppscotch-app/helpers/powerSearchNavigation.ts
Normal file
54
packages/hoppscotch-app/helpers/powerSearchNavigation.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { ref } from "@nuxtjs/composition-api"
|
||||
|
||||
const NAVIGATION_KEYS = ["ArrowDown", "ArrowUp", "Enter"]
|
||||
|
||||
export function useArrowKeysNavigation(searchItems: any, options: any = {}) {
|
||||
function handleArrowKeysNavigation(
|
||||
event: any,
|
||||
itemIndex: any,
|
||||
preventPropagation: Boolean
|
||||
) {
|
||||
if (!NAVIGATION_KEYS.includes(event.key)) return
|
||||
|
||||
if (preventPropagation) event.stopImmediatePropagation()
|
||||
|
||||
const itemsLength = searchItems.value.length
|
||||
const lastItemIndex = itemsLength - 1
|
||||
const itemIndexValue = itemIndex.value
|
||||
const action = searchItems.value[itemIndexValue].action
|
||||
|
||||
if (action && event.key === "Enter" && options.onEnter) {
|
||||
options.onEnter(action)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown") {
|
||||
itemIndex.value = itemIndexValue < lastItemIndex ? itemIndexValue + 1 : 0
|
||||
} else if (itemIndexValue === 0) itemIndex.value = lastItemIndex
|
||||
else if (event.key === "ArrowUp") itemIndex.value = itemIndexValue - 1
|
||||
}
|
||||
|
||||
const preventPropagation = options && options.stopPropagation
|
||||
|
||||
const selectedEntry = ref(0)
|
||||
|
||||
const onKeyUp = (event: any) => {
|
||||
handleArrowKeysNavigation(event, selectedEntry, preventPropagation)
|
||||
}
|
||||
|
||||
function bindArrowKeysListerners() {
|
||||
window.addEventListener("keydown", onKeyUp, { capture: preventPropagation })
|
||||
}
|
||||
|
||||
function unbindArrowKeysListerners() {
|
||||
window.removeEventListener("keydown", onKeyUp, {
|
||||
capture: preventPropagation,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
bindArrowKeysListerners,
|
||||
unbindArrowKeysListerners,
|
||||
selectedEntry,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user