Giter Site home page Giter Site logo

vueform / multiselect Goto Github PK

View Code? Open in Web Editor NEW
768.0 4.0 146.0 2.76 MB

Vue 3 multiselect component with single select, multiselect and tagging options (+Tailwind CSS support).

Home Page: https://vueform.com

License: MIT License

JavaScript 86.13% Vue 5.60% SCSS 6.50% CSS 1.78%
vue select vueselect vue-select dropdown vue3 vue2 multiselect autocomplete tailwindcss

multiselect's Introduction

npm CircleCI npm bundle size (scoped version) Discord npm

Vue 3 Multiselect






Sponsors



Vueform

Vueform is comprehensive form development framework for Vue.js. It supercharges and standardizes the entire form building process and takes care of everything from rendering to validation and processing. With our latest tool, the Drag and Drop Form Builder, you can allow your developers & non-tech workforce to build the most complex forms without coding.

Feature highlights:

  • integrate Vueform Drag and Drop Form Builder into any application
  • save forms in database as a JSON
  • use your own form elements with custom configuration options
  • a complete theming and templating system with Tailwind support
  • 25+ form elements with multi-file uploads, date pickers and rich text editor
  • element nesting and repeating
  • 50+ validators with async, dependent and custom rules
  • conditional logic on element & form level
  • breaking forms into steps with form wizard
  • translating form content and global i18n support.
Vueform Builder

Learn more:

Other Vueform libraries:

  • @vueform/slider - Vue 3 slider component with multihandles, tooltips merging and formatting.
  • @vueform/toggle - Vue 3 toggle component with labels, custom slots and styling options.

Comparison with other libraries

Feature @vueform/multiselect vue-multiselect vue-select
Basic Features*
Vue.js 2 support
Vue.js 3 support ~ ~
Single select
Multiselect (without tags) -
Tags
Search & filtering
Option groups -
Advanced features*
Async search ~ ~
New option when using tags ~
New option when not using tags -
New option validation ~ -
Infinite scroll ~ ~
Append to body ~
Object value support -
Accents/diacritics sensitivity ~ ~
Search regex - ~
Native select support (required) - ~
Options definition*
Array
Object - -
Array of objects
Function (async) - -
Styling*
Override class names - -
CSS vars support -
Class based CSS support
Tailwind CSS support
- -
Support*
Accessibility (a11y) ~ ~
Internationalization (i18n) ~ ~
RTL support
Typescript support
SSR support
ES Module support (ESM) - -
CSP compilant - -
API*
Events 12 7 11
Slots 14 12 11
Documented API methods 10 0 0
Stats**
Minzipped size 9.7 KB 14.2 KB 20.6 KB
Open issues 18 233 191
Monthly downloads 180k 1.1M 947k
Dependencies 0 0 0
Coverage 100% unknown 96%
Latest realease 2022. 12. 21. 2019. 04. 27. 2022. 12. 18.

~ - partial support / requires manual extension or official support is in progress
* - as of June 13, 2022 - reviewed periodically
** - as of Dec 21, 2022 - reviewed periodically

Disclaimer: based on docs, Github issues and code discovery.

Docs

Demo

Check out our demo.

Installation

npm install @vueform/multiselect

Using with Vue 3

<template>
  <div>
    <Multiselect
      v-model="value"
      :options="options"
    />
  </div>
</template>

<script>
  import Multiselect from '@vueform/multiselect'

  export default {
    components: {
      Multiselect,
    },
    data() {
      return {
        value: null,
        options: [
          'Batman',
          'Robin',
          'Joker',
        ]
      }
    }
  }
</script>

<style src="@vueform/multiselect/themes/default.css"></style>

Using with Vue 2

<template>
  <div>
    <Multiselect
      v-model="value"
      :options="options"
    />
  </div>
</template>

<script>
  import Multiselect from '@vueform/multiselect/dist/multiselect.vue2.js'

  export default {
    components: {
      Multiselect,
    },
    data() {
      return {
        value: null,
        options: [
          'Batman',
          'Robin',
          'Joker',
        ]
      }
    }
  }
</script>

<style src="@vueform/multiselect/themes/default.css"></style>

Using with < Vue 2.7

Switch to <= 2.4.2 to use the Multiselect with Vue.js < 2.7.

Support

Join our Discord channel or open an issue.

Configuration

Basic props

