@deboxsoft/svelte-select
v2.0.1
Published
A select/autocomplete component for Svelte apps. With support for grouping, filtering, async and more.
Downloads
216
Readme
svelte-select
A select/autocomplete component for Svelte apps. With support for grouping, filtering, async and more.
Installation
pnpm add @deboxsoft/svelte-select
Rollup and low/no-build setups
List position and floating is powered by floating-ui
, see their package-entry-points docs if you encounter build errors.
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 |
| itemId | string
| value
| Override default identifier |
| label | string
| label
| Override default label |
| id | string
| null
| id attr for input field |
| filterText | string
| ''
| Text to filter items
by |
| placeholder | string
| Please select
| Placeholder text |
| hideEmptyState | boolean
| false
| When no items hide list |
| listOpen | boolean
| false
| Open/close list |
| class | string
| ''
| container classes |
| containerStyles | string
| ''
| Add inline styles to container |
| clearable | boolean
| true
| Enable clearing of value(s) |
| disabled | boolean
| false
| Disable select |
| multiple | boolean
| false
| Enable multi-select |
| searchable | boolean
| true
| If false
search/filtering is disabled |
| groupHeaderSelectable | boolean
| false
| Enable selectable group headers |
| focused | boolean
| false
| Controls input focus |
| listAutoWidth | boolean
| true
| If false
will ignore width of select |
| showChevron | boolean
| false
| Show chevron |
| inputAttributes | object
| {}
| Pass in HTML attributes to Select's input |
| placeholderAlwaysShow | boolean
| false
| When multiple
placeholder text will always show |
| loading | boolean
| false
| Shows loading-icon
. loadOptions
will override this |
| listOffset | number
| 5
| px
space between select and list |
| debounceWait | number
| 300
| milliseconds
debounce wait |
| floatingConfig | object
| {}
| Floating UI Config |
Named slots
<Select>
<div slot="prepend" />
<div slot="selection" let:selection />
<div slot="clear-icon" />
<div slot="multi-clear-icon" />
<div slot="loading-icon" />
<div slot="chevron-icon" />
<div slot="list" let:filteredItems />
<div slot="item" let:item let:index />
<!-- Remember you can also use `svelte:fragment` to avoid a container DOM element. -->
<svelte:fragment slot="empty" />
</Select>
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 |
| 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 itemId = 'id';
const label = 'title';
const items = [
{id: 0, title: 'Foo'},
{id: 1, title: 'Bar'},
];
</script>
<Select {itemId} {label} {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} />
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;
};
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.`;
}
Styling
You can style a component by overriding the available CSS variables.
<script>
import Select from 'svelte-select';
const items = ['One', 'Two', 'Three'];
</script>
<style>
.themed {
--border: 3px solid blue;
--borderRadius: 10px;
--placeholderColor: blue;
}
</style>
<div class="themed">
<h2>Theming</h2>
<Select {items}></Select>
</div>
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>
Events
| Event Name | Callback | Description |
|------------|-------------------|--------------------------------------------------------------------------------|
| select | { detail } | fires when value changes |
| clear | { detail } | fires when clear all is invoked or item is removed (by user) from multi select |
| loaded | { items } | fires when loadOptions
resolves |
| error | { type, details } | fires when error is caught |
<script>
import Select from 'svelte-select';
let items = [...];
function handleSelect(event) {
// event.detail will contain the selected value
...
}
function handleClear(event) {
// event.detail will be null unless isMulti is true and user has removed a single item
...
}
</script>
<Select {items} on:select={handleSelect} on:clear={handleClear}></Select>
Development
yarn global add serve@8
yarn
yarn dev
yarn test:browser
In your favourite browser go to http://localhost:3000 and open devtools and see the console for the test output. When developing its handy to see the component on the page; comment out the select.$destroy();
on the last test in /test/src/index.js or use the 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();
});