1<template>
2 <div
3 v-if="props.items.length > 0"
4 data-suggestion-popup
5 class="max-h-[300px] min-w-40 overflow-y-auto rounded-6 bg-surface-base p-1 text-base shadow-lg pointer-events-auto hide-scrollbar"
6 >
7 <button
8 v-for="(item, index) in props.items"
9 :key="item.value"
10 :ref="
11 (el) => {
12 if (el) itemRefs[index] = el as HTMLButtonElement
13 }
14 "
15 :class="[
16 'flex w-full items-center whitespace-nowrap rounded-5 px-2 py-1.5 text-sm text-ink-gray-9',
17 index === selectedIndex ? 'bg-surface-gray-2' : '',
18 ]"
19 @click.stop.prevent="selectItem(index)"
20 @mouseover="selectedIndex = index"
21 >
22 {{ item.label }}
23 </button>
24 </div>
25</template>
26
27<script setup lang="ts">
28import { nextTick, onBeforeUpdate, ref, watch, type PropType } from "vue";
29import type { Editor, Range } from "@tiptap/core";
30import type { FieldItem } from "./tiptap";
31
32const props = defineProps({
33 items: {
34 type: Array as PropType<any[]>,
35 required: true,
36 },
37 editor: {
38 type: Object as PropType<Editor>,
39 required: true,
40 },
41 range: {
42 type: Object as PropType<Range>,
43 required: true,
44 },
45 command: {
46 type: Function as PropType<(item: FieldItem) => void>,
47 required: true,
48 },
49 query: String,
50});
51
52const selectItem = (index: number) => {
53 const item = props.items[index];
54 if (item) {
55 props.command(item);
56 }
57};
58
59const onKeyDown = ({ event }: { event: KeyboardEvent }) => {
60 if (!props.items.length) return false;
61
62 if (event.key === "ArrowUp") {
63 upHandler();
64 return true;
65 }
66 if (event.key === "ArrowDown") {
67 downHandler();
68 return true;
69 }
70 if (event.key === "Enter") {
71 enterHandler();
72 return true;
73 }
74 return false;
75};
76const selectedIndex = ref(0);
77const itemRefs = ref<HTMLButtonElement[]>([]);
78
79onBeforeUpdate(() => {
80 itemRefs.value = [];
81});
82
83const scrollIntoView = () => {
84 nextTick(() => {
85 const selectedElement = itemRefs.value[selectedIndex.value];
86 if (selectedElement) {
87 selectedElement.scrollIntoView({ block: "nearest" });
88 }
89 });
90};
91
92const upHandler = () => {
93 selectedIndex.value =
94 (selectedIndex.value + props.items.length - 1) % props.items.length;
95 scrollIntoView();
96};
97
98const downHandler = () => {
99 selectedIndex.value = (selectedIndex.value + 1) % props.items.length;
100 scrollIntoView();
101};
102
103const enterHandler = () => {
104 selectItem(selectedIndex.value);
105};
106
107defineExpose({
108 onKeyDown,
109});
110
111watch(
112 () => props.items,
113 () => {
114 selectedIndex.value = 0;
115 }
116);
117</script>
118