Name {Type} Default Description
mode {string} 'single' Possible values: 'single'|'multiple'|'tags'.
options {array|object|function} [] List of options. Can be:
- an array (eg. [1,2,3])
- an object (eg. {a:1,b:2,c:3})
- an array of objects:
[
  {
    [valueProp]: 1,
    [label]: 'v1',
    disabled:true|false
  },
  //...
]
- a function returning a Promise (async function) with query and select$ param. The select$ represents the Multiselect component and its API can be accessed. The promise should return options as an object or as an array of objects.
When an array of objects is provided it must have properties that equal to :valueProp's, :trackBy's and :label's value.
groups {boolean} false Whether options should be grouped. Example:
{
  groups: true,
  options: [
    {
      [groupLabel]: 'Group label',
      [groupOptions]: {options},
      disabled: true|false,
    }
    //...
  ]
}
The {options} should equal to regular options definition.
groupLabel {string} 'label' The name of the property that contains the label of a group when options are provided in group format and groups is true.
groupOptions {string} 'options' The name of the property that contains the options of a group when options are provided in group format and groups is true.
groupSelect {boolean} true Whether groups can be selected when using multiple or tags mode.
groupHideEmpty {boolean} false Whether groups that have no options by default should be hidden.
required {boolean} false Whether the HTML5 required attribute should be used for multiselect (using an invisible fake input).
infinite {boolean} false Whether the actual option nodes should only be loaded on scroll. The limit option defines how many options are loaded initially and in each new batch.
appendToBody {boolean} false [Vue 3 only] (experimental) Whether the dropdown list should be appended to <body> and positioned absolutely.
appendTo {string} undefined [Vue 3 only] (experimental) Can be used instead of appendToBody to teleport the dropdown to a specific DOM. The value should be a query selector.
closeOnScroll {boolean} false Closes the dropdown list on scrolling parent DOM / window when using appendToBody: true.
searchable {boolean} false Whether the options should be searchable.
valueProp {string} 'value' If you provide an array of objects as options this property should be used as the value of the option.
trackBy {string|array} undefined The name(s) of the properties that should be searched when searchable is true and an array of objects are provided as options. If left undefined the label prop will be used instead.
label {string} 'label' If you provide an array of objects as options the value of this property will be displayed as selected option.
disabledProp {string} 'disabled' If you provide an array of objects as options this property should be used to determine whether the option is disabled.
placeholder {string} null The text that should be displayed before any option is selected.
multipleLabel function(value, select$) A function that returns the label to be displayed for selected options when using multiple mode. It receives value as first argument and the multiselect component select$ as second. By default it renders 1 option selected and [n] options selected based on value length.
disabled {boolean} false Whether the input should be disabled for the user (API can still be used programmatically).
inputType {string} 'text' The type attribute of the search input.
autocomplete {string} undefined The autocomplete attribute of the search input.
rtl {boolean} false Whether the multiselect should be right-to-left. It also respects dir="rtl" on any parent element.
max {number} -1 The maximum number of options that can be selected when using multiple or tags mode. If -1 the number of options won't be limited.
limit {number} -1 The maximum number of options that should be displayed. If -1 the number of options won't be limited.
loading {boolean} false Whether a loading spinner should be shown.
id {string} 'multiselect' The id of the multiselect container DOM.
caret {boolean} true Whether should display the caret symbol on the right.
locale {string} null The locale of the multiselect. If a locale is set labels might have an object value with different keys for different locales.
locale {string} 'en' The fallback locale.
noOptionsText {string|object} 'The list is empty' The text that should be displayed when options list is empty. It can be an object with different keys for different locales.
noResultsText {string|object} 'No results found' The text that should be displayed when there are no search results. It can be an object with different keys for different locales.
openDirection {string} 'bottom' Whether the option list should be displayed above or below the multiselect. Possible values: top|bottom
reverse {boolean} false Whether the option list should be reversed. Only works with groups: false.
regex {regex|string} undefined The regex that search input should be tested against when searchable: true.
strict {boolean} true Whether should regard accents/diacritics in search.
searchStart {boolean} false Whether the search should match the start of the options' trackBys.
searchFilter function(option, query, select$) null A custom search function that overrides the default search algorithm.
aria object An object containing aria attributes to be added for the multiselect.
classes object An object of class names that gets merged with the default values. Default: {
  container: 'multiselect',
  containerDisabled: 'is-disabled',
  containerOpen: 'is-open',
  containerOpenTop: 'is-open-top',
  containerActive: 'is-active',
  wrapper: 'multiselect-wrapper',
  singleLabel: 'multiselect-single-label',
  singleLabelText: 'multiselect-single-label-text',
  multipleLabel: 'multiselect-multiple-label',
  search: 'multiselect-search',
  tags: 'multiselect-tags',
  tag: 'multiselect-tag',
  tagDisabled: 'is-disabled',
  tagWrapper: 'multiselect-tag-wrapper',
  tagWrapperBreak: 'multiselect-tag-wrapper-break',
  tagRemove: 'multiselect-tag-remove',
  tagRemoveIcon: 'multiselect-tag-remove-icon',
  tagsSearchWrapper: 'multiselect-tags-search-wrapper',
  tagsSearch: 'multiselect-tags-search',
  tagsSearchCopy: 'multiselect-tags-search-copy',
  placeholder: 'multiselect-placeholder',
  caret: 'multiselect-caret',
  caretOpen: 'is-open',
  clear: 'multiselect-clear',
  clearIcon: 'multiselect-clear-icon',
  spinner: 'multiselect-spinner',
  infinite: 'multiselect-infinite',
  infiniteSpinner: 'multiselect-infinite-spinner',
  dropdown: 'multiselect-dropdown',
  dropdownTop: 'is-top',
  dropdownHidden: 'is-hidden',
  options: 'multiselect-options',
  optionsTop: 'is-top',
  group: 'multiselect-group',
  groupLabel: 'multiselect-group-label',
  groupLabelPointable: 'is-pointable',
  groupLabelPointed: 'is-pointed',
  groupLabelSelected: 'is-selected',
  groupLabelDisabled: 'is-disabled',
  groupLabelSelectedPointed: 'is-selected is-pointed',
  groupLabelSelectedDisabled: 'is-selected is-disabled',
  groupOptions: 'multiselect-group-options',
  option: 'multiselect-option',
  optionPointed: 'is-pointed',
  optionSelected: 'is-selected',
  optionDisabled: 'is-disabled',
  optionSelectedPointed: 'is-selected is-pointed',
  optionSelectedDisabled: 'is-selected is-disabled',
  noOptions: 'multiselect-no-options',
  noResults: 'multiselect-no-results',
  fakeInput: 'multiselect-fake-input',
  assist: 'multiselect-assistive-text'
  spacer: 'multiselect-spacer'
}
Vueform

Advanced Props

Name {Type} Default Description
allowAbsent {boolean} false Whether values should be allowed which are not part of options even when using object: false. The selected values which are not part of the option list will have the same value and label. This can be useful if you're using an async option list with an array of string options as a result where both labels and values will be the same and you want to have default values which are not part of the initially resolved options. Example #13
canDeselect {boolean} true Whether a selected option can be deselected when using single mode.
canClear {boolean} true Whether option(s) can be cleared.
clearOnSearch {boolean} false Whether the option list should be cleared when a new character is typed before loading new options list, when using async options.
clearOnSelect {boolean} true Whether the option list should be cleared upon selecting an option when using async options.
closeOnSelect {boolean} true Whether the option list should be closed upon selecting an option.
closeOnDeselect {boolean} true Whether the option list should be closed upon deselecting an option.
clearOnBlur {boolean} true Whether the search should be cleared when the input is blurred when searchable: true.
delay {number} -1 The delay in milliseconds that should occur between the last typed character and refreshing an async option list. If -1 the option list will not refresh when the search query changes. If 0 it will refresh without delay.
filterResults {boolean} true Whether option list should be filtered by search query. This may be set to false if you are handling filtering manually when returning async options.
minChars {number} 0 The minimum number of characters that should be typed to refresh async option list. If 0 it will refresh even when the search field becomes empty.
resolveOnLoad {boolean} true Whether async options should be loaded initially (with an empty query). This should be true if you are planning to load non-object value(s) initially while using async options (to fetch matching objects for values).
breakTags {boolean} false Whether long tags should be broken into multiple lines (otherwise truncated at max width).
appendNewTag {boolean} true Deprecated 2.3.0: use appendNewOption instead.
Whether it should append new tag automatically to option list when using tags mode with createTag. If set to false you need to take care of appending a new tag to the provided :options list upon @tag event.
createTag {boolean} false Deprecated 2.3.0: use createOption instead.
Whether it should allow creating new tags based on search query when using tags mode.
addTagOn {array} ['enter'] Deprecated 2.3.0: use addOptionOn instead.
The list of keys that creates a new tag while typing in the search field when having createTag enabled. Possible values: 'enter'|'space'|'tab'|';'|','.
appendNewOption {boolean} true Whether it should append new option automatically to option list when searchable and createTag are enabled. If set to false you need to take care of appending a new option to the provided :options list upon @option event.
createOption {boolean} false Whether it should allow creating new options based on search query when searchable is enabled.
addOptionOn {array} ['enter'] The list of keys that creates a new option while typing in the search field when having createOption enabled. Possible values: 'enter'|'space'|'tab'|';'|','.
onCreate function(option, select$) Transforms the created tag before being added when createOption is enabled. It receives the original option as first param, which is the object that would be added to the option list ({value: 'Value', label: 'Label'}) and the Multiselect component as the second. It should return an object that contains at least the keys defined by valueProp, label & trackBy options. If defined and returns false the option will not be added (the add can be handled manually by updating options & v-model).
hideSelected {boolean} true Whether selected options should be excluded from the option list when using multiple or tags mode.
showOptions {boolean} true Whether option list should be displayed. Can be used to create free-typed tags.
object {boolean} false Whether the value should be stored as an object.
If false:
value: ['js','jsx','ts']
If true:
value: [
  {value:'js',label:'Javascript'},
  {value:'jsx',label:'JSX'},
  {value:'ts',label:'Typescript'}
]
attrs {object} {} HTML attributes to add to the input field when search is enabled.
nativeSupport {boolean} false Whether hidden input fields should be appended to achieve native data handling.

API methods

Name Params Description
open Opens the options list.
close Closes the options list.
select option Selects an option based on its value.
deselect option Deselects an option based on its value.
remove option Alias for deselect.
selectAll Selects all options if mode is tags or multiple.
clear Deselects all selected options.
clearSearch Clears current search query.
refreshOptions callback Refreshes async options list.
setPointer option Points an option based on its value.

To access API use ref on Multiselect component:

<Multiselect
  v-model="value"
  :options="options"
  ref="multiselect"
/>
// eg:
mounted() {
  this.$refs.multiselect.open()
}

