IKHSAN
ENID Baca dalam Bahasa Indonesia

opendesk

Public
Branch
main
Latest commit
08d9f3328 October 2026

FieldAutocompleteList.vue

Open raw
<template>
  <div
    v-if="props.items.length > 0"
    data-suggestion-popup
    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"
  >
    <button
      v-for="(item, index) in props.items"
      :key="item.value"
      :ref="
        (el) => {
          if (el) itemRefs[index] = el as HTMLButtonElement
        }
      "
      :class="[
        'flex w-full items-center whitespace-nowrap rounded-5 px-2 py-1.5 text-sm text-ink-gray-9',
        index === selectedIndex ? 'bg-surface-gray-2' : '',
      ]"
      @click.stop.prevent="selectItem(index)"
      @mouseover="selectedIndex = index"
    >
      {{ item.label }}
    </button>
  </div>
</template>

<script setup lang="ts">
import { nextTick, onBeforeUpdate, ref, watch, type PropType } from "vue";
import type { Editor, Range } from "@tiptap/core";
import type { FieldItem } from "./tiptap";

const props = defineProps({
  items: {
    type: Array as PropType<any[]>,
    required: true,
  },
  editor: {
    type: Object as PropType<Editor>,
    required: true,
  },
  range: {
    type: Object as PropType<Range>,
    required: true,
  },
  command: {
    type: Function as PropType<(item: FieldItem) => void>,
    required: true,
  },
  query: String,
});

const selectItem = (index: number) => {
  const item = props.items[index];
  if (item) {
    props.command(item);
  }
};

const onKeyDown = ({ event }: { event: KeyboardEvent }) => {
  if (!props.items.length) return false;

  if (event.key === "ArrowUp") {
    upHandler();
    return true;
  }
  if (event.key === "ArrowDown") {
    downHandler();
    return true;
  }
  if (event.key === "Enter") {
    enterHandler();
    return true;
  }
  return false;
};
const selectedIndex = ref(0);
const itemRefs = ref<HTMLButtonElement[]>([]);

onBeforeUpdate(() => {
  itemRefs.value = [];
});

const scrollIntoView = () => {
  nextTick(() => {
    const selectedElement = itemRefs.value[selectedIndex.value];
    if (selectedElement) {
      selectedElement.scrollIntoView({ block: "nearest" });
    }
  });
};

const upHandler = () => {
  selectedIndex.value =
    (selectedIndex.value + props.items.length - 1) % props.items.length;
  scrollIntoView();
};

const downHandler = () => {
  selectedIndex.value = (selectedIndex.value + 1) % props.items.length;
  scrollIntoView();
};

const enterHandler = () => {
  selectItem(selectedIndex.value);
};

defineExpose({
  onKeyDown,
});

watch(
  () => props.items,
  () => {
    selectedIndex.value = 0;
  }
);
</script>
bytes
2,570
lines
118
Type
vue
Latest commit
28 October 2026