Newer
Older
gnexus-tasks / frontend / src / components / TagField.vue
<script setup lang="ts">
// Обёртка GnTagInput (кит v0.4.0 без автодополнения): рядок подсказок под полем.
// Набранный текст читается из всплывающего input-события внутреннего поля кита
// (слушатель через attrs попадает на корень .tag-input). Клик по подсказке —
// то же, что набрать имя и нажать Enter; внутренний ввод чистится синтетическим
// input-событием (паттерн синтетических событий уже используется в TaskForm).
import { computed, ref } from 'vue'
import { GnChip, GnTagInput } from 'gnexus-ui-kit/vue'

const props = defineProps<{
  modelValue: string[]
  allTags: string[]
  label?: string
  placeholder?: string
}>()

const emit = defineEmits<{
  'update:modelValue': [string[]]
  add: [string]
  remove: [string]
}>()

// Набранный текст внутреннего input'а кита (input-события всплывают до корня)
const typed = ref('')
const rootEl = ref<HTMLElement | null>(null)

function onTyped(e: Event): void {
  if (e.target instanceof HTMLInputElement) typed.value = e.target.value
}

// Подсказки: существующие теги, не выбранные (регистронезависимо), по подстроке
const suggestions = computed(() => {
  const q = typed.value.trim().toLowerCase()
  if (!q) return []
  const chosen = new Set(props.modelValue.map((t) => t.toLowerCase()))
  return props.allTags
    .filter((name) => !chosen.has(name.toLowerCase()) && name.toLowerCase().includes(q))
    .slice(0, 8)
})

// Клик по подсказке = добавить тег и очистить внутренний ввод кита (иначе
// набранный кусок оставался бы и Enter-ом добавился бы как отдельный тег)
function pick(name: string): void {
  if (props.modelValue.some((t) => t.toLowerCase() === name.toLowerCase())) return
  emit('update:modelValue', [...props.modelValue, name])
  emit('add', name)
  const field = rootEl.value?.querySelector<HTMLInputElement>('.tag-input-field')
  if (field && field.value) {
    field.value = ''
    field.dispatchEvent(new Event('input', { bubbles: true }))
  }
}
</script>

<template>
  <div ref="rootEl" class="tag-field">
    <GnTagInput
      :model-value="modelValue"
      :label="label"
      :placeholder="placeholder"
      unique
      @update:model-value="emit('update:modelValue', $event)"
      @add="emit('add', $event)"
      @remove="emit('remove', $event)"
      @input="onTyped"
    />
    <!-- Подсказки: клик добавляет тег к выбранным -->
    <div v-if="suggestions.length" class="tag-suggest">
      <GnChip v-for="name in suggestions" :key="name" variant="info" clickable @click="pick(name)">
        <i class="ph ph-plus" aria-hidden="true" />{{ name }}
      </GnChip>
    </div>
  </div>
</template>

<style scoped>
/* Подсказки: строка чипов под полем; клик добавляет тег */
.tag-suggest {
  margin-top: 5px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.tag-suggest .chip {
  cursor: pointer;
}
</style>