To programmatically open and focus the multiselect, call focus() on the element:

mounted() {
  this.$refs.multiselect.$el.focus()
}

Events

Event Attributes Description
@change value, select$ Emitted after the value is changed.
@select value, option, select$ Emitted after an option or tag is selected.
@deselect value, option, select$ Emitted after an option is deselected or a tag is removed.
@open select$ Emitted after opening the option list.
@close select$ Emitted after closing the option list.
@search-change query, select$ Emitted after a character is typed.
@tag query, select$ Deprecated 2.3.0: use @create instead. Emitted after enter is hit when a new tag is being created.
@option query, select$ Deprecated 2.6.0: use @create instead. Emitted after enter is hit when a new option is being created.
@create query, select$ Emitted after enter is hit when a new option is being created.
@clear select$ Emitted when the options are cleared.
@paste Event, select$ Emitted when value is pasted into the search field.
@keydown Event, select$ Emitted on keydown.
@keyup Event, select$ Emitted on keyup.
@max select$ Emitted when max is reached when in multiple or tags mode.

The select$ param in each event is the Multiselect component's instance.

Slots

Slot Attributes Description
placeholder Rendered as placeholder when the multiselect does not have value and placeholder prop is defined.
afterlist Rendered after the options list.
beforelist Rendered before the options list.
multiplelabel values Rendered when using multiple mode and options are selected. By default it renders the return value of multipleLabel function.
nooptions Rendered when the options list is empty. By default renders noOptionsText.
noresults Rendered when there are no search results. By default renders noResultsText.
grouplabel group, isPointed, isSelected Renders an option group label. The isPointed and isSelected props are function that used be used like isPointed(option) to determine the state.
option option, isPointed, isSelected, search Renders an option in options list. The isPointed and isSelected props are function that used be used like isPointed(option) to determine the state.
singlelabel value Rendered when using single mode and an option is selected. By default it renders the :label if the selected option.
tag option, handleTagRemove, disabled Renders a tag when using tags mode. When disabled the remove icon should not be displayed. The handleTagRemove prop should be used to trigger the removal of the tag.
caret handleCaretClick, isOpen Renders a small triangle on the right side of the multiselect. The content of the slot should have pointer-events: false when isOpen: false to replicate the original behaviour.
clear clear Renders a remove icon if the multiselect has any value. The clear method should be used on mousedown event.
spinner Renders a loader icon when async options are being fetched.
infinite Renders a loader icon when infinite scroll is in progress.

Note: we don't use camelCase because they are normalized back to lowercase when written in DOM.

Styling

Styling with CSS vars

The following CSS variables can be used to customize multiselect when using default.css:

--ms-font-size: 1rem;
--ms-line-height: 1.375;
--ms-bg: #FFFFFF;
--ms-bg-disabled: #F3F4F6;
--ms-border-color: #D1D5DB;
--ms-border-width: 1px;
--ms-border-color-active: #D1D5DB;
--ms-border-width-active: 1px;
--ms-radius: 4px;
--ms-py: 0.5rem;
--ms-px: 0.875rem;
--ms-ring-width: 3px;
--ms-ring-color: #10B98130;
--ms-placeholder-color: #9CA3AF;
--ms-max-height: 10rem;

--ms-spinner-color: #10B981;
--ms-caret-color: #999999;
--ms-clear-color: #999999;
--ms-clear-color-hover: #000000;
  
--ms-tag-font-size: 0.875rem;
--ms-tag-line-height: 1.25rem;
--ms-tag-font-weight: 600;
--ms-tag-bg: #10B981;
--ms-tag-bg-disabled: #9CA3AF;
--ms-tag-color: #FFFFFF;
--ms-tag-color-disabled: #FFFFFF;
--ms-tag-radius: 4px;
--ms-tag-py: 0.125rem;
--ms-tag-px: 0.5rem;
--ms-tag-my: 0.25rem;
--ms-tag-mx: 0.25rem;

--ms-tag-remove-radius: 4px;
--ms-tag-remove-py: 0.25rem;
--ms-tag-remove-px: 0.25rem;
--ms-tag-remove-my: 0rem;
--ms-tag-remove-mx: 0.125rem;

--ms-dropdown-bg: #FFFFFF;
--ms-dropdown-border-color: #D1D5DB;
--ms-dropdown-border-width: 1px;
--ms-dropdown-radius: 4px;

--ms-group-label-py: 0.3rem;
--ms-group-label-px: 0.75rem;
--ms-group-label-line-height: 1.375;
--ms-group-label-bg: #E5E7EB;
--ms-group-label-color: #374151;
--ms-group-label-bg-pointed: #D1D5DB;
--ms-group-label-color-pointed: #374151;
--ms-group-label-bg-disabled: #F3F4F6;
--ms-group-label-color-disabled: #D1D5DB;
--ms-group-label-bg-selected: #059669;
--ms-group-label-color-selected: #FFFFFF;
--ms-group-label-bg-selected-pointed: #0c9e70;
--ms-group-label-color-selected-pointed: #FFFFFF;
--ms-group-label-bg-selected-disabled: #75cfb1;
--ms-group-label-color-selected-disabled: #D1FAE5;

--ms-option-font-size: 1rem;
--ms-option-line-height: 1.375;
--ms-option-bg-pointed: #FFFFFF;
--ms-option-color-pointed: #1F2937;
--ms-option-bg-selected: #10B981;
--ms-option-color-selected: #FFFFFF;
--ms-option-bg-disabled: #FFFFFF;
--ms-option-color-disabled: #D1D5DB;
--ms-option-bg-selected-pointed: #26C08E;
--ms-option-color-selected-pointed: #FFFFFF;
--ms-option-bg-selected-disabled: #FFFFFF;
--ms-option-color-selected-disabled: #D1FAE5;
--ms-option-py: 0.5rem;
--ms-option-px: 0.75rem;

--ms-empty-color: #4B5563;

Override them globally:

:root {
  --ms-tag-bg: #059669;
  --ms-tag-color: #D1FAE5;
  --ms-tag-radius: 9999px;
  --ms-tag-font-weight: 400;
}

Or on an instance level:

<Multiselect
  v-model="value"
  :options="options"
  class="multiselect-green"
/>

<Multiselect
  v-model="value"
  :options="options"
  class="multiselect-blue"
/>
.multiselect-green {
  --ms-tag-bg: #D1FAE5;
  --ms-tag-color: #059669;
}

.multiselect-blue {
  --ms-tag-bg: #DBEAFE;
  --ms-tag-color: #2563EB;
}

Styling with Tailwind CSS

To use Multiselect with Tailwind CSS first you need install npm i -D mini-svg-data-uri and add background images to tailwind.config.js:

// tailwind.config.js

const svgToDataUri = require('mini-svg-data-uri')

module.exports = {
  theme: {
    extend: {
      backgroundImage: (theme) => ({
        'multiselect-caret': `url("${svgToDataUri(
          `<svg viewBox="0 0 320 512" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"></path></svg>`,
        )}")`,
        'multiselect-spinner': `url("${svgToDataUri(
          `<svg viewBox="0 0 512 512" fill="${theme('colors.green.500')}" xmlns="http://www.w3.org/2000/svg"><path d="M456.433 371.72l-27.79-16.045c-7.192-4.152-10.052-13.136-6.487-20.636 25.82-54.328 23.566-118.602-6.768-171.03-30.265-52.529-84.802-86.621-144.76-91.424C262.35 71.922 256 64.953 256 56.649V24.56c0-9.31 7.916-16.609 17.204-15.96 81.795 5.717 156.412 51.902 197.611 123.408 41.301 71.385 43.99 159.096 8.042 232.792-4.082 8.369-14.361 11.575-22.424 6.92z"></path></svg>`,
        )}")`,
        'multiselect-remove': `url("${svgToDataUri(
          `<svg viewBox="0 0 320 512" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path></svg>`,
        )}")`,
      })
    },
  }
}

