Socket
Socket
Sign inDemoInstall

svelte-multiselect

Package Overview
Dependencies
0
Maintainers
1
Versions
75
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    svelte-multiselect

Svelte multi-select component


Version published
Maintainers
1
Created

Changelog

Source

v8.0.3

15 November 2022

Readme

Source

Svelte MultiSelect
 Svelte MultiSelect

Tests Netlify Status NPM version Needs Svelte version REPL Open in StackBlitz

Keyboard-friendly, accessible and highly customizable multi-select component. View the docs

Key features

  • Bindable: bind:selected gives you an array of the currently selected options. Thanks to Svelte's 2-way binding, it can also control the component state externally through assignment selected = ['foo', 42].
  • Keyboard friendly for mouse-less form completion
  • No run-time deps: needs only Svelte as dev dependency
  • Dropdowns: scrollable lists for large numbers of options
  • Searchable: start typing to filter options
  • Tagging: selected options are listed as tags within the input
  • Single / multiple select: pass maxSelect={1, 2, 3, ...} prop to restrict the number of selectable options
  • Configurable: see props

Recent breaking changes

  • v6.0.0  The prop showOptions which controls whether the list of dropdown options is currently open or closed was renamed to open. PR 103.
  • v6.0.1  The prop disabledTitle which sets the title of the <MultiSelect> <input> node if in disabled mode was renamed to disabledInputTitle. PR 105.
  • v6.0.1  The default margin of 1em 0 on the wrapper div.multiselect was removed. Instead, there is now a new CSS variable --sms-margin. Set it to --sms-margin: 1em 0; to restore the old appearance. PR 105.
  • 6.1.0  The dispatch events focus and blur were renamed to open and close, respectively. These actions refer to the dropdown list, i.e. <MultiSelect on:open={(event) => console.log(event)}> will trigger when the dropdown list opens. The focus and blur events are now regular DOM (not Svelte dispatch) events emitted by the <input> node. PR 120.
  • v7.0.0  selected (as well selectedLabels and selectedValues) used to be arrays always. Now, if maxSelect=1, they will no longer be a length-1 array but simply a single a option (label/value respectively) or null if no option is selected. PR 123.
  • 8.0.0 
    • Props selectedLabels and selectedValues were removed. If you were using them, they were equivalent to assigning bind:selected to a local variable and then running selectedLabels = selected.map(option => option.label) and selectedValues = selected.map(option => option.value) if your options were objects with label and value keys. If they were simple strings/numbers, there was no point in using selected{Labels,Values} anyway. PR 138
    • Prop noOptionsMsg was renamed to noMatchingOptionsMsg. PR 133.

Installation

npm install -D svelte-multiselect
pnpm add -D svelte-multiselect
yarn add -D svelte-multiselect

Usage

<script>
  import MultiSelect from 'svelte-multiselect'

  const ui_libs = [`Svelte`, `React`, `Vue`, `Angular`, `...`]

  let selected = []
</script>

Favorite Frontend Frameworks?

<code>selected = {JSON.stringify(selected)}</code>

<MultiSelect bind:selected options={ui_libs} />

Props

