Transfer
Manba va nishon to‘plamlari o‘rtasida elementlarni ko‘chirish uchun ikki panelli ro‘yxat.
<script setup lang="ts">
import { ref } from 'vue'
import { VxTransfer } from 'veloxui'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
disabled: i === 3 || i === 7,
}))
const value = ref([2, 5])
</script>
<template>
<VxTransfer v-model="value" :data="data" />
</template>Filterable
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([1, 4])
</script>
<template>
<VxTransfer v-model="value" :data="data" filterable />
</template>Custom Titles
<script setup lang="ts">
import { ref } from 'vue'
const cities = [
{ key: 'nyc', label: 'New York' },
{ key: 'la', label: 'Los Angeles' },
{ key: 'chi', label: 'Chicago' },
{ key: 'hou', label: 'Houston' },
{ key: 'phx', label: 'Phoenix' },
]
const value = ref(['nyc', 'chi'])
</script>
<template>
<VxTransfer
v-model="value"
:data="cities"
:titles="['Available Cities', 'Selected Cities']"
filterable
/>
</template>Field Mapping
Ma’lumotlaringizni qayta ishlamasdan maxsus xususiyatlarni moslashtirish uchun key-field va label-field’dan foydalaning.
<script setup lang="ts">
import { ref } from 'vue'
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
{ id: 4, name: 'Diana' },
{ id: 5, name: 'Eve' },
]
const value = ref([2])
</script>
<template>
<VxTransfer
v-model="value"
:data="users"
key-field="id"
label-field="name"
:titles="['Users', 'Team']"
/>
</template>Panel Width
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 6 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([1, 3])
</script>
<template>
<!-- Number = pixels -->
<VxTransfer v-model="value" :data="data" :panel-width="300" />
<!-- String = any CSS value -->
<VxTransfer v-model="value" :data="data" panel-width="50%" />
</template>Sort
Ikkala paneldagi elementlar taqdim etilgan comparator funksiyasi yordamida tartiblanadi.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([3, 1])
</script>
<template>
<VxTransfer
v-model="value"
:data="data"
:sort-method="(a, b) => a.label.localeCompare(b.label)"
:titles="['Sorted A-Z', 'Sorted A-Z']"
/>
</template>Move All
Barcha ruxsat etilgan elementlarni bir martaga ko‘chirish uchun "hammasini ko‘chirish" tugmalarini (qo‘sh strelkalar) ko‘rsating.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
disabled: i === 3 || i === 7,
}))
const value = ref([])
</script>
<template>
<VxTransfer v-model="value" :data="data" show-move-all />
</template>Button Texts
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([])
</script>
<template>
<VxTransfer v-model="value" :data="data" :button-texts="['Add', 'Remove']" />
</template>Custom Button Slots
Har bir slotga move, disabled va checkedCount uzatiladi.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 8 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([4])
</script>
<template>
<VxTransfer v-model="value" :data="data">
<template #to-target-button="{ move, disabled, checkedCount }">
<button :disabled="disabled" @click="move">
Add {{ checkedCount || '' }} →
</button>
</template>
<template #to-source-button="{ move, disabled, checkedCount }">
<button :disabled="disabled" @click="move">
← Remove {{ checkedCount || '' }}
</button>
</template>
</VxTransfer>
</template>Custom Item Slot
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([3])
</script>
<template>
<VxTransfer v-model="value" :data="data">
<template #item="{ item }">
<span>{{ item.label }}</span>
<span style="margin-left: auto; font-size: 11px; opacity: 0.5">
#{{ item.key }}
</span>
</template>
</VxTransfer>
</template>Header & Footer Slots
Panel headerlari va footerlarini moslashtiring. Slotlarga { checked, total, query } uzatiladi.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 6 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([])
</script>
<template>
<VxTransfer v-model="value" :data="data">
<template #source-header="{ total }">
<div class="vx-transfer__header" style="background: var(--muted);">
<span style="font-size: 13px; font-weight: 600;">
Pick items ({{ total }})
</span>
</div>
</template>
<template #target-footer="{ total }">
<div class="vx-transfer__footer">
<span class="vx-transfer__footer-info">
{{ total }} selected
</span>
</div>
</template>
</VxTransfer>
</template>Drag and Drop
Elementlarni panellar orasida sudrab o‘tkazing. draggable bilan yoqing. Element ustiga sudralganda nishon paneli yoritiladi.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
disabled: i === 3 || i === 7,
}))
const value = ref([2, 5])
</script>
<template>
<VxTransfer v-model="value" :data="data" draggable />
</template>Keyboard Navigation
Panel ichini bosib fokuslang, keyin elementlar orasida harakat qilish uchun Yuqori/Pastki strelka, tanlovni o‘zgartirish uchun Space yoki Enter tugmalaridan foydalaning. Fokuslangan element kontur halqasi bilan belgilanadi.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([])
</script>
<template>
<!-- Click inside a panel, then use Arrow keys + Space/Enter -->
<VxTransfer v-model="value" :data="data" />
</template>Scroll Pagination
Foydalanuvchi scroll qilganda elementlarni bosqichma-bosqich yuklash uchun page-size’ni belgilang. Faqat ko‘rinayotgan sahifa renderlanadi — pastga yaqinlashganda qo‘shimcha elementlar avtomatik paydo bo‘ladi.
<script setup lang="ts">
import { ref } from 'vue'
const bigData = Array.from({ length: 100 }, (_, i) => ({
key: i + 1,
label: `Record ${i + 1}`,
disabled: i === 9,
}))
const value = ref([5, 10, 20, 30, 40])
</script>
<template>
<VxTransfer
v-model="value"
:data="bigData"
:page-size="15"
filterable
:titles="['All Records (100)', 'Selected']"
/>
</template>Virtual Scroll
Katta ma’lumotlar to‘plamlari uchun (1000+ element) virtual-scroll’ni item-height bilan yoqing — bu 60fps’da silliq scrollni ta’minlaydi. DOM’da faqat ko‘rinadigan qatorlar renderlanadi.
<script setup lang="ts">
import { ref } from 'vue'
const hugeData = Array.from({ length: 5000 }, (_, i) => ({
key: i + 1,
label: `Virtual item ${i + 1}`,
}))
const value = ref([100, 200, 500])
</script>
<template>
<VxTransfer
v-model="value"
:data="hugeData"
virtual-scroll
:item-height="36"
filterable
:titles="['5,000 Items', 'Selected']"
/>
</template>Remote Data (fetchOptions)
Cheksiz scroll bilan API’dan manba ma’lumotlarini yuklang. TransferItem[] qaytaruvchi asinxron fetchOptions(query, page) funksiyasini uzating. Keyingi sahifani yuklash uchun pastga scroll qiling. Qidiruv inputi debounce qilingan va yangi fetch’ni ishga tushiradi.
<script setup lang="ts">
import { ref } from 'vue'
async function fetchComments(query: string, page: number) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/comments?_limit=10&_page=${page}`
)
const data = await res.json()
return data.map((c: any) => ({ key: c.id, label: c.name }))
}
const value = ref([])
</script>
<template>
<VxTransfer
v-model="value"
:fetch-options="fetchComments"
filterable
:titles="['Comments (remote)', 'Selected']"
/>
</template>Loading
Tashqi loading holati — manba panelining pastki qismida spinner ko‘rsatadi.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 4 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([1])
const loading = ref(true)
</script>
<template>
<VxTransfer v-model="value" :data="data" :loading="loading" />
</template>Target Order
Odatda elementlar asl ma’lumotlar tartibini saqlaydi. Elementlarni tanlangan tartibda qo‘shish uchun target-order="push"’dan foydalaning.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 12 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([2])
</script>
<template>
<VxTransfer v-model="value" :data="data" target-order="push" />
</template>Button Visibility
Ko‘chirish tugmalaridan birini yoki ikkalasini yashiring.
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 6 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([1, 3])
</script>
<template>
<!-- Only allow adding to target (no remove button) -->
<VxTransfer v-model="value" :data="data" :show-to-source-button="false" />
<!-- Only allow removing from target (no add button) -->
<VxTransfer v-model="value" :data="data" :show-to-target-button="false" />
</template>Custom Empty State
Panelda elementlar bo‘lmaganda ko‘rsatiladigan kontentni moslashtirish uchun source-empty va target-empty slotlaridan foydalaning.
<template>
<VxTransfer :data="[]">
<template #source-empty>
<div style="text-align: center; padding: 20px;">
<div style="font-size: 24px;">📭</div>
<div>Nothing here yet</div>
<div style="opacity: 0.7;">Add items to get started</div>
</div>
</template>
<template #target-empty>
<div style="text-align: center; padding: 20px;">
<div style="font-size: 24px;">🎯</div>
<div>No selections</div>
<div style="opacity: 0.7;">Move items from the left</div>
</div>
</template>
</VxTransfer>
</template>Disabled
<script setup lang="ts">
import { ref } from 'vue'
const data = Array.from({ length: 6 }, (_, i) => ({
key: i + 1,
label: `Item ${i + 1}`,
}))
const value = ref([])
</script>
<template>
<VxTransfer v-model="value" :data="data" disabled />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | (string | number)[] | [] | Nishon ro‘yxatidagi kalitlar (v-model) |
data | TransferItem[] | [] | To‘liq ma’lumot manbasi |
titles | [string, string] | ['Source', 'Target'] | Panel sarlavhalari |
filterable | boolean | false | Qidiruv inputini ko‘rsatish |
filterPlaceholder | string | 'Search...' | Qidiruv placeholder |
filterMethod | (query, item) => boolean | — | Maxsus filtr funksiyasi |
disabled | boolean | false | Barcha interaksiyani o‘chirib qo‘yish |
loading | boolean | false | Tashqi loading holati |
targetOrder | 'original' | 'push' | 'original' | Nishon elementlarining tartibi |
buttonTexts | [string?, string?] | [] | Ko‘chirish tugmalari yozuvlari |
showToTargetButton | boolean | true | Nishonga ko‘chirish tugmasini ko‘rsatish |
showToSourceButton | boolean | true | Manbaga qaytarish tugmasini ko‘rsatish |
showMoveAll | boolean | false | Hammasini ko‘chirish tugmalarini ko‘rsatish |
pageSize | number | 0 | Bir sahifadagi elementlar soni (0 = hammasini ko‘rsatish) |
scrollDistance | number | 30 | Yuklashni boshlash uchun pastdan masofa (px) |
fetchOptions | (query, page) => Promise<TransferItem[]> | — | Asinxron masofaviy manba |
debounce | number | 300 | Masofaviy qidiruv uchun debounce (ms) |
keyField | string | 'key' | Element kaliti uchun xususiyat nomi |
labelField | string | 'label' | Element yozuvi uchun xususiyat nomi |
panelWidth | number | string | 240 | Panel kengligi (px yoki CSS qiymati) |
sortMethod | (a, b) => number | — | Elementlar uchun tartiblash comparatori |
draggable | boolean | false | Panellar orasida drag & drop’ni yoqish |
virtualScroll | boolean | false | Katta ro‘yxatlar uchun virtual scroll’ni yoqish |
itemHeight | number | 36 | Qator balandligi (px) — virtual scroll uchun |
Events
| Event | Type | Description |
|---|---|---|
update:modelValue | (value) | Nishon kalitlari o‘zgardi |
change | (value, direction, movedKeys) | Elementlar ko‘chirildi |
load-more | (page) | Keyingi masofaviy sahifa so‘ralgan |
Slots
| Slot | Props | Description |
|---|---|---|
item | { item } | Elementni maxsus renderlash |
source-header | { checked, total, query } | Manba paneli uchun maxsus header |
target-header | { checked, total, query } | Nishon paneli uchun maxsus header |
source-footer | { checked, total } | Manba paneli uchun maxsus footer |
target-footer | { checked, total } | Nishon paneli uchun maxsus footer |
source-empty | — | Manbaning bo‘sh holati |
target-empty | — | Nishonning bo‘sh holati |
to-target-button | { move, disabled, checkedCount } | Nishonga ko‘chirish tugmasini moslashtirish |
to-source-button | { move, disabled, checkedCount } | Manbaga qaytarish tugmasini moslashtirish |
Exposed Methods
| Method | Description |
|---|---|
moveRight() | Belgilangan elementlarni nishonga ko‘chirish |
moveLeft() | Belgilangan elementlarni manbaga qaytarish |
moveAllRight() | Barcha ruxsat etilgan elementlarni nishonga ko‘chirish |
moveAllLeft() | Barcha ruxsat etilgan elementlarni manbaga qaytarish |
TransferItem Interface
interface TransferItem {
key: string | number
label: string
disabled?: boolean
[k: string]: any
}