Then you need to import themes/tailwind.css to you main component:

<template>
  <div id="app">
    <Multiselect ... />
  </div>
</template>

<script>
  // ...
</script>

<style>
  @import '@vueform/multiselect/themes/tailwind.css';
  /* or */
  /* @import './path/to/node_modules/@vueform/multiselect/themes/tailwind.css'; */
</style>

Using :classes prop

Alternatively you can define class names directly by passing them to the Multiselect component via classes property. When using this approach you don't need to import tailwind.css. Here's a default styling for Tailwind CSS (the same included in tailwind.css):

<Multiselect ... :classes="{
  container: 'relative mx-auto w-full flex items-center justify-end box-border cursor-pointer border border-gray-300 rounded bg-white text-base leading-snug outline-none',
  containerDisabled: 'cursor-default bg-gray-100',
  containerOpen: 'rounded-b-none',
  containerOpenTop: 'rounded-t-none',
  containerActive: 'ring ring-green-500 ring-opacity-30',
  wrapper: 'relative mx-auto w-full flex items-center justify-end box-border cursor-pointer outline-none',
  singleLabel: 'flex items-center h-full max-w-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 pr-16 box-border rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5',
  singleLabelText: 'overflow-ellipsis overflow-hidden block whitespace-nowrap max-w-full',
  multipleLabel: 'flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5',
  search: 'w-full absolute inset-0 outline-none focus:ring-0 appearance-none box-border border-0 text-base font-sans bg-white rounded pl-3.5 rtl:pl-0 rtl:pr-3.5',
  tags: 'flex-grow flex-shrink flex flex-wrap items-center mt-1 pl-2 min-w-0 rtl:pl-0 rtl:pr-2',
  tag: 'bg-green-500 text-white text-sm font-semibold py-0.5 pl-2 rounded mr-1 mb-1 flex items-center whitespace-nowrap min-w-0 rtl:pl-0 rtl:pr-2 rtl:mr-0 rtl:ml-1',
  tagDisabled: 'pr-2 opacity-50 rtl:pl-2',
  tagWrapper: 'whitespace-nowrap overflow-hidden overflow-ellipsis',
  tagWrapperBreak: 'whitespace-normal break-all',
  tagRemove: 'flex items-center justify-center p-1 mx-0.5 rounded-sm hover:bg-black hover:bg-opacity-10 group',
  tagRemoveIcon: 'bg-multiselect-remove bg-center bg-no-repeat opacity-30 inline-block w-3 h-3 group-hover:opacity-60',
  tagsSearchWrapper: 'inline-block relative mx-1 mb-1 flex-grow flex-shrink h-full',
  tagsSearch: 'absolute inset-0 border-0 outline-none focus:ring-0 appearance-none p-0 text-base font-sans box-border w-full',
  tagsSearchCopy: 'invisible whitespace-pre-wrap inline-block h-px',
  placeholder: 'flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 text-gray-400 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5',
  caret: 'bg-multiselect-caret bg-center bg-no-repeat w-2.5 h-4 py-px box-content mr-3.5 relative z-10 opacity-40 flex-shrink-0 flex-grow-0 transition-transform transform pointer-events-none rtl:mr-0 rtl:ml-3.5',
  caretOpen: 'rotate-180 pointer-events-auto',
  clear: 'pr-3.5 relative z-10 opacity-40 transition duration-300 flex-shrink-0 flex-grow-0 flex hover:opacity-80 rtl:pr-0 rtl:pl-3.5',
  clearIcon: 'bg-multiselect-remove bg-center bg-no-repeat w-2.5 h-4 py-px box-content inline-block',
  spinner: 'bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 mr-3.5 animate-spin flex-shrink-0 flex-grow-0 rtl:mr-0 rtl:ml-3.5',
  infinite: 'flex items-center justify-center w-full',
  infiniteSpinner: 'bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 animate-spin flex-shrink-0 flex-grow-0 m-3.5',
  dropdown: 'max-h-60 absolute -left-px -right-px bottom-0 transform translate-y-full border border-gray-300 -mt-px overflow-y-scroll z-50 bg-white flex flex-col rounded-b',
  dropdownTop: '-translate-y-full top-px bottom-auto rounded-b-none rounded-t',
  dropdownHidden: 'hidden',
  options: 'flex flex-col p-0 m-0 list-none',
  optionsTop: '',
  group: 'p-0 m-0',
  groupLabel: 'flex text-sm box-border items-center justify-start text-left py-1 px-3 font-semibold bg-gray-200 cursor-default leading-normal',
  groupLabelPointable: 'cursor-pointer',
  groupLabelPointed: 'bg-gray-300 text-gray-700',
  groupLabelSelected: 'bg-green-600 text-white',
  groupLabelDisabled: 'bg-gray-100 text-gray-300 cursor-not-allowed',
  groupLabelSelectedPointed: 'bg-green-600 text-white opacity-90',
  groupLabelSelectedDisabled: 'text-green-100 bg-green-600 bg-opacity-50 cursor-not-allowed',
  groupOptions: 'p-0 m-0',
  option: 'flex items-center justify-start box-border text-left cursor-pointer text-base leading-snug py-2 px-3',
  optionPointed: 'text-gray-800 bg-gray-100',
  optionSelected: 'text-white bg-green-500',
  optionDisabled: 'text-gray-300 cursor-not-allowed',
  optionSelectedPointed: 'text-white bg-green-500 opacity-90',
  optionSelectedDisabled: 'text-green-100 bg-green-500 bg-opacity-50 cursor-not-allowed',
  noOptions: 'py-2 px-3 text-gray-600 bg-white text-left rtl:text-right',
  noResults: 'py-2 px-3 text-gray-600 bg-white text-left rtl:text-right',
  fakeInput: 'bg-transparent absolute left-0 right-0 -bottom-px w-full h-px border-0 p-0 appearance-none outline-none text-transparent',
  assist: 'absolute -m-px w-px h-px overflow-hidden',
  spacer: 'h-9 py-px box-content'
}" />

Certain classes has different states which are merged to the base class when the state is active. For example dropdown will be merged with dropdownTop when open-direction: 'top' resulting in the following classes: absolute -left-px -right-px bottom-0 transform translate-y-full border border-gray-300 -mt-px overflow-y-scroll z-50 bg-white flex flex-col rounded-b -translate-y-full top-px bottom-auto rounded-b-none rounded-t

The same is true for container, tag, options, groupLabel and option classes.

In case you need to override the same type of utility you might use @neojp/tailwind-important-variant for eg. bg-green-500!.

Examples

Single select

<Multiselect
  v-model="value"
  :options="['Batman', 'Robin', 'Joker']"
/>

JSFiddle - Example #1

Multiselect with object options

<Multiselect
  v-model="value"
  mode="multiple"
  :close-on-select="false"
  :options="{
    batman: 'Batman',
    robin: 'Robin',
    joker: 'Joker'
  }"
/>

JSFiddle - Example #2

Multiselect with disabled options

<Multiselect
  v-model="value"
  mode="multiple"
  :close-on-select="false"
  :options="[
    { value: 'batman', label: 'Batman' },
    { value: 'robin', label: 'Robin', disabled: true },
    { value: 'joker', label: 'Joker' },
  ]"
/>

JSFiddle - Example #3

