plugin-ui-for-kzt 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +2 -2
  2. package/dist/components/BaseDropdown/BaseDropdown.vue.d.ts +1 -1
  3. package/dist/components/BaseInput/BaseInput.vue.d.ts +3 -3
  4. package/dist/components/BaseInputCalendar/BaseInputCalendar.vue.d.ts +4 -4
  5. package/dist/components/BaseInputCurrency/BaseInputCurrency.vue.d.ts +3 -3
  6. package/dist/components/BaseInputEmail/BaseInputEmail.vue.d.ts +3 -3
  7. package/dist/components/BaseInputPhone/BaseInputPhone.vue.d.ts +3 -3
  8. package/dist/components/BaseRadio/BaseRadio.vue.d.ts +2 -2
  9. package/dist/components/BaseSelect/BaseSelect.vue.d.ts +16 -3
  10. package/dist/components/BaseTable/BaseTable.vue.d.ts +14 -1
  11. package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +3 -3
  12. package/dist/components/BaseToggle/BaseToggle.vue.d.ts +2 -2
  13. package/dist/components/BaseTooltip/BaseTooltip.vue.d.ts +2 -2
  14. package/dist/index.js +1 -1
  15. package/dist/sprite.svg +1 -1
  16. package/example/App.vue +271 -7
  17. package/package.json +1 -1
  18. package/src/assets/icons/search.svg +4 -0
  19. package/src/components/BaseButton/BaseButton.vue +20 -20
  20. package/src/components/BaseSelect/BaseSelect.vue +164 -10
  21. package/src/components/BaseSelect/README.md +110 -1
  22. package/src/components/BaseTable/BaseTable.vue +278 -26
  23. package/src/components/BaseTable/README.md +96 -1
  24. package/src/components/BaseTooltip/BaseTooltip.vue +9 -5
  25. package/src/components/BaseUpload/BaseUpload.vue +97 -25
  26. package/src/types/input.d.ts +1 -0
  27. package/src/types/table.d.ts +6 -0
  28. package/src/vue-virtual-scroller.d.ts +4 -0
@@ -4,21 +4,22 @@
4
4
  <table class="base-table__table">
5
5
  <thead v-if="hasHeader">
6
6
  <slot name="header">
7
- <tr class="base-table__header">
7
+ <tr class="base-table__header" :style="headerStyle">
8
8
  <!-- Чек‑бокс «Выбрать всё» -->
9
- <th v-if="showRowSelection" class="base-table__header-column">
9
+ <th v-if="showRowSelection" class="base-table__header-column" :style="getColumnStyle(0)">
10
10
  <span v-if="checkboxTitle">{{ checkboxTitle }}</span>
11
11
  <BaseCheckbox id="selectAll" size="small" v-model="isAllSelected" />
12
12
  </th>
13
13
 
14
14
  <!-- Обычные колонки -->
15
- <th v-for="col in columns"
15
+ <th v-for="(col, index) in columns"
16
16
  :key="String(col.key)"
17
17
  class="base-table__header-column"
18
18
  @click="onHeaderClick(col)"
19
19
  :title="col.tooltip"
20
+ :style="{ ...getColumnStyle(getColumnIndex(Number(index))), ...getAlignStyle(col) }"
20
21
  >
21
- <div class="column-header">
22
+ <div class="column-header" :style="{ justifyContent: getAlignStyle(col).justifyContent }">
22
23
  <component
23
24
  v-if="col.icons?.[0]"
24
25
  :is="col.icons[0]"
@@ -41,11 +42,84 @@
41
42
  </th>
42
43
 
43
44
  <!-- Колонка‑действий -->
44
- <th v-if="showActions" class="base-table__cell base-table__header-column base-table__actions-column"></th>
45
+ <th v-if="showActions" class="base-table__cell base-table__header-column base-table__actions-column" :style="getColumnStyle(actionsColumnIndex)"></th>
46
+
47
+ <!-- Колонка меню -->
48
+ <th v-if="$slots['row-menu']" class="base-table__cell base-table__header-column base-table__menu-column" :style="getColumnStyle(menuColumnIndex)"></th>
45
49
  </tr>
46
50
  </slot>
47
51
  </thead>
