@eav93/vue3-treeselect
v0.7.0
Published
Tree select component with nested options for Vue 3.5+ (TypeScript). A fork of vue-treeselect with fixes and performance improvements for large trees.
Downloads
784
Maintainers
Readme
@eav93/vue3-treeselect
A multi-select component with nested options for Vue 3.5+, written in TypeScript.
This is a fork with fixes. It continues riophae/vue-treeselect (Vue 2) → megafetis/vue3-treeselect → SODA-NL/vue3-treeselect.
Compared to the other Vue 3 ports it is:
- rewritten with the Composition API and TypeScript (types are included);
- fixed: async search, focus handling, checkboxes, counts, disabled descendants, slots, keyboard navigation, IME input,
appendToBody(now a<Teleport>), a watcher leak in async search and more, see the changelog;- much faster on large trees (tens of thousands of nodes): incremental selection state, single-pass search, a flat and progressively rendered menu and optional virtual scrolling;
- covered by a test suite.
The API is the API of vue-treeselect, so its documentation applies, with the differences listed below.
Installation
npm install @eav93/vue3-treeselectvue ^3.5 is a peer dependency.
Usage
<template>
<Treeselect v-model="value" :options="options" :multiple="true" placeholder="Select..." />
</template>
<script setup>
import { ref } from 'vue'
import { Treeselect } from '@eav93/vue3-treeselect'
import '@eav93/vue3-treeselect/dist/vue3-treeselect.css'
const value = ref([])
const options = [
{
id: 'fruits',
label: 'Fruits',
children: [
{ id: 'apple', label: 'Apple' },
{ id: 'banana', label: 'Banana', isDisabled: true },
],
},
{ id: 'vegetables', label: 'Vegetables' },
]
</script>The component is also the default export (import Treeselect from '@eav93/vue3-treeselect').
A UMD build is available as dist/vue3-treeselect.umd.js (global Vue3Treeselect).
Differences from vue-treeselect
v-modelusesmodelValue/update:modelValue. All events (update:modelValue,select,deselect,open,close,search-change) get the instance id as the last argument.Slots:
option-label({ node, shouldShowCount, count, labelClassName, countClassName }),value-label({ node }),before-list,after-list.loadOptionsmay return a promise; forASYNC_SEARCHthe resolved value is used as the options.Selecting a branch never selects its disabled descendants, at any depth.
After a search the first match is highlighted (Enter selects it), and the options can change during a search.
Works with SSR (
appendToBodyincluded) and<KeepAlive>; slots and props are typed.New props:
| Prop | Default | Description | | --- | --- | --- | |
virtualScroll|false| Render only the options in the visible part of the menu. Rows must have a fixed height. | |optionHeight| measured | Row height in px forvirtualScroll. | |searchDebounceDelay|200| Debounce delay of the search input in ms. |Methods available on the component ref:
openMenu,closeMenu,toggleMenu,toggleExpanded,select,clear,removeLastValue,getValue,getNode,isSelected,focusInput,blurInput,getInput,getMenu,getControl,initialize,loadRootOptions,traverseAllNodesDFS,traverseAllNodesByIndex,traverseDescendantsBFS,traverseDescendantsDFS, and the stateforest,menu,trigger,localSearch,selectedNodes,internalValue.The menu is a flat list:
.vue-treeselect__list-itemrows are indented with.vue-treeselect__indent-level-Nclasses instead of being nested, and options hidden by a search are not rendered.
Localization
All texts are English by default. Other languages are separate entry points, so only the languages you import end up in your bundle (each one is under 1 kB).
For the whole app (recommended): provide the locale once, every <Treeselect> picks it up.
import { createApp } from 'vue'
import { TREESELECT_LOCALE } from '@eav93/vue3-treeselect'
import { ru } from '@eav93/vue3-treeselect/locales/ru'
createApp(App)
.provide(TREESELECT_LOCALE, ru)
.mount('#app')For one component: the locale prop.
<script setup>
import { de } from '@eav93/vue3-treeselect/locales/de'
</script>
<template>
<Treeselect :options="options" :locale="de" />
</template>By language code: register the locales you use, then pass the code. Regional codes
(ru-RU) map to the language.
import { registerLocale } from '@eav93/vue3-treeselect'
import { ru } from '@eav93/vue3-treeselect/locales/ru'
import { uk } from '@eav93/vue3-treeselect/locales/uk'
registerLocale('ru', ru)
registerLocale('uk', uk)<Treeselect :options="options" :locale="currentLanguage" /> <!-- 'ru', 'uk', 'en' -->Your own texts: pass an object; missing texts stay English. Everything can be overridden, including the texts of an imported locale:
<Treeselect :locale="{ placeholder: 'Pick one', noResultsText: 'Nothing found' }" />
<Treeselect :locale="{ ...ru, placeholder: 'Город' }" />The text props (placeholder, noResultsText, noOptionsText, noChildrenText, loadingText,
searchPromptText, retryText, retryTitle, clearAllText, clearValueText, limitText)
take precedence over the locale, so placeholder can still be set per component. Order of
precedence: text prop → locale prop → app-wide locale → English.
Available locales (@eav93/vue3-treeselect/locales/<code>): ar, cs, de, es, fr, he,
hi, id, it, ja, ko, nl, pl, pt, ro, ru, sv, tr, uk, vi, zh
(en is exported from the package root). ar and he are right-to-left: set dir="rtl" on
a parent element, the menu follows it (also with appendToBody).
With the UMD build (no bundler) there are no locale files: pass your texts as an object.
Customizing styles
Instead of the compiled CSS you can use the SCSS source and configure it with variables
(see the top of sass/style.scss for all of them):
@use "@eav93/vue3-treeselect/sass/style.scss" with (
$treeselect-control-border-color: #ced4da,
$treeselect-control-border-color-focus: #86b7fe,
$treeselect-option-bg-highlight: #f5f6fe,
$treeselect-checkbox-color-highlight: #26a65b,
$treeselect-multi-value-item-bg: #26a65b,
$treeselect-multi-value-item-bg-hover: #2bbb66,
$treeselect-multi-value-font-color: #fff,
);The SCSS needs the material-colors and sass-easing packages, which are installed as
dependencies. Configuring variables is preferable to overriding the generated rules with CSS:
it covers all states (hover, focus, disabled) and doesn't fight selector specificity.
Colors and sizes can also be changed at runtime, without Sass, with CSS custom properties:
--vue-treeselect-<name> corresponds to $treeselect-<name> (the SCSS value is the default), e.g.
.vue-treeselect {
--vue-treeselect-control-border-color: #ced4da;
--vue-treeselect-checkbox-checked-bg: #26a65b;
--vue-treeselect-padding: 8px;
}Set them on an ancestor of both the component and .vue-treeselect__portal-target (e.g. :root
or a theme class on body) when appendToBody is used: the menu is then rendered outside of the
component. The mark colors are --vue-treeselect-checkbox-mark-color / -disabled, the arrow of a
branch is mirrored with --vue-treeselect-arrow-direction: -1 (set automatically for [dir="rtl"]).
The indentation of a row is padding + level * narrow-cell-width, computed from the --level
custom property of the .vue-treeselect__list-item elements.
Checkbox marks are SVG masks painted with $treeselect-checkbox-icon-color / -disabled, and they
can be replaced with any CSS image (only its shape matters). For example, Bootstrap-like checkboxes:
@use "@eav93/vue3-treeselect/sass/style.scss" with (
$treeselect-checkbox-size: 1em,
$treeselect-checkbox-border-radius: 0.25em,
$treeselect-checkbox-color: var(--bs-border-color),
$treeselect-checkbox-color-highlight: var(--bs-primary),
$treeselect-checkbox-icon-width: 100%,
$treeselect-checkbox-icon-height: 100%,
$treeselect-checkbox-checked-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"),
$treeselect-checkbox-indeterminate-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"),
);Option rows are rendered in blocks with content-visibility: auto, so the browser skips rendering
of blocks outside the menu viewport. It clips content overflowing a block; if a custom option-label
slot renders something outside of the row, set $treeselect-option-content-visibility: visible
(or --vue-treeselect-option-content-visibility: visible).
Large trees
- Pass non-reactive options, e.g.
shallowReformarkRaw, and replace the array to update them. Reactive options are deep-watched (for compatibility), which costs memory and time on large trees. - The menu renders the first screens at once and the rest progressively, without blocking the page.
For very large expanded trees or search results use
virtualScroll. - Rows are rendered in blocks with
content-visibility: auto, so scrolling, keyboard navigation and selecting stay at 60 fps even with tens of thousands of rendered rows. npm run bench:browsermeasures opening a menu with 11k expanded options in Chrome (about 130 ms to the first paint, about 100 ms withvirtualScroll);bench/browser/perf.mjsmeasures scrolling, typing, selecting and more.
Development
Requires Node.js 20.19+ or 22.12+ (Vite 8).
npm install
npm test # tests (vitest + happy-dom)
npm run type-check
npm run build # dist/ (ES, CJS, UMD, CSS, type declarations)
npm run bench # benchmark in happy-dom
npm run bench:browser # benchmark in Chrome (CHROME_PATH, BASE_REF=<git ref> to compare)License
MIT. Originally created by Riophae Lee and ported to Vue 3 by megafetis.