Multiselect with groups

<Multiselect
  v-model="value"
  mode="multiple"
  :close-on-select="false"
  :groups="true"
  :options="[
    {
      label: 'DC',
      options: ['Batman', 'Robin', 'Joker'],
    },
    {
      label: 'Marvel',
      options: ['Spiderman', 'Iron Man', 'Captain America'],
    },
  ]"
/>

JSFiddle - Example #4

Tags with search, create and array of objects options

<Multiselect
  v-model="value"
  mode="tags"
  :close-on-select="false"
  :searchable="true"
  :create-option="true"
  :options="[
    { value: 'batman', label: 'Batman' },
    { value: 'robin', label: 'Robin' },
    { value: 'joker', label: 'Joker' },
  ]"
/>

JSFiddle - Example #5

Autocomplete with async options

<Multiselect
  v-model="value"
  placeholder="Choose a programming language"
  :filter-results="false"
  :min-chars="1"
  :resolve-on-load="false"
  :delay="0"
  :searchable="true"
  :options="async function(query) {
    return await fetchLanguages(query) // check JS block in JSFiddle for implementation
  }"
/>

JSFiddle - Example #6

Tags with async options

<Multiselect
  v-model="value"
  mode="tags"
  placeholder="Choose your stack"
  :close-on-select="false"
  :filter-results="false"
  :min-chars="1"
  :resolve-on-load="false"
  :delay="0"
  :searchable="true"
  :options="async function(query, select$) {
    return await fetchLanguages(query) // check JS block in JSFiddle for implementation
  }"
/>

JSFiddle - Example #7

Select with custom options slot

<Multiselect
  v-model="value"
  placeholder="Select your character"
  label="name"
  :options="[
    { value: 'captainamerica', name: 'Captain America', icon: 'https://cdn2.iconfinder.com/data/icons/avengers-filled/48/03_-_Captain_America_-_infinity_war_-_end_game_-_marvel_-_avengers_-_super_hero-512.png' },
    { value: 'spiderman', name: 'Spiderman', icon: 'https://cdn2.iconfinder.com/data/icons/avengers-filled/48/12_-_Spiderman_-_infinity_war_-_end_game_-_marvel_-_avengers_-_super_hero-512.png' },
    { value: 'ironman', name: 'Iron Man', icon: 'https://cdn2.iconfinder.com/data/icons/avengers-filled/48/02_-_IRONMAN_-_infinity_war_-_end_game_-_marvel_-_avengers_-_super_hero-512.png' },
  ]"
>
  <template v-slot:singlelabel="{ value }">
    <div class="multiselect-single-label">
      <img class="character-label-icon" :src="value.icon"> {{ value.name }}
    </div>
  </template>

  <template v-slot:option="{ option }">
    <img class="character-option-icon" :src="option.icon"> {{ option.name }}
  </template>
</Multiselect>

JSFiddle - Example #8

Multiselect with custom label slot

<Multiselect
  v-model="value"
  mode="multiple"
  placeholder="Select your characters"
  :close-on-select="false"
  :options="{
    batman: 'Batman',
    robin: 'Robin',
    joker: 'Joker'
  }"
>
  <template v-slot:multiplelabel="{ values }">
    <div class="multiselect-multiple-label">
      {{ values.length }} characters selected
    </div>
  </template>
</Multiselect>

JSFiddle - Example #9

Tags with custom tags slot

<template>
  <Multiselect
    v-model="value"
    mode="tags"
    placeholder="Select employees"
    track-by="name"
    label="name"
    :close-on-select="false"
    :searchable="true"
    :options="[
      { value: 'judy', name: 'Judy', image: 'https://randomuser.me/api/portraits/med/women/1.jpg' },
      { value: 'jane', name: 'Jane', image: 'https://randomuser.me/api/portraits/med/women/2.jpg' },
      { value: 'john', name: 'John', image: 'https://randomuser.me/api/portraits/med/men/1.jpg' },
      { value: 'joe', name: 'Joe', image: 'https://randomuser.me/api/portraits/med/men/2.jpg' }
    ]"
  >
      <template v-slot:tag="{ option, handleTagRemove, disabled }">
        <div
          class="multiselect-tag is-user"
          :class="{
            'is-disabled': disabled
          }"
        >
          <img :src="option.image">
          {{ option.name }}
          <span
            v-if="!disabled"
            class="multiselect-tag-remove"
            @click="handleTagRemove(option, $event)"
          >
            <span class="multiselect-tag-remove-icon"></span>
          </span>
        </div>
      </template>
  </Multiselect>
</template>

<style>
  .multiselect-tag.is-user {
    padding: 5px 8px;
    border-radius: 22px;
    background: #35495e;
    margin: 3px 3px 8px;
  }

  .multiselect-tag.is-user img {
    width: 18px;
    border-radius: 50%;
    height: 18px;
    margin-right: 8px;
    border: 2px solid #ffffffbf;
  }

  .multiselect-tag.is-user i:before {
    color: #ffffff;
    border-radius: 50%;;
  }

  .user-image {
    margin: 0 6px 0 0;
    border-radius: 50%;
    height: 22px;
  }
</style>

JSFiddle - Example #10

Async options with default values

When using resolveOnLoad: false we can add default values with object: true and providing options as objects, containing both label and value props. This is because option list is not resolved when the component is mounted so Multiselect has no idea of what option labels should be if only plain values were provided.

<template>
  <Multiselect
    mode="tags"
    v-model="value"
    placeholder="Select options"
    :close-on-select="false"
    :searchable="true"
    :object="true"
    :resolve-on-load="false"
    :delay="0"
    :min-chars="1"
    :options="async (query) => {
      return await fetchLanguages(query)
    }"
  />
</template>
<script>
export default {
  data: () => ({
    value: [
      { value: 'Java', label: 'Java' },
      { value: 'JavaScript', label: 'JavaScript' },
    ]
  })
}
</script>

JSFiddle - Example #11

Default values that are not among the options using object: true

If we want to add default values without having to add them to options list we can use object: true and provide them as objects, containing both label and value props. This is because if a plain value is not among Multiselect options it has no idea of what option label should be.

<template>
  <Multiselect
    mode="tags"
    v-model="value"
    placeholder="Select options"
    :close-on-select="false"
    :searchable="true"
    :object="true"
    :resolve-on-load="false"
    :delay="0"
    :min-chars="1"
    :options="async (query) => {
      return await fetchLanguages(query)
    }"
  />
</template>
<script>
export default {
  data: () => ({
    value: [
      { value: 'Java', label: 'Java' },
      { value: 'JavaScript', label: 'JavaScript' },
    ]
  })
}
</script>

JSFiddle - Example #12

Default values that are not among the options using allowAbsent: true

If our async option list returns an array of strings we can use allowAbsent: true to allow value(s) which are not among the option list. The reason why this only works with an array of strings option list is because plain values like Java and JavaScript will use the same string for label and value.

<template>
  <Multiselect
    mode="tags"
    v-model="value"
    placeholder="Select options"
    :allow-absent="true"
    :close-on-select="false"
    :searchable="true"
    :resolve-on-load="false"
    :delay="0"
    :min-chars="1"
    :options="async (query) => {
      return await fetchLanguages(query)
    }"
  />
</template>
<script>
export default {
  data: () => ({
    value: [
      'Java',
      'JavaScript',
    ]
  })
}
</script>

JSFiddle - Example #13

Manage created tag asynchronously

Search is restricted by regex and tag creation is controlled by onCreate(option, select$).