Full list of props/bindable variables for this component. The Option type you see below is defined in src/lib/index.ts and can be imported as import { type Option } from 'svelte-multiselect'.

  1. activeIndex: number | null = null
    

    Zero-based index of currently active option in the array of currently matching options, i.e. if the user typed a search string into the input and only a subset of options match, this index refers to the array position of the matching subset of options

  2. activeOption: Option | null = null
    

    Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.

  3. addOptionMsg: string = `Create this option...`
    

    Message shown to users after entering text when no options match their query and allowUserOptions is truthy.

  4. allowUserOptions: boolean | 'append' = false
    

    Whether users can enter values that are not in the dropdown list. true means add user-defined options to the selected list only, 'append' means add to both options and selected. If allowUserOptions is true or 'append' then the type object | number | string of entered value is determined by typeof options[0] (i.e. the first option in the dropdown list) to keep type homogeneity.

  5. autocomplete: string = `off`
    

    Applied to the <input>. Specifies if browser is permitted to auto-fill this form field. Should usually be one of 'on' or 'off' but see MDN docs for other admissible values.

  6. autoScroll: boolean = true
    

    false disables keeping the active dropdown items in view when going up/down the list of options with arrow keys.

  7. breakpoint: number = 800
    

    Screens wider than breakpoint in pixels will be considered 'desktop', everything narrower as 'mobile'.

  8. defaultDisabledTitle: string = `This option is disabled`
    

    Title text to display when user hovers over a disabled option. Each option can override this through its disabledTitle attribute.

  9. disabled: boolean = false
    

    Disable the component. It will still be rendered but users won't be able to interact with it.

  10. disabledInputTitle: string = `This input is disabled`
    

    Tooltip text to display on hover when the component is in disabled state.

  1. duplicateFunc: (op1: Option, op2: Option) => boolean = (op1, op2) =>
     `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
    

    This option determines when two options are considered duplicates. Defaults to case-insensitive equality comparison after string coercion (looking only at the label key of object options). I.e. the default duplicateFunc considers 'Foo' == 'foo', '42' == 42 and { label: `Foo`, value: 0 } == { label: `foo`, value: 42 }.

  2. duplicates: boolean = false
    

    Whether to allow users to select duplicate options. Applies only to the selected item list, not the options dropdown. Keeping that free of duplicates is left to developer. The selected item list can have duplicates if allowUserOptions is truthy, duplicates is true and users create the same option multiple times. Use duplicateOptionMsg to customize the message shown to user if duplicates is false and users attempt this and duplicateFunc to customize when a pair of options is considered a duplicate.

  3. duplicateOptionMsg: string = `This option is already selected`
    

    Text to display to users when allowUserOptions is truthy and they try to create a new option that's already selected.

  4. filterFunc = (op: Option, searchText: string): boolean => {
      if (!searchText) return true
      return `${get_label(op)}`.toLowerCase().includes(searchText.toLowerCase())
    }
    

    Customize how dropdown options are filtered when user enters search string into <MultiSelect />. Defaults to:

  5. focusInputOnSelect: boolean | 'desktop' = `desktop`
    

    One of true, false or 'desktop'. Whether to set the cursor back to the input element after selecting an element. 'desktop' means only do so if current window width is larger than the current value of breakpoint prop (default 800).

  6. id: string | null = null
    

    Applied to the <input> element for associating HTML form <label>s with this component for accessibility. Also, clicking a <label> with same for attribute as id will focus this component.

  7. input: HTMLInputElement | null = null
    

    Handle to the <input> DOM node. Only available after component mounts (null before then).

  8. inputmode: string | null = null
    

    The inputmode attribute hints at the type of data the user may enter. Values like 'numeric' | 'tel' | 'email' allow browsers to display an appropriate virtual keyboard. See MDN for details.

  9. invalid: boolean = false
    

    If required=true and user tries to submit but selected = [] is empty, invalid is automatically set to true and CSS class invalid applied to the top-level div.multiselect. invalid class is removed again as soon as the user selects an option. invalid can also be controlled externally by binding to it <MultiSelect bind:invalid /> and setting it to true based on outside events or custom validation.

  10. loading: boolean = false
    

    Whether the component should display a spinner to indicate it's in loading state. Use <slot name='spinner'> to specify a custom spinner.

  11. matchingOptions: Option[] = []
    

    List of options currently displayed to the user. Same as options unless the user entered searchText in which case this array contains only those options for which filterFunc = (op: Option, searchText: string) => boolean returned true.

  12. maxSelect: number | null = null
    

    Positive integer to limit the number of options users can pick. null means no limit. maxSelect={1} will change the type of selected to be a single Option (or null) (not a length-1 array). Likewise, the type of selectedLabels changes from (string | number)[] to string | number | null and selectedValues from unknown[] to unknown | null. maxSelect={1} will also give div.multiselect a class of single. I.e. you can target the selector div.multiselect.single to give single selects a different appearance from multi selects.

  13. maxSelectMsg: ((current: number, max: number) => string) | null = (
      current: number,
      max: number
    ) => (max > 1 ? `${current}/${max}` : ``)
    

    Inform users how many of the maximum allowed options they have already selected. Set maxSelectMsg={null} to not show a message. Defaults to null when maxSelect={1} or maxSelect={null}. Else if maxSelect > 1, defaults to:

    maxSelectMsg = (current: number, max: number) => `${current}/${max}`
    
  14. name: string | null = null
    

    Applied to the <input> element. Sets the key of this field in a submitted form data object. See form example.

  15. noMatchingOptionsMsg: string = `No matching options`
    

    What message to show if no options match the user-entered search string.

  16. open: boolean = false
    

    Whether the dropdown list is currently visible. Is two-way bindable, i.e. can be used for external control of when the options are visible.

  17. options: Option[]
    

    The only required prop (no default). Array of strings/numbers or Option objects to be listed in the dropdown. The only required key on objects is label which must also be unique. An object's value defaults to label if undefined. You can add arbitrary additional keys to your option objects. A few keys like preselected and title have special meaning though. See type ObjectOption in src/lib/index.ts for all special keys and their purpose.

  18. outerDiv: HTMLDivElement | null = null
    

    Handle to outer <div class="multiselect"> that wraps the whole component. Only available after component mounts (null before then).

  19. parseLabelsAsHtml: boolean = false
    

    Whether option labels should be passed to Svelte's @html directive or inserted into the DOM as plain text. true will raise an error if allowUserOptions is also truthy as it makes your site susceptible to cross-site scripting (XSS) attacks.

  20. pattern: string | null = null
    

    The pattern attribute specifies a regular expression which the input's value must match. If a non-null value doesn't match the pattern regex, the read-only patternMismatch property will be true. See MDN for details.

  21. placeholder: string | null = null
    

    String shown in the text input when no option is selected.

  22. removeAllTitle: string = `Remove all`
    

    Title text to display when user hovers over remove-all button.

  23. removeBtnTitle: string = `Remove`
    

    Title text to display when user hovers over button to remove selected option (which defaults to a cross icon).

  24. required: boolean = false
    

    Whether forms can be submitted without selecting any options. Aborts submission, is scrolled into view and shows help "Please fill out" message when true and user tries to submit with no options selected.

  25. resetFilterOnAdd: boolean = true
    

    Whether text entered into the input to filter options in the dropdown list is reset to empty string when user selects an option.

  26. searchText: string = ``
    

    Text the user-entered to filter down on the list of options. Binds both ways, i.e. can also be used to set the input text.

  27. selected: Option[] =
     options
       ?.filter((op) => (op as ObjectOption)?.preselected)
       .slice(0, maxSelect ?? undefined) ?? []
    

    Array of currently selected options. Supports 2-way binding bind:selected={[1, 2, 3]} to control component state externally. Can be passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction.

  28. sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
    

    Default behavior is to render selected items in the order they were chosen. sortSelected={true} uses default JS array sorting. A compare function enables custom logic for sorting selected options. See the /sort-selected example.

  29. value: Option | Option[] | null = null
    

    If maxSelect={1}, value will be the single item in selected (or null if selected is empty). If maxSelect != 1, maxSelect and selected are equal. Warning: value supports 1-way binding only, meaning bind:value will update value when internal component state changes but changing value externally will not update internal component state. This is because value is already reactive to selected and making selected reactive to value would be cyclic. Suggestions for better solutions that solve both #86 and #136 welcome!

Slots

MultiSelect.svelte has 3 named slots:

  • slot="option": Customize rendering of dropdown options. Receives as props an option and the zero-indexed position (idx) it has in the dropdown.
  • slot="selected": Customize rendering of selected items. Receives as props an option and the zero-indexed position (idx) it has in the list of selected items.
  • slot="spinner": Custom spinner component to display when in loading state. Receives no props.
  • slot="disabled-icon": Custom icon to display inside the input when in disabled state. Receives no props. Use an empty <span slot="disabled-icon" /> or div to remove the default disabled icon.
  • slot="remove-icon": Custom icon to display as remove button. Will be used both by buttons to remove individual selected options and the 'remove all' button that clears all options at once. Receives no props.

Example:

<MultiSelect options={[`Red`, `Green`, `Blue`, `Yellow`, `Purple`]}>
  <span let:idx let:option slot="option">
    {idx + 1}
    {option.label}
    <span style:background={option.label} style=" width: 1em; height: 1em;" />
  </span>

  <span let:idx let:option slot="selected">
    {idx + 1}
    {option.label}
    <span style:background={option.label} style=" width: 1em; height: 1em;" />
  </span>

  <CustomSpinner slot="spinner">
  <strong slot="remove-icon">X</strong>
</MultiSelect>

Events

MultiSelect.svelte dispatches the following events:

  1. on:add={(event) => console.log(event.detail.option)}
    

    Triggers when a new option is selected.

  2. on:remove={(event) => console.log(event.detail.option)}`
    

    Triggers when one selected option provided as event.detail.option is removed.

  3. on:removeAll={(event) => console.log(event.detail.options)}`
    

    Triggers when all selected options are removed. The payload event.detail.options gives the options that were previously selected.

  4. on:change={(event) => console.log(`${event.detail.type}: '${event.detail.option}'`)}
    

    Triggers when an option is either added or removed, or all options are removed at once. type is one of 'add' | 'remove' | 'removeAll' and payload will be option: Option or options: Option[], respectively.

  5. on:open={(event) => console.log(`Multiselect dropdown was opened by ${event}`)}
    

    Triggers when the dropdown list of options appears. Event is the DOM's FocusEvent,KeyboardEvent or ClickEvent that initiated this Svelte dispatch event.

  6. on:close={(event) => console.log(`Multiselect dropdown was closed by ${event}`)}
    

    Triggers when the dropdown list of options disappears. Event is the DOM's FocusEvent, KeyboardEvent or ClickEvent that initiated this Svelte dispatch event.

For example, here's how you might annoy your users with an alert every time one or more options are added or removed:

<MultiSelect
  on:change={(e) => {
    if (e.detail.type === 'add') alert(`You added ${e.detail.option}`)
    if (e.detail.type === 'remove') alert(`You removed ${e.detail.option}`)
    if (e.detail.type === 'removeAll') alert(`You removed ${e.detail.options}`)
  }}
/>

Note: Depending on the data passed to the component the options(s) payload will either be objects or simple strings/numbers.

The above list of events are Svelte dispatch events. This component also forwards many DOM events from the <input> node: blur, change, click, keydown, keyup, mousedown, mouseenter, mouseleave, touchcancel, touchend, touchmove, touchstart. Registering listeners for these events works the same:

<MultiSelect
  options={[1, 2, 3]}
  on:keyup={(event) => console.log('key', event.target.value)}
/>

TypeScript

TypeScript users can import the types used for internal type safety:

<script lang="ts">
  import MultiSelect from 'svelte-multiselect'
  import type { Option, ObjectOption } from 'svelte-multiselect'

  const myOptions: ObjectOption[] = [
    { label: 'foo', value: 42 },
    { label: 'bar', value: 69 },
  ]
  // an Option can be string | number | ObjectOption
  const myNumbers: Option[] = [42, 69]
</script>

Styling

There are 3 ways to style this component. To understand which options do what, it helps to keep in mind this simplified DOM structure of the component:

<div class="multiselect">
  <ul class="selected">
    <li>Selected 1</li>
    <li>Selected 2</li>
  </ul>
  <ul class="options">
    <li>Option 1</li>
    <li>Option 2</li>
  </ul>
</div>

With CSS variables

If you only want to make small adjustments, you can pass the following CSS variables directly to the component as props or define them in a :global() CSS context. See app.css for how these variables are set on the demo site of this component.

  • div.multiselect
    • border: var(--sms-border, 1pt solid lightgray): Change this to e.g. to 1px solid red to indicate this form field is in an invalid state.
    • border-radius: var(--sms-border-radius, 3pt)
    • padding: var(--sms-padding, 0 3pt)
    • background: var(--sms-bg)
    • color: var(--sms-text-color)
    • min-height: var(--sms-min-height, 22pt)
    • width: var(--sms-width)
    • max-width: var(--sms-max-width)
    • margin: var(--sms-margin)
    • font-size: var(--sms-font-size, inherit)
  • div.multiselect.open
    • z-index: var(--sms-open-z-index, 4): Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
  • div.multiselect:focus-within
    • border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue)): Border when component has focus. Defaults to --sms-active-color if not set which defaults to cornflowerblue.
  • div.multiselect.disabled
    • background: var(--sms-disabled-bg, lightgray): Background when in disabled state.
  • div.multiselect input::placeholder
    • color: var(--sms-placeholder-color)
    • opacity: var(--sms-placeholder-opacity)
  • div.multiselect > ul.selected > li
    • background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15)): Background of selected options.
    • padding: var(--sms-selected-li-padding, 1pt 5pt): Height of selected options.
    • color: var(--sms-selected-text-color, var(--sms-text-color)): Text color for selected options.
  • ul.selected > li button:hover, button.remove-all:hover, button:focus
    • color: var(--sms-remove-btn-hover-color, lightskyblue): Color of the remove-icon buttons for removing all or individual selected options when in :focus or :hover state.
    • background: var(--sms-remove-btn-hover-bg, rgba(0, 0, 0, 0.2)): Background for hovered remove buttons.
  • div.multiselect > ul.options
    • background: var(--sms-options-bg, white): Background of dropdown list.
    • max-height: var(--sms-options-max-height, 50vh): Maximum height of options dropdown.
    • overscroll-behavior: var(--sms-options-overscroll, none): Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See MDN.
    • box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black): Box shadow of dropdown list.
  • div.multiselect > ul.options > li
    • scroll-margin: var(--sms-options-scroll-margin, 100px): Top/bottom margin to keep between dropdown list items and top/bottom screen edge when auto-scrolling list to keep items in view.
  • div.multiselect > ul.options > li.selected
    • background: var(--sms-li-selected-bg): Background of selected list items in options pane.
    • color: var(--sms-li-selected-color): Text color of selected list items in options pane.
  • div.multiselect > ul.options > li.active
    • background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15))): Background of active dropdown item. Items become active either by mouseover or by navigating to them with arrow keys.
  • div.multiselect > ul.options > li.disabled
    • background: var(--sms-li-disabled-bg, #f5f5f6): Background of disabled options in the dropdown list.
    • color: var(--sms-li-disabled-text, #b8b8b8): Text color of disabled option in the dropdown list.

For example, to change the background color of the options dropdown:

<MultiSelect --sms-options-bg="white" />

With CSS frameworks

The second method allows you to pass in custom classes to the important DOM elements of this component to target them with frameworks like Tailwind CSS.

  • outerDivClass: wrapper div enclosing the whole component
  • ulSelectedClass: list of selected options
  • liSelectedClass: selected list items
  • ulOptionsClass: available options listed in the dropdown when component is in open state
  • liOptionClass: list items selectable from dropdown list
  • liActiveOptionClass: the currently active dropdown list item (i.e. hovered or navigated to with arrow keys)

This simplified version of the DOM structure of the component shows where these classes are inserted:

<div class="multiselect {outerDivClass}">
  <input class={inputClass} />
  <ul class="selected {ulSelectedClass}">
    <li class={liSelectedClass}>Selected 1</li>
    <li class={liSelectedClass}>Selected 2</li>
  </ul>
  <ul class="options {ulOptionsClass}">
    <li class={liOptionClass}>Option 1</li>
    <li class="{liOptionClass} {liActiveOptionClass}">
      Option 2 (currently active)
    </li>
  </ul>
</div>

With global CSS

Odd as it may seem, you get the most fine-grained control over the styling of every part of this component by using the following :global() CSS selectors. ul.selected is the list of currently selected options rendered inside the component's input whereas ul.options is the list of available options that slides out when the component is in its open state. See also simplified DOM structure.

:global(div.multiselect) {
  /* top-level wrapper div */
}
:global(div.multiselect.open) {
  /* top-level wrapper div when dropdown open */
}
:global(div.multiselect.disabled) {
  /* top-level wrapper div when in disabled state */
}
:global(div.multiselect > ul.selected) {
  /* selected list */
}
:global(div.multiselect > ul.selected > li) {
  /* selected list items */
}
:global(div.multiselect button) {
  /* target all buttons in this component */
}
:global(div.multiselect > ul.selected > li button, button.remove-all) {
  /* buttons to remove a single or all selected options at once */
}
:global(div.multiselect > ul.selected > li > input) {
  /* input inside the top-level wrapper div */
}
:global(div.multiselect > ul.options) {
  /* dropdown options */
}
:global(div.multiselect > ul.options > li) {
  /* dropdown list items */
}
:global(div.multiselect > ul.options > li.selected) {
  /* selected options in the dropdown list */
}
:global(div.multiselect > ul.options > li:not(.selected):hover) {
  /* unselected but hovered options in the dropdown list */
}
:global(div.multiselect > ul.options > li.active) {
  /* active means item was navigated to with up/down arrow keys */
  /* ready to be selected by pressing enter */
}
:global(div.multiselect > ul.options > li.disabled) {
  /* options with disabled key set to true (see props above) */
}

Want to contribute?

To submit a PR, clone the repo, install dependencies and start the dev server to try out your changes.

git clone https://github.com/janosh/svelte-multiselect
cd svelte-multiselect
pnpm install
pnpm dev

Keywords

FAQs

Last updated on 15 Nov 2022

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc