Skip to content

rob-balfre/svelte-select

Repository files navigation

Svelte Select

Svelte Select

A select/autocomplete/typeahead Svelte component.

Demo

✨ REPL: Simple ✨

💃 REPL: Show me everything 🕺

Installation

npm install svelte-select@beta

Upgrading to v5

See migration guide if upgrading from v4 to v5.

Props

Prop Type Default Description
items any[] [] Array of items available to display / filter
value any null Selected value(s)
justValue any null READ-ONLY Selected value(s) excluding container object
optionIdentifier string value Override default identifier
labelIdentifier string label Override default identifier
id string null Add an id to the filter input field
filterText string '' Text to filter items by
placeholder string Please select Placeholder text
noOptionsMessage string No options Message displayed when no items
hideEmptyState boolean false When no items hide list and noOptionsMessage
listOpen boolean false Open/close list
class string '' container classes
containerStyles string '' Add inline styles to container
isClearable boolean true Enable clearing of value(s)
isCreatable boolean false Can create new item(s) to be added to value
isDisabled boolean false Disable select
isMulti boolean false Enable multi-select
isSearchable boolean true If false search/filtering is disabled
isGroupHeaderSelectable boolean false Enable selectable group headers
listPlacement string auto Display list 'auto', 'top' or 'bottom'
hasError boolean false Show error styles around select input
listAutoWidth boolean true If false will ignore width of select
showChevron boolean false Show chevron at all times
inputAttributes object {} Pass in HTML attributes to Select's input
iconProps object {} Icon props
filteredItems array [] List of items after filtering (read only)
placeholderAlwaysShow boolean false When isMulti placeholder text will always show
isWaiting boolean false Show LoadingIcon. loadOptions will override this
listOffset number 5 px space between select and list
debounceWait number 300 milliseconds debounce wait
suggestions string[] null Show search suggestions before user input
appendListTarget Element document.body Change where List gets appended

Replaceable components

Import Type Description
Item component Item component
Selection component Selection component
Multi component Multi select support
ChevronIcon component Chevron Icon
ClearIcon component Clear Icon
LoadingIcon component Spinning Loading Icon

Optional component imports

Import Type Description
VirtualList component Virtual list support (uses svelte-tiny-virtual-list)
Icon component Icon component

Events

Event Name Callback Description
select { detail } fires when item is selected
change { detail } fires when value changes
focus { detail } fires when select > input on:focus
blur { detail } fires when select > input on:blur
clear { detail } fires when clear all is invoked or item is removed (by user) from multi select
loaded { options } fires when loadOptions resolves
itemCreated { options } fires when isCreatable true and item is created
error { type, details } fires when error is caught

Items

items can be simple arrays or collections.

<script>
  import Select from 'svelte-select';

  let simple = ['one', 'two', 'three'];

  let collection = [
    { value: 1, label: 'one' },
    { value: 2, label: 'two' },
    { value: 3, label: 'three' },
  ];
</script>

<Select items={simple} />

<Select items={collection} />

They can also be grouped and include non-selectable items.

<script>
  import Select from 'svelte-select';

  const items = [
    {value: 'chocolate', label: 'Chocolate', group: 'Sweet'},
    {value: 'pizza', label: 'Pizza', group: 'Savory'},
    {value: 'cake', label: 'Cake', group: 'Sweet', selectable: false},
    {value: 'chips', label: 'Chips', group: 'Savory'},
    {value: 'ice-cream', label: 'Ice Cream', group: 'Sweet'}
  ];

  const groupBy = (item) => item.group;
</script>

<Select {items} {groupBy} />

You can also use custom collections.

<script>
  import Select from 'svelte-select';

  const optionIdentifier = 'id';
  const labelIdentifier = 'title';

  const items = [
    {id: 0, title: 'Foo'},
    {id: 1, title: 'Bar'},
  ];
</script>

<Select {optionIdentifier} {labelIdentifier} {items} />

Async Items

To load items asynchronously then loadOptions is the simplest solution. Supply a function that returns a Promise that resolves with a list of items. loadOptions has debounce baked in and fires each time filterText is updated.

<script>
  import Select from 'svelte-select';

  import { someApiCall } from './services';

  async function examplePromise(filterText) {
    // Put your async code here...
    // For example call an API using filterText as your search params
    // When your API responds resolve your Promise
    let res = await someApiCall(filterText);
    return res;
  }
</script>

<Select loadOptions={examplePromise} />

Change where List gets appended

By default List gets appended to the document.body. For most use-cases this is fine. If you want more control then supply a appendListTarget Element

<script>
  import Select from 'svelte-select';

  let target = null;
</script>

<form bind:this={target}>
  <Select appendListTarget={target} />
  <!-- List will now get appended to the DOM inside this form Element> -->
</form>

Exposed methods

These internal functions are exposed to override if needed. See the adv demo or look through the test file (test/src/index.js) for examples.

export let itemFilter = (label, filterText, option) => label.toLowerCase().includes(filterText.toLowerCase());
export let groupBy = undefined;
export let groupFilter = groups => groups;
export let createGroupHeaderItem = groupValue => {
  return {
    value: groupValue,
    label: groupValue
  };
};
export let createItem = filterText => {
  return {
    value: filterText,
    label: filterText
  };
};
export let getOptionLabel = (option, filterText) => {
  return option.isCreator ? `Create \"${filterText}\"` : option.label;
};
export let getSelectionLabel = option => {
  if (option) return option.label;
};
export let getGroupHeaderLabel = option => {
  return option.label;
};
export function handleClear() {
  value = undefined;
  listOpen = false;
  dispatch("clear", value);
  handleFocus();
}
export let loadOptions = undefined; // if used must return a Promise that updates 'items'
/* Return an object with { cancelled: true } to keep the loading state as active. */
export const getFilteredItems = () => {
  return filteredItems;
};
export function debounce(fn, wait = 1) {
    let timeout;
    return function (...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => fn.apply(this, ...args), wait);
    };
}
export const sanitiseLabel = (text) => text && `${text}`.replace(/\</gi, '&lt;');

Override core functionality at your own risk! See (get-items.js, filter.js, compute-placement.js)

    // core replaceable methods...
    <Select 
      filter={...}
      getItems={...}
      computePlacement={...}
    />

A11y (Accessibility)

Override these methods to change the aria-context and aria-selection text.

export let ariaValues = (values) => {
  return `Option ${values}, selected.`;
}

export let ariaListOpen = (label, count) => {
  return `You are currently focused on option ${label}. There are ${count} results available.`;
}

export let ariaFocused = () => {
  return `Select is focused, type to refine list, press down to open the menu.`;
}

CSS custom properties (variables)

You can style a component by overriding the available CSS custom properties.

<script>
  import Select from 'svelte-select';
</script>

<Select --border-radius= "10px" --placeholder-color="blue" />

You can also use the inputStyles prop to write in any override styles needed for the input.

<script>
  import Select from 'svelte-select';

  const items = ['One', 'Two', 'Three'];
</script>

<Select {items} inputStyles="box-sizing: border-box;"></Select>

🧪 Experimental: Replace styles (Tailwind, Bootstrap, Bulma etc)

If you'd like to supply your own styles use: import Select from 'svelte-select/no-styles/Select.svelte'. Then somewhere in your code or build pipeline add your own. There is a tailwind stylesheet via import 'svelte-select/tailwind.css'. It uses @extend so PostCSS is required.

Development

npm i
npm run dev-tests
npm test:browser

Open http://localhost:3000 and see devtools console output. When developing it's useful to see the component on the page; comment out the select.$destroy(); on test your debugging in /test/src/index.js and use test.only() to target just one test.

For example:

test.only('when getSelectionLabel contains HTML then render the HTML', async (t) => {
  const select = new Select({
    target,
    props: {
      value: items[0],
      getSelectionLabel: (option) => `<p>${option.label}</p>`,
    }
  });

  t.ok(document.querySelector('.selection').innerHTML === '<p>Chocolate</p>');

  //select.$destroy();
});

License

LIL