All Projects → janosh → svelte-multiselect

janosh / svelte-multiselect

Licence: MIT license
Keyboard-friendly, accessible and highly customizable multi-select component

Programming Languages

Svelte
593 projects
typescript
32286 projects
CSS
56736 projects
HTML
75241 projects
javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to svelte-multiselect

svelte-search
Accessible, customizable Svelte search component
Stars: ✭ 17 (-81.32%)
Mutual labels:  input, svelte, svelte-component
Customui
Library to create custom UI's in MCPE 1.2+
Stars: ✭ 60 (-34.07%)
Mutual labels:  input, forms
Awesomevalidation
Android validation library which helps developer boil down the tedious work to three easy steps.
Stars: ✭ 1,093 (+1101.1%)
Mutual labels:  input, forms
Vue Formulate
⚡️ The easiest way to build forms with Vue.
Stars: ✭ 1,947 (+2039.56%)
Mutual labels:  input, forms
hookahjs
Add empty/dirty/touched CSS hooks to input and textarea elements automatically (1056 bytes)
Stars: ✭ 21 (-76.92%)
Mutual labels:  input, forms
Form Js
Easily create web forms. Supports Meteor, AngularJS, React, Polymer and any CSS library, e.g. Bootstrap.
Stars: ✭ 9 (-90.11%)
Mutual labels:  input, forms
Input Value
React hook for creating input values
Stars: ✭ 104 (+14.29%)
Mutual labels:  input, forms
svelte-intersection-observer
Detect if an element is in the viewport using the Intersection Observer API
Stars: ✭ 151 (+65.93%)
Mutual labels:  svelte, svelte-component
Validation
Framework agnostic validation library for PHP
Stars: ✭ 146 (+60.44%)
Mutual labels:  input, forms
insect
🛠 Highly customisable, minimalistic input x select field for React.
Stars: ✭ 33 (-63.74%)
Mutual labels:  input, forms
svelte-lottie-player
Lottie Player component for Svelte
Stars: ✭ 90 (-1.1%)
Mutual labels:  svelte, svelte-component
Svelte Forms Lib
📝. A lightweight library for managing forms in Svelte
Stars: ✭ 238 (+161.54%)
Mutual labels:  forms, svelte
svelte-inview
A Svelte action that monitors an element enters or leaves the viewport.🔥
Stars: ✭ 358 (+293.41%)
Mutual labels:  svelte, svelte-component
Svelte Input Mask
Input masking component for Svelte with simple API and rich customization
Stars: ✭ 56 (-38.46%)
Mutual labels:  input, svelte
Sveltik
Powerful forms in Svelte, inspired by Formik.
Stars: ✭ 128 (+40.66%)
Mutual labels:  forms, svelte
Autosize Input
🎈 Effortless, dynamic-width text boxes in vanilla JavaScript
Stars: ✭ 64 (-29.67%)
Mutual labels:  input, forms
svelte-undoable
Memento design pattern in Svelte
Stars: ✭ 39 (-57.14%)
Mutual labels:  svelte, svelte-component
sveld
Generate TypeScript definitions for your Svelte components
Stars: ✭ 281 (+208.79%)
Mutual labels:  svelte, svelte-component
Svelte Tags Input
Svelte tags input is a component to use with Svelte and easily enter tags and customize some options
Stars: ✭ 123 (+35.16%)
Mutual labels:  input, svelte
svelte-multistep-form
Svelte MultiStep Form like, this component is still in beta stage
Stars: ✭ 29 (-68.13%)
Mutual labels:  forms, svelte

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

  • v5.0.0 Supports both simple and object options. Previously strings and numbers were converted to { value, label } objects internally and returned by bind:selected. Now, if you pass in string[], that's exactly what you'll get from bind:selected.

Installation

npm install -D svelte-multiselect
pnpm install -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:

name default description
options required prop 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. MultiSelect A few keys like preselected and title have special meaning though. See src/lib/index.ts for all special keys and their purpose.
showOptions false Bindable boolean that controls whether the options dropdown is visible.
searchText `` 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.
activeOption null Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
maxSelect null Positive integer to limit the number of options users can pick. null means no limit.
selected [] Array of currently selected options. Can be bound to bind:selected={[1, 2, 3]} to control component state externally or passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction.
selectedLabels [] Labels of currently selected options. Exposed just for convenience, equivalent to selected.map(op => op.label) when options are objects. If options are simple strings, selected === selectedLabels. Supports binding but is read-only, i.e. since this value is reactive to selected, you cannot control selected by changing bind:selectedLabels.
selectedValues [] Values of currently selected options. Exposed just for convenience, equivalent to selected.map(op => op.value) when options are objects. If options are simple strings, selected === selectedValues. Supports binding but is read-only, i.e. since this value is reactive to selected, you cannot control selected by changing bind:selectedValues.
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 (see exposed methods below for details on filterFunc).
sortSelected boolean | ((op1, op2) => number) 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.
noOptionsMsg 'No matching options' What message to show if no options match the user-entered search string.
disabled false Disable the component. It will still be rendered but users won't be able to interact with it.
disabledTitle 'This field is disabled' Tooltip text to display on hover when the component is in disabled state.
placeholder undefined String shown in the text input when no option is selected.
input null Handle to the <input> DOM node. Only available after component mounts (null before then).
outerDiv null Handle to outer <div class="multiselect"> that wraps the whole component. Only available after component mounts (null before then).
id undefined 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.
name id Applied to the <input> element. If not provided, will be set to the value of id. Sets the key of this field in a submitted form data object. Not useful at the moment since the value is stored in Svelte state, not on the <input>.
required 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.
autoScroll true false disables keeping the active dropdown items in view when going up/down the list of options with arrow keys.
allowUserOptions false Whether users are allowed to enter values not in the dropdown list. true means add user-defined options to the selected list only, 'append' means add to both options and selected.
parseLabelsAsHtml 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.
addOptionMsg 'Create this option...' Message shown to users after entering text when no options match their query and allowUserOptions is truthy.
loading false Whether the component should display a spinner to indicate it's in loading state. Use <slot name='spinner'> to specify a custom spinner.
removeBtnTitle 'Remove' Title text to display when user hovers over button to remove selected option (which defaults to a cross icon).
removeAllTitle 'Remove all' Title text to display when user hovers over remove-all button.
defaultDisabledTitle 'This option is disabled' Title text to display when user hovers over a disabled option. Each option can override this through its disabledTitle attribute.
autocomplete 'off' Applied to the <input>. Specifies if browser is permitted to auto-fill this form field. See MDN docs for other admissible values.
invalid 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.

Exposed methods

  1. filterFunc = (op: Option, searchText: string) => boolean: Customize how dropdown options are filtered when user enters search string into <MultiSelect />. Defaults to:

    import type { Option } from 'svelte-multiselect'
    
    filterFunc = (op: Option, searchText: string) => {
      if (!searchText) return true
      return `${op.label}`.toLowerCase().includes(searchText.toLowerCase())
    }
  2. maxSelectMsg = (current: number, max: number) => string: 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}`

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:

name detail description
add { option: Option } Triggers when a new option is selected.
remove { option: Option } Triggers when one selected option provided as event.detail.option is removed.
removeAll options: Option[] Triggers when all selected options are removed. The payload event.detail.options gives the options that were previously selected.
change type: 'add' | 'remove' | 'removeAll' Triggers when a option is either added or removed, or all options are removed at once. Payload will be a single or an array of Options, respectively.
blur none Triggers when the input field looses focus.

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

Examples

  • on:add={(event) => console.log(event.detail.option)}
  • on:remove={(event) => console.log(event.detail.option)}.
  • on:change={(event) => console.log(`${event.detail.type}: '${event.detail.option}'`)}
  • on:blur={myFunction}
<MultiSelect
  on:change={(e) => alert(`You ${e.detail.type}ed '${e.detail.option}'`)}
/>

TypeScript

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

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

  const myOptions: Option[] = [
    { label: 'foo', value: 42 },
    { label: 'bar', value: 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.

  • 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)
    • max-width: var(--sms-max-width)
  • 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)
    • color: 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, 5pt 1pt): 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-button-hover-color, lightskyblue): Color of the remove-icon buttons for removing all or individual selected options when in :focus or :hover state.
  • 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
  • ulSelectedClass
  • liSelectedClass
  • ulOptionsClass
  • liOptionClass
  • liActiveOptionClass

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>

Granular control through global CSS

You can alternatively style every part of this component with more fine-grained control 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 has focus.

: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) */
}

Downstream testing

To test a Svelte component which imports svelte-multiselect, you need to configure your test runner to avoid transpiling issues.

For Jest, exclude svelte-multiselect from transformIgnorePatterns in your jest.config.json:

{
  "transformIgnorePatterns": ["node_modules/?!(svelte-multiselect)"],
  "transform": {
    "^.+\\.[t|j]s?$": "esbuild-jest",
    "^.+\\.svelte$": ["svelte-jester", { "preprocess": true }]
  }
}

For Vitest, include svelte-multiselect in deps.inline:

// vite.config.ts
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default {
  plugins: [svelte({ hot: !process.env.VITEST })],
  test: {
    deps: {
      inline: [/svelte-multiselect/],
    },
  },
}

Here's a Stackblitz example that also uses vitest-svelte-kit.

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
yarn
yarn dev
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].