48
- <transition-group tag="tbody"
52
+ <!-- Виртуальный скролл для больших списков -->
53
+ <template v-if="useVirtualScroll">
54
+ <tbody class="base-table__virtual-tbody">
55
+ <tr>
56
+ <td :colspan="colspan" class="base-table__virtual-cell">
57
+ <recycle-scroller
58
+ :items="paginatedData"
59
+ :item-size="60"
60
+ key-field="id"
61
+ class="base-table__scroller"
62
+ :style="{ maxHeight: virtualScrollHeight + 'px' }"
63
+ @scroll="handleScroll"
64
+ >
65
+ <template #default="{ item, index }: { item: Row, index: number }">
66
+ <div class="base-table__virtual-row" :style="{ gridTemplateColumns: virtualGridColumns }">
67
+ <!-- Чек‑бокс отдельной строки -->
68
+ <div v-if="showRowSelection" class="base-table__virtual-cell-checkbox">
69
+ <BaseCheckbox
70
+ :id="String(item.id || index)"
71
+ size="small"
72
+ :modelValue="isRowSelected(item)"
73
+ :disabled="Boolean(item.disabled)"
74
+ @update:modelValue="toggleRow(item)"
75
+ />
76
+ </div>
77
+
78
+ <!-- Ячейки -->
79
+ <div v-for="col in columns"
80
+ :key="String(col.key)"
81
+ class="base-table__virtual-cell-content"
82
+ :style="getAlignStyle(col)">
83
+ <!-- Пользовательский рендеринг ячейки -->
84
+ <slot
85
+ :name="`cell-${String(col.key)}`"
86
+ :row="item"
87
+ :value="item[col.key]"
88
+ >
89
+ {{ item[col.key] }}
90
+ </slot>
91
+ </div>
92
+
93
+ <!-- Колонка‑действий -->
94
+ <div v-if="showActions" class="base-table__virtual-cell-actions">
95
+ <slot name="actions" :row="item">
96
+ <BaseButton
97
+ v-for="action in actions"
98
+ :key="action.key"
99
+ :disabled="Boolean(item.disabled)"
100
+ size="extra-small"
101
+ color="quaternary-gray"
102
+ only-icon
103
+ @click="action.handler(item)"
104
+ >
105
+ <BaseIcon :name="typeof action.icon === 'string' ? action.icon : 'help'" size="custom" />
106
+ </BaseButton>
107
+ </slot>
108
+ </div>
109
+
110
+ <div v-if="$slots['row-menu']" class="base-table__virtual-cell-menu">
111
+ <slot name="row-menu" :row="item"></slot>
112
+ </div>
113
+ </div>
114
+ </template>
115
+ </recycle-scroller>
116
+ </td>
117
+ </tr>
118
+ </tbody>
119
+ </template>
120
+
121
+ <!-- Обычный рендеринг для небольших списков -->
122
+ <transition-group v-else tag="tbody"
49
123
  name="row"
50
124
  class="base-table__tbody"
51
125
  appear
@@ -54,18 +128,18 @@
54
128
  <template v-if="paginatedData.length">
55
129
  <tr
56
130
  v-for="(row, rowIndex) in paginatedData"
57
- :key="rowId(row)"
58
- :class="{ '--is-disabled': row.disabled }"
131
+ :key="String(rowId(row))"
132
+ :class="{ '--is-disabled': Boolean(row.disabled) }"
59
133
  class="base-table__row"
60
134
  >
61
135
 
62
136
  <!-- Чек‑бокс отдельной строки -->
63
137
  <td v-if="showRowSelection" class="base-table__cell">
64
138
  <BaseCheckbox
65
- :id="row.id"
139
+ :id="String(row.id || rowIndex)"
66
140
  size="small"
67
141
  :modelValue="isRowSelected(row)"
68
- :disabled="row.disabled"
142
+ :disabled="Boolean(row.disabled)"
69
143
  @update:modelValue="toggleRow(row)"
70
144
  />
71
145
  </td>
@@ -73,7 +147,8 @@
73
147
  <!-- Ячейки -->
74
148
  <td v-for="col in columns"
75
149
  :key="String(col.key)"
76
- class="base-table__cell">
150
+ class="base-table__cell"
151
+ :style="getAlignStyle(col)">
77
152
  <!-- Пользовательский рендеринг ячейки -->
78
153
  <slot
79
154
  :name="`cell-${String(col.key)}`"
@@ -90,12 +165,13 @@
90
165
  <BaseButton
91
166
  v-for="action in actions"
92
167
  :key="action.key"
93
- :disabled="row.disabled"
168
+ :disabled="Boolean(row.disabled)"
94
169
  size="extra-small"
95
170
  color="quaternary-gray"
171
+ only-icon
96
172
  @click="action.handler(row)"
97
173
  >
98
- <BaseIcon :name="action.icon" size="extra-small" />
174
+ <BaseIcon :name="typeof action.icon === 'string' ? action.icon : 'help'" size="custom" />
99
175
  </BaseButton>
100
176
  </slot>
101
177
  </td>
@@ -135,6 +211,8 @@
135
211
 
136
212
  <script setup lang="ts">
137
213
  import { ref, computed, watch, defineProps, defineEmits, useSlots } from 'vue'
214
+ import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
215
+ import { RecycleScroller } from 'vue-virtual-scroller'
138
216
  import BasePagination from '../BasePagination/BasePagination.vue'
139
217
  import BaseCheckbox from '../BaseCheckbox/BaseCheckbox.vue'
140
218
  import BaseButton from '../BaseButton/BaseButton.vue'
@@ -152,6 +230,7 @@ const emit = defineEmits<{
152
230
  (e: 'select-all', selected: boolean): void
153
231
  (e: 'sort-change', payload: { key: string; order: 'asc' | 'desc' }): void
154
232
  (e: 'page-change', page: number): void
233
+ (e: 'scroll-end'): void
155
234
  }>()
156
235
 