<template>
  <Multiselect
    mode="tags"
    v-model="value"
    placeholder="Accepts numbers <= 67 (delay: 1000ms)"
    :options="[]"
    :create-option="true"
    :searchable="true"
    :regex="/\d/"
    :on-create="handleTagCreate"
  />
</template>
<script>
export default {
  methods: {
    handleTagCreate: async (option, select$) => {
      // Do not allow create tags above 67
      if (parseInt(option.value) > 67) {
        alert(`${option.value} is not allowed. Option must by <= 67.`)

        // If returns `false` the tag will not be added
        return false
      }

      // Async request (eg. for validating)
      await new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve()
        }, 1000)
      })

      // Modifying option label
      option.label = option.label + ' - confirmed'

      return option
    }
  }
}
</script>

JSFiddle - Example #14

Load async options from API on open with infinite scroll

Options are not loaded initially, only when the users clicks the dropdown the first time. It also virtualizes the option list with infinite: true even large list of options can be loaded.

<Multiselect
  v-model="value"
  mode="tags"
  placeholder="Choose your stack"
  :close-on-select="false"
  :filter-results="false"
  :min-chars="0"
  :resolve-on-load="false"
  :infinite="true"
  :limit="10"
  :clear-on-search="true"
  :delay="0"
  :searchable="true"
  :options="async (query) => {
    return await fetchLanguages(query)
  }"
  @open="(select$) => {
    if (select$.noOptions) {
      select$.resolveOptions()
    }
  }"
/>

JSFiddle - Example #15

Multiselect with localized texts

Options are not loaded initially, only when the users clicks the dropdown the first time. It also virtualizes the option list with infinite: true even large list of options can be loaded.

<template>
  <Multiselect
    v-model="value"
    locale="de"
    fallback-locale="en"
    :options="[
      { value: 1, label: { en: 'One', de: 'Eins' } },
      { value: 2, label: { en: 'Two' } },
      { value: 3, label: { es: 'Tres'} },
      { value: 4, label: 'Four' },
    ]"
  />
</template>
<script>
export default {
  data: () => ({
    value: [1]
  })
}
</script>

JSFiddle - Example #16

License

MIT

multiselect's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

multiselect's Issues

Pre-populate tags

In using multiselect in tags mode is it possible to pre-populate tags with existing values when showing the control.

See https://jsfiddle.net/57okrzx2/3/ which sets the v-model value on creation but it isn't reflected on the control

Thanks

.select() after .refreshOptions() race

Version

  • Vue version: 3

Description

I'm using a callback in refreshOptions to update the options for a rendered multi-select. Providing an option via this update that matches the value in modelValue isn't selected automatically, and I cannot select it via the .select() api as there is no promise or similar to respond to.

Is there a work-around or something I'm missing?

Demo

this.$refs.input.refreshOptions(
          this.getItems().then(() => {
            this.$refs.input.select(this.modelValue); // this fails because the multi-select isn't yet up to date with the new options, I probably shouldn't have to do this at all considering the modelValue is already set.
          })
        );

Placeholder in input

Hi!

Using v1.2.5 multiselect in the single mode with searchable: true

When i set placeholder option i see it in html markup in div.multiselect-placeholder
Is there any way to move placeholder into input field (div.multiselect-search > input)?

Thanks.

Custom "valueProp" not working in single mode

Hello together. Happy new year.

When I select a option, modelValue changed but the input stays empty.

Here my code example:

<template>
  <div>
    <Multiselect
      v-model="value"
      :options="options"
      value-prop="customer"
      label="bezeichnung1"
    />
  </div>
</template>

<script>
import Multiselect from "@vueform/multiselect";

export default {
  components: {
    Multiselect,
  },
  data() {
    return {
      value: null,
      options: [
        { bezeichnung1: "test", customer: "1" },
        { bezeichnung1: "test2", customer: "2" },
        { bezeichnung1: "test3", customer: "3" },
        { bezeichnung1: "test4", customer: "4" },
      ],
    };
  },
};
</script>

<style src="@vueform/multiselect/themes/default.css"></style>

When I change "customer" to "value" in template and script, it's working.
This problem is only in "single" mode.

Styling horizontal scrollbar

Hello,

is it intended that there is always a scrollbar at the bottom of the options dropdown?

If not, maybe change:

.multiselect-options{ overflow: scroll }

to:

.multiselect-options{ overflow-y: scroll }

regards,
lux

Disabled for options is not possible

Hi,

need to disable a few options of a filteredList and still need them to be listed in the dropdown (like crossed out).

I tried with:

[ { "value": "1", "label": "de-AT", "disabled": false }, { "value": "2", "label": "de-CH", "disabled": false }, { "value": "3, "label": "en-GB", "disabled": false }, { "value": "4", "label": "en-CA", "disabled": false }, { "value": "5", "label": "de-DE", "disabled": true }, { "value": "6", "label": "en-US", "disabled": false } ]

as options, but this doesn't really do anything.

Is there a possibility to achieve this or is your multiselect designed to display only choosable options?

does not work at all on Android

Version 1.3.5

  • Vue version: 2

Description

This is a pretty big deal.. I just noticed this multiselect is completely unresponsive on my Android phone. It's not only my personal project, your own demo shows the same results https://jsfiddle.net/q6Lnpr7a/

Try the Autocomplete demo #5. I haven't tested it on IOS but on Android if you try to type inside the autocomplete box, the text goes on top of the placeholder, and it doesn't actually perform any search! It appears to perform a search when you tap outside the box, but then I think that also causes your text to disappear to it doesn't actually search.

Works perfectly on desktop. Also you can't reproduce this using Desktop Chrome's "device toolbar". Maybe you can reproduce it with GenyMotion/Xcode simulator.. hopefully you have an Android phone available.

Label text doesn't change if options updated

Version

  • Vue version: 3

Description

We have typical use case: country select, translated to several locales. Options is array of objects:

const options = [{code: 'au', name: 'Autstralia'}]

If we have selected value au for example in English locale, our label shows Australia. But when change locale for example to Russian (ru), and update options, label will remain the same

I wrote test-case for this to clear our problem:

it('should reactively changes label when options has been changed', () => {
  let select = createSelect({
    value: 'ru',
    label: 'name',
    valueProp: 'code',
    options: [{ code: 'au', name: 'Australia' }, { code: 'ru', name: 'Russia' }, { code: 'us', name: 'USA' }],
  })

  select.vm.options = [{ code: 'au', name: 'Австралия' }, { code: 'ru', name: 'Россия' }, { code: 'us', name: 'США' }]

  expect(select.find('.multiselect-single-label').element).toBeVisible()
  expect(select.find('.multiselect-single-label').html()).toContain('Россия') // <---- Fails here, but shouldn't
})

Demo

https://jsfiddle.net/cubk7z50/13/

Return values, not keys

Is it possible to get the selected values as ['kia', 'mazda'] and not [1, 2] as it is now?

options: ['ford', 'kia', 'mazda'] and mode is tags

Prefilled tags without providing options

As mentioned in #33 I would like to be able to prefill tags by only using the v-model. Currently (v1.3.3) I need to provide :options with matching tags to prefill the tags input even when setting the new showOptions/show-options property to false.
That's redundant imo.

Search containing spaces in single mode

Hi,

I'm having some trouble when the component is searcheable and the search term contains spaces. When the user types space, the first option of the combo is selected / deselected, making it impossible to search for more than one term. This can be seen in demo number 5 - Autocomplete with async options (https://jsfiddle.net/q6Lnpr7a/). I'm not sure, but I suspect the problem is onKeyup event.

image

Could you check it, please?

Thank you very much.

Adding tag by 'Space'

For now, we add a tag by 'Enter'. Is there any way to add a tag (via creating) by 'Space'?
E.g. typing 'tagNumber1', pressing 'Space', boom, tag created.

Unable to output array of object

Hello and Happy new year.
Probably the solution is very easy but I just can't make it work.
My problem is
when i trying to use Multiselect with an array of objects from api.

.get("/strategy/g") .then((response) => { this.strategies = response.data; })

in 'strategies' is stored the data
the output looks like this:
[ { "id": 2, "name": "Test one", "user_id": 1, }, { "id": 1, "name": "Test two ", "user_id": 2 } ]

Now when i try to output this in Multiselect

<Multiselect v-model="form.strategy_id" label="name" valueProp="id" trackBy="name" :searchable="true" :createTag="true" :object="true" :options="strategies" />

i get nothing

I also have tried like this but unfortunately without success

<Multiselect v-model="form.strategy_id" label="label" valueProp="value" trackBy="label" :searchable="true" :createTag="true" :object="true" :options="strategies.map((item) => { return { value: item.id, label: item.name }; })" />

Thanks in advance

Show previously added options recieved from server when using async options search

Show previously added options when using async options search

I`m using the select for async search options

      v-model="cities"
      mode="tags"
      :searchable="true"
      :options="async (query) => {
        return await fetchCities(query)
      }"
      :minChars="1"
      :resolveOnLoad="true"
      :delay="0"

I set resolveOnLoad="true", but how can I show tags, that asynchronously comes to v-model on form init, in the select field? Is there any way to set something like 'defaultOptions / initialOptions' when I`m "searching" for options and adding them as tags?

Add event on `nooptions` or `noresults` click

Hi there folks,

If the title is not already clear I try to explain what I mean and what I am trying to achieve. I am using Vue3 by the way.

I would like to have an event when the user clicks on the nooptions and noresults slots - just like the @select event. At the moment it doesn't work if I add a @click on the custom template slot as per below example:

<multiselect ...>
    <template v-slot:noresults>
        <div class="multiselect-option" @click="openModal">
            Create New
        </div>
    </template>
</multiselect>

Am I missing something or at the current stage it should not work until a custom event within the package is added?

Open direction - UP

Hi, many thanks for implementing html5 required feature. I think your project is going to be very popular because everybody who uses vue-multiselect and plans to migrate to vue3 looks for alternatives and your project stands out from the rest.

I'm writing for another feature request. Currently if multiselect is near the bottom of modal , or near the bottom of page it opens to bottom and user has to scroll down to see available options. This is very unpleasant experience, especially if user uses mobile phone. Is there any way to get open direction to top?

Unable to scroll multiselect component dropdown within modal

Version

  • Vue version: 3

Description

Unable to scroll through dropdown options whenever multiselect component is inside div element with tabindex value assigned.

I need to use such component inside a modal whose tabindex value is set to "-1" by default. When I try scrolling through dropdown options by clicking anywhere in the scrollbar, the dropdown list is immediately closes, losing focus. Any parent div element with any tabindex value assigned (not empty) causes this behavior. It's important to note that scrolling works by using the keyboard or mouse wheel. I'm able to select items in this dropdown list by clicking them, it's only a scrollbar issue.

Demo

https://jsfiddle.net/x5yhzou2/1/

Failing to get initial value (Vue 3)

This is my code. It works just fine, except when "restaurantId" has initial value, it doesn't pre-select the option.

<restaurant-autocomplete v-model="filters.restaurantId" /> //Int

RestaurantAutocomplete.vue:

<template>
    <Multiselect
		    :delay="500"
		    :resolveOnLoad="!!$attrs.modelValue"
		    :options="async (q) => await autocomplete(q)" // [{value: Int, label: String}]
		    :searchable="true"
		    :filterResults="false"
		    :clearOnSearch="true"
		    :clearOnSelect="false"
		    placeholder="Restaurant"
		    :loading="loading"
		    @select="$emit('update:modelValue', $event)"/>
</template>

Bug cross and arrow

Version

  • Vue version:
    Vue 3

Description

The cross and arrow when you select a person is a bit confusing to use.
I was retouching it, and I have seen that it is due to the heigth default of the images, but I think that the optimal solution instead of hiding it due to overflow, hide it with a v-if if it detects that there is a selected value the arrow that disappears and becomes the cross and the other way around. regards!

Demo

Please use our JSFiddle template to reproduce the bug. Issues without working reproduction might be ignored.
https://jsfiddle.net/2rnhytpg/
I don't know how that website works, but I think it's already here

<Multiselect
v-model="value"
mode="tags"
placeholder="Select employees"
trackBy="name"
label="name"
:search="true"
:options="[
{
value: 'judy',
name: 'Judy',
image: 'https://randomuser.me/api/portraits/med/women/1.jpg',
},
{
value: 'jane',
name: 'Jane',
image: 'https://randomuser.me/api/portraits/med/women/2.jpg',
},
{
value: 'john',
name: 'John',
image: 'https://randomuser.me/api/portraits/med/men/1.jpg',
},
{
value: 'joe',
name: 'Joe',
image: 'https://randomuser.me/api/portraits/med/men/2.jpg',
},
]"

<template v-slot:tag="{ option, handleTagRemove, disabled }">
  <div class="multiselect-tag is-user">
    <img :src="option.image" />
    {{ option.name }}
    <i
      v-if="!disabled"
      @click.prevent
      @mousedown.prevent.stop="handleTagRemove(option, $event)"
    />
  </div>
</template>
<template v-slot:option="{ option }">
  <img class="character-option-icon" :src="option.image" />
  {{ option.name }}
</template>
---- ![image](https://user-images.githubusercontent.com/40855265/114036201-293c8500-9880-11eb-983b-7fa0d71d57ac.png) ![image](https://user-images.githubusercontent.com/40855265/114036240-30fc2980-9880-11eb-8424-d6fa1dccc21e.png) ![image](https://user-images.githubusercontent.com/40855265/114036282-3b1e2800-9880-11eb-823a-2ac8c1133d94.png) ![image](https://user-images.githubusercontent.com/40855265/114036330-45d8bd00-9880-11eb-9fbf-f445499b7f3a.png)

Select first option on enter when search is enabled

I stumbled upon this in my project but could reproduce in the demo with "example #5 - Autocomplete with async options" as well:

  1. Type in "perl"
  2. An async list is loaded with a single entry "Perl"
  3. Press Enter
  4. Nothing happens, but the first list entry should be selected

Note this issue is not about case-insensitive search! The async backend function could also return "Practical Extraction and Report Language" when searching for "perl". My use case is a search for languages where searching for the language names or codes returns language names, e.g. search "de" will show first list entry "German".

Problem when searching in component "tags with async options"

Version

  • Vue version: 3

Description

Hi,

I'm having some trouble when doing some search in the "tags with async options". When the user do some search, pick an option and do another search that does not contain the first picked option, the component stop working. I'm not sure, but I suspect the problem is that the component try to retrieve some information about the tag in the options list but the option array doesn't have it on the second search.

Demo

This issue can be easily reproduced in example number 6 - Tags with async options (https://jsfiddle.net/q6Lnpr7a/).

 To reproduce this error:

 1 - Search for 'a';
 2 - Choose '@Formula' option;
 3 - Search for 'b';

image

Thank you very much.

filterResults not work

<template>
  <div>
    <multiselect2
      mode="single"
      :value="selectedCountries"
      label="entityName"
      trackBy="entityName"
      @select="select"
      :object="true"
      :placeholder="placeholder"
      open-direction="bottom"
      :options="countries"
      :searchable="true"
      :loading="isLoading"
      :internal-search="false"
      :clear-on-select="false"
      :close-on-select="false"
      :filterResults="false"
      :options-limit="300"
      :limit-text="limitText"
      :max-height="200"
      @search-change="asyncFind"
    >
      <template #noResult>
        <span>Oops! No elements found.</span>
      </template>
    </multiselect2>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, nextTick } from "vue";
import _ from "lodash";
import Multiselect from "@vueform/multiselect";
import { useStore } from "../../../../store/modules";
import {
  ActionTypes,
  AirPortType,
} from "../../../../../src/store/modules/avia/contract";
const MIN_LEN_SEARCH = 2;
export default defineComponent({

  name: "SearchAirPort",
  props:{
    placeholder: String
  },
  components: {
    Multiselect2: Multiselect,
  },
  setup(props, { emit }) {
    // пока так не могу обойти фильтра селекта
    type extendAirPort = {
      entityId: number;
      languageId: number;
      entityCode: string;
      entityName: string;
      complexName: string;
    };
    type searchCity = Array<extendAirPort>;
    const selectedCountries = ref<AirPortType | null>(null);
    const countries = ref<searchCity>([]);
    const isLoading = ref<boolean>(false);
    const store = useStore();
    const limitText = (count) => {
      return `and ${count} other countries`;
    };

    const asyncFindFunc = _.debounce(async (query: string) => {
      console.log("search");
      isLoading.value = true;
      const paramsIn = {
        searhAirport: query,
      };
      const res = await store.dispatch(ActionTypes.SEARH_AIRPORT, paramsIn);

      if (res) {
        countries.value = res.map((item) => {
          return {
            ...item,
            complexName: item.entityCode + " " + item.entityName,
          };
        });
      }
      isLoading.value = false;
    }, 500);

    const asyncFind = (query: string) => {
      if (query && query.length > MIN_LEN_SEARCH) {
        asyncFindFunc(query);
      }
    };

    const select = (selectedOption: null | AirPortType) => {
      selectedCountries.value = selectedOption;
      let convetData: AirPortType | null = null;
      if (selectedOption) {
        convetData = {
          entityId: selectedOption.entityId,
          languageId: selectedOption.languageId,
          entityCode: selectedOption.entityCode,
          entityName: selectedOption.entityName,
        };
      }

      emit("select-data", convetData);
    };
    return {
      selectedCountries,
      countries,
      isLoading,
      limitText,
      asyncFind,
      select,
    };
  },
});
</script>

<style scoped></style>

пришлось пойти на хитрость изобретать extendAirPort что бы все норм было.
но проблема в том что фильтрация не отключается как показано в документации

I had to go to the trick of inventing extendAirPort that everything would be normal.
but the problem is that filtering is not disabled as shown in the documentation

v-model value not respected on start

Hey!

Thank you very much for this very easy to use and customizable component! 🎉

It works very well so far, except showing initial selected values. In the example below I load a list of users and their associated roles and a list of all roles as well.
The user.role_ids property is an array with only one value [ 1 ], but nothing is displayed in the Multiselect component.
When selecting one of the options in the Multiselect dropdown, the user.role_ids changes and the entry is displayed in the components div.

But I can see no difference in the result in user.role_ids.
Is there something I am missing or using the component the wrong way?

Example:

<template>
    <div>
        <div v-for="user in state.users" :key="user.id">
            {{ user }}
            // { "id": 1, "name": "Admin", "email": "....", "created_at": "2017-12-20T09:47:36.000000Z",
            // "updated_at": "2021-01-26T14:19:00.000000Z", "role_ids": [ 1 ],
            // "roles": [ { "id": 1, "name": "admin", "display_name": "Administrator", "description": "Project Administrator",
            // "created_at": "2017-12-20T09:47:35.000000Z", "updated_at": "2017-12-20T09:47:35.000000Z" } ] } 

            {{ user.role_ids }}
            // [ 1 ]

             {{ state.roles }}
             // [ { "id": 1, "name": "admin", "display_name": "Administrator", "description": "Project Administrator",
             // "created_at": "2017-12-20T09:47:35.000000Z", "updated_at": "2017-12-20T09:47:35.000000Z" },
             // { "id": 2, "name": "guest", "display_name": "Guest", "description": "Guest User",
             // "created_at": "2017-12-20T09:47:35.000000Z", "updated_at": "2017-12-20T09:47:35.000000Z" } ] 
             <multiselect
                 v-model="user.role_ids"
                 :label="'display_name'"
                 :track-by="'display_name'"
                 :valueProp="'id'"
                 :mode="'tags'"
                 :searchable="true"
                 :options="state.roles"
                 :placeholder="t('main.user.add-role-placeholder')">
             </multiselect>
         </div>
    </div>
</template>

<script>
import {
    computed,
    reactive,
} from 'vue';

export default {
setup() {
    const state = reactive({
        userList: computed(_ => store.getters.users),
        roles: computed(_ => store.getters.roles),
    });
    return {
        state
    }
}
</script>

Missing features

Hello, is it possible to add these features?

  1. clear button to remove all values in v-model. It's not very user friendly to remove 200 tags manually
  2. close prop, so that I can close options list when I pick value from multiple/tags mode
  3. limit options list to given length

Right click trigger undesired actions

Version

  • Vue version: 3
  • Plugin version: 1.2.5

Description

Because the component listen to mousedown events, right clicks trigger actions just like a normal click.

For example if you right click on the button to delete an item — let’s say to inspect it — the item gets removed.

It would be better to listen to the click event to avoid this unexpected behavior.

html5 validation - missing required

We use html5 validation in our project and consider using the library, but we miss one feature the "required" attribute. Is there any way to get it?

Add input support for submitting forms in regular way

Hi guys,

I using Laravel and Vue and in some cases i have regular point to point form submission without ajax request.

When i place request to my controller, i can't see selected options in tag mode.

I found some work-around with @ change event to populate some hidden input, but it will be better if vue-multiselect package can handle this case.

my final work-around;
add this somewhere in vue component
<input v-for="id in v-model" type="hidden" name="variable_name[]" :value="id" />

Thanks.

Question: How can I give focus to the input field in an elegant way?

I need to programmatically focus the input field. Before, we used a normal <input> element and were able to simply this.$refs.input.focus(). This is not possible when using multiselect and I couldn't find anything in the docs or code that would allow it.

My current workaround is this:

const input = this.$refs.input.$el.getElementsByTagName("input")[0]
input && input.focus()

Is there are more elegant way of solving this? Thanks!

Unable to close dropdown after select

If :object="true" the dropdown doesn't close after selecting an option. This might be intended for mode="multiselect", but it happens also for single select.

@keydown.prevent.enter issue

Currently I set :add-tag-on="[]" and handle tagging carefully by testing every key typed with regex before addition.

Unfortunately I cannot implement features like on enter jump to next input, on enter do other stuff like add tag manually or display html5 validation message if something is wrong.

Could you provide option to turn @keydown.prevent.enter off so developers can handle this feature by themselves?

I think this won't take much time to implement and will make your component much more flexible.

V-slot:tag remove function not working in 1.3.2?

Version

  • Vue version: 3

Demo

See the simple demo below. It is using 1.3.0. Select a tag, then click it to remove it. It works. Now change the version to 1.3.2. Select a tag, then click to remove it. An error is thrown and the tag isn't removed.

https://jsfiddle.net/do30skf2/1/

Description

It seems like the { remove } option is no longer present or working in v-slot:tag? In my actual project (setup using Vue CLI), I'm getting the error runtime-core.esm-bundler.js?5c40:217 Uncaught TypeError: remove is not a function.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.