157
236
  const colspan = computed(() => {
@@ -195,14 +274,15 @@ function onHeaderClick(col: Column) {
195
274
 
196
275
  /* ---------- ПАГИНАЦИЯ ---------- */
197
276
  const pagination = computed<TPaginationProps>(() => props.pagination ?? { perPage: 10 })
198
- const perPage = computed(() => pagination.value.totalPages ?? 10)
277
+ const perPage = computed(() => pagination.value.perPage ?? 10)
199
278
 
200
279
  const totalPages = computed(() => {
201
280
  if (!props.pagination) return 1
202
- return Math.max(1, Math.ceil(props.data.length / perPage.value))
281
+ const perPageValue = Number(perPage.value) || 10
282
+ return Math.max(1, Math.ceil(props.data.length / perPageValue))
203
283
  })
204
284
 
205
- const showPagination = computed(() => totalPages.value > 1)
285
+ const showPagination = computed(() => props.pagination && totalPages.value > 1)
206
286
 
207
287
  const currentPage = ref(pagination.value.currentPage ?? 1)
208
288
 
@@ -212,11 +292,121 @@ function onPageChange(page: number) {
212
292
  }
213
293
 
214
294
  const paginatedData = computed(() => {
215
- const start = (currentPage.value - 1) * perPage.value
216
- const end = start + perPage.value
295
+ if (!props.pagination) {
296
+ return props.data
297
+ }
298
+
299
+ const perPageValue = Number(perPage.value) || 10
300
+ const start = (currentPage.value - 1) * perPageValue
301
+ const end = start + perPageValue
217
302
  return props.data.slice(start, end)
218
303
  })
219
304
 
305
+ const useVirtualScroll = computed<boolean>(() => props.virtualScroll && props.data.length > (props.virtualScrollThreshold ?? 100))
306
+ const virtualScrollHeight = computed<number>(() => props.virtualScrollHeight ?? 400)
307
+
308
+ const scrollbarWidth = ref<number>(0)
309
+
310
+ function getScrollbarWidth(): number {
311
+ const outer = document.createElement('div')
312
+ outer.style.visibility = 'hidden'
313
+ outer.style.overflow = 'scroll'
314
+ document.body.appendChild(outer)
315
+
316
+ const inner = document.createElement('div')
317
+ outer.appendChild(inner)
318
+
319
+ const width = outer.offsetWidth - inner.offsetWidth
320
+ document.body.removeChild(outer)
321
+
322
+ return width
323
+ }
324
+
325
+ watch(useVirtualScroll, (isVirtual) => {
326
+ if (isVirtual) {
327
+ scrollbarWidth.value = getScrollbarWidth()
328
+ }
329
+ }, { immediate: true })
330
+
331
+ const allColumnWidths = computed(() => {
332
+ const widths = []
333
+
334
+ if (props.showRowSelection) {
335
+ widths.push('60px')
336
+ }
337
+
338
+ for (let i = 0; i < props.columns.length; i++) {
339
+ const col = props.columns[i]
340
+ if (col.width) {
341
+ widths.push(typeof col.width === 'number' ? `${col.width}px` : col.width)
342
+ } else {
343
+ widths.push('1fr')
344
+ }
345
+ }
346
+
347
+ if (props.showActions) {
348
+ widths.push('auto')
349
+ }
350
+
351
+ if (useSlots()['row-menu']) {
352
+ widths.push('auto')
353
+ }
354
+
355
+ return widths as string[]
356
+ })
357
+
358
+ const virtualGridColumns = computed<string>(() => allColumnWidths.value.join(' '))
359
+
360
+ const actionsColumnIndex = computed<number>(() => {
361
+ const total = allColumnWidths.value.length
362
+ return useSlots()['row-menu'] ? total - 2 : total - 1
363
+ })
364
+
365
+ const menuColumnIndex = computed<number>(() => allColumnWidths.value.length - 1)
366
+
367
+ function getColumnIndex(colIndex: number): number {
368
+ return props.showRowSelection ? colIndex + 1 : colIndex
369
+ }
370
+
371
+ const headerStyle = computed(() => {
372
+ if (!useVirtualScroll.value) return {}
373
+ return {
374
+ display: 'grid' as const,
375
+ gridTemplateColumns: virtualGridColumns.value,
376
+ paddingRight: `${scrollbarWidth.value}px`
377
+ }
378
+ })
379
+
380
+ function getColumnStyle(index: number): Record<string, string | undefined> {
381
+ if (!useVirtualScroll.value) return {}
382
+
383
+ const width = allColumnWidths.value[index]
384
+ if (!width) return {}
385
+
386
+ return {
387
+ width: width === 'auto' || width.includes('fr') ? width : width,
388
+ minWidth: width === 'auto' || width.includes('fr') ? undefined : width,
389
+ maxWidth: width === 'auto' || width.includes('fr') ? undefined : width,
390
+ }
391
+ }
392
+
393
+ function getAlignStyle(col: Column): { textAlign: 'left' | 'center' | 'right'; justifyContent: string } {
394
+ const align = col.align || 'left'
395
+ return {
396
+ textAlign: align,
397
+ justifyContent: align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start'
398
+ }
399
+ }
400
+
401
+ const handleScroll = (event: Event) => {
402
+ const target = event.target as HTMLElement
403
+ const { scrollTop, scrollHeight, clientHeight } = target
404
+
405
+ if (scrollTop + clientHeight >= scrollHeight - 100) {
406
+ emit('scroll-end')
407
+ }
408
+ }
409
+
220
410
  /* ---------- ВЫБОР СТРОК (чек‑боксы) ---------- */
221
411
  const selectedIds = ref<Set<unknown>>(new Set())
222
412
 
@@ -255,7 +445,7 @@ function emitSelected() {
255
445
  watch(
256
446
  () => props.data,
257
447
  () => {
258
- const newIds = new Set(props.data.map(rowId))
448
+ const newIds = new Set(props.data.map((row: Row) => rowId(row)))
259
449
  selectedIds.value.forEach(id => {
260
450
  if (!newIds.has(id)) selectedIds.value.delete(id)
261
451
  })
@@ -263,12 +453,6 @@ watch(
263
453
  { deep: true }
264
454
  )
265
455
 
266
- /* ---------- Подтаблица (expand / collapse) ---------- */
267
- const expandedRows = ref<Set<number>>(new Set())
268
- function toggleSubRow(idx: number) {
269
- if (expandedRows.value.has(idx)) expandedRows.value.delete(idx)
270
- else expandedRows.value.add(idx)
271
- }
272
456
  </script>
273
457
 
274
458
  <style scoped lang="scss">
@@ -290,7 +474,7 @@ function toggleSubRow(idx: number) {
290
474
  &__table {
291
475
  width: 100%;
292
476
  border-collapse: collapse;
293
- table-layout: fixed;
477
+ table-layout: auto;
294
478
  }
295
479
 
296
480
  &__header-column,
@@ -353,6 +537,11 @@ function toggleSubRow(idx: number) {
353
537
  text-align: right;
354
538
  }
355
539
 
540
+ &__actions-column {
541
+ display: flex;
542
+ align-items: center;
543
+ }
544
+
356
545
  &__footer {
357
546
  padding: 20px;
358
547
  display: flex;
@@ -408,4 +597,67 @@ function toggleSubRow(idx: number) {
408
597
  .base-table__tbody {
409
598
  position: relative;
410
599
  }
600
+
601
+ .base-table__virtual-tbody {
602
+ width: 100%;
603
+ }
604
+
605
+ .base-table__virtual-cell {
606
+ padding: 0;
607
+ border: none;
608
+ }
609
+
610
+ .base-table__scroller {
611
+ width: 100%;
612
+ }
613
+
614
+ .base-table__virtual-row {
615
+ display: grid;
616
+ align-items: center;
617
+ width: 100%;
618
+ min-height: 60px;
619
+ border-bottom: 1px solid var(--primary-black-100);
620
+ padding: var(--spacing-l) 0;
621
+ gap: 0;
622
+ }
623
+
624
+ .base-table__virtual-row:hover {
625
+ background: var(--primary-black-100);
626
+ }
627
+
628
+ .base-table__virtual-row.--is-disabled {
629
+ cursor: not-allowed;
630
+ background: var(--primary-black-50);
631
+ }
632
+
633
+ .base-table__virtual-cell-checkbox {
634
+ display: flex;
635
+ align-items: center;
636
+ padding: 0 var(--spacing-xl);
637
+ }
638
+
639
+ .base-table__virtual-cell-content {
640
+ display: flex;
641
+ align-items: center;
642
+ text-align: left;
643
+ white-space: nowrap;
644
+ padding: 0 var(--spacing-xl);
645
+
646
+ @include text-clamp(1);
647
+ }
648
+
649
+ .base-table__virtual-cell-actions {
650
+ display: flex;
651
+ align-items: center;
652
+ gap: var(--spacing-xs);
653
+ justify-content: flex-end;
654
+ padding: 0 var(--spacing-xl);
655
+ }
656
+
657
+ .base-table__virtual-cell-menu {
658
+ display: flex;
659
+ align-items: center;
660
+ justify-content: center;
661
+ padding: 0 var(--spacing-xl);
662
+ }
411
663
  </style>
@@ -73,8 +73,26 @@ export interface Column {
73
73
  icons?: [any, any] // любая vue‑компонента
74
74
  /** Делает колонку сортируемой */
75
75
  sortable?: boolean
76
- /** Ширина/стилизация – задаётся через CSS (необязательно) */
76
+ /**
77
+ * Ширина колонки (необязательно)
78
+ * Поддерживаемые форматы:
79
+ * - Строка с единицами: '120px', '1fr', '2fr', 'auto'
80
+ * - Число (будет преобразовано в пиксели): 120 → '120px'
81
+ * - Если не указано, используется '1fr' (равномерное распределение)
82
+ *
83
+ * Важно: ширины применяются как к обычной таблице, так и к виртуальному скроллу,
84
+ * обеспечивая полную синхронизацию thead и tbody
85
+ */
77
86
  width?: string | number
87
+ /**
88
+ * Выравнивание содержимого колонки (необязательно)
89
+ * - 'left' - выравнивание влево (по умолчанию)
90
+ * - 'center' - выравнивание по центру
91
+ * - 'right' - выравнивание вправо
92
+ *
93
+ * Применяется как к заголовку, так и к ячейкам колонки
94
+ */
95
+ align?: 'left' | 'center' | 'right'
78
96
  }
79
97
 
80
98
  export interface TPaginationProps {
@@ -292,3 +310,80 @@ const rowActions = [
292
310
  </BaseDropdown>
293
311
  </template>
294
312
  </BaseTable>
313
+
314
+ ### 3.8. Настройка ширины колонок
315
+
316
+ Можно точно контролировать ширину каждой колонки через свойство `width`:
317
+
318
+ ```vue
319
+ <template>
320
+ <BaseTable
321
+ :data="data"
322
+ :columns="columns"
323
+ :virtual-scroll="true"
324
+ :virtual-scroll-height="400"
325
+ :show-row-selection="true"
326
+ :show-actions="true"
327
+ />
328
+ </template>
329
+
330
+ <script setup lang="ts">
331
+ import BaseTable from '@/components/BaseTable/BaseTable.vue'
332
+
333
+ const columns = [
334
+ { key: 'id', label: 'ID', width: '80px' }, // Фиксированная ширина
335
+ { key: 'name', label: 'Имя', width: 200 }, // Число автоматически → '200px'
336
+ { key: 'email', label: 'Email', width: '1fr' }, // Гибкая ширина (CSS Grid)
337
+ { key: 'status', label: 'Статус', width: '120px' } // Фиксированная ширина
338
+ ]
339
+
340
+ const data = [
341
+ { id: 1, name: 'Иван', email: 'ivan@example.com', status: 'Активен' },
342
+ // ...
343
+ ]
344
+ </script>
345
+ ```
346
+
347
+ **Преимущества:**
348
+ - ✅ Ширины колонок **синхронизированы** между `<thead>` и виртуальным скроллом
349
+ - ✅ Поддержка различных единиц: `px`, `fr`, `auto`, числа
350
+ - ✅ Если `width` не указан, используется `1fr` (равномерное распределение)
351
+ - ✅ Особенно важно для виртуального скролла, где `thead` и `tbody` используют разные системы рендеринга
352
+ - ✅ Автоматическая компенсация ширины скроллбара - thead получает `padding-right`, предотвращая смещение колонок
353
+
354
+ ### 3.9. Выравнивание содержимого колонок
355
+
356
+ Вы можете настроить выравнивание для каждой колонки через свойство `align`:
357
+
358
+ ```vue
359
+ <template>
360
+ <BaseTable
361
+ :data="data"
362
+ :columns="columns"
363
+ />
364
+ </template>
365
+
366
+ <script setup lang="ts">
367
+ import BaseTable from '@/components/BaseTable/BaseTable.vue'
368
+
369
+ const columns = [
370
+ { key: 'id', label: 'ID', width: '80px', align: 'center' }, // ID по центру
371
+ { key: 'name', label: 'Имя', width: '200px', align: 'left' }, // Текст влево (по умолчанию)
372
+ { key: 'price', label: 'Цена', width: '120px', align: 'right' }, // Числа вправо
373
+ { key: 'status', label: 'Статус', width: '100px', align: 'center' } // Статус по центру
374
+ ]
375
+
376
+ const data = [
377
+ { id: 1, name: 'Товар 1', price: '1000₸', status: 'Активен' },
378
+ { id: 2, name: 'Товар 2', price: '2500₸', status: 'Неактивен' },
379
+ // ...
380
+ ]
381
+ </script>
382
+ ```
383
+
384
+ **Когда использовать:**
385
+ - `align: 'left'` - текстовые данные (имена, описания) - по умолчанию
386
+ - `align: 'center'` - короткие значения (ID, статусы, иконки)
387
+ - `align: 'right'` - числовые данные (цены, количество, проценты)
388
+
389
+ **Важно:** Выравнивание применяется и к заголовку колонки, и к её содержимому, обеспечивая визуальную согласованность.
@@ -1,6 +1,7 @@
1
1
  <template>
2
- <div class="base-tooltip-container">
2
+ <div class="base-tooltip-container" ref="containerRef">
3
3
  <div
4
+ ref="triggerRef"
4
5
  class="base-tooltip__trigger"
5
6
  @mouseenter="showTooltip"
6
7
  @mouseleave="hideTooltip"
@@ -17,6 +18,7 @@
17
18
  <Transition name="tooltip">
18
19
  <div
19
20
  v-if="isVisible"
21
+ ref="tooltipRef"
20
22
  class="base-tooltip"
21
23
  :class="classList"
22
24
  >
@@ -42,6 +44,9 @@ const props = withDefaults(defineProps<ITooltipProps>(), {
42
44
  });
43
45
 
44
46
  const isVisible = ref(false);
47
+ const containerRef = ref<HTMLElement>();
48
+ const triggerRef = ref<HTMLElement>();
49
+ const tooltipRef = ref<HTMLElement>();
45
50
 
46
51
  function showTooltip() {
47
52
  if (props.trigger === 'hover') {
@@ -62,10 +67,9 @@ function toggleTooltip() {
62
67
  }
63
68
 
64
69
  function handleClickOutside(event: MouseEvent) {
65
- if (props.trigger === 'click' && isVisible.value) {
66
- const tooltip = document.querySelector('.base-tooltip');
67
- const trigger = document.querySelector('.base-tooltip__trigger');
68
- if (tooltip && trigger && !tooltip.contains(event.target as Node) && !trigger.contains(event.target as Node)) {
70
+ if (props.trigger === 'click' && isVisible.value && containerRef.value) {
71
+ const target = event.target as Node;
72
+ if (!containerRef.value.contains(target)) {
69
73
  isVisible.value = false;
70
74
  }
71
75
  }