Skip to content

Commit

Permalink
Implement allowUserOptions, autoScroll and loading (closes #39) (
Browse files Browse the repository at this point in the history
…#41)

* add prop autoScroll = true to disable keeping dropdown items in view when going up/down list with arrow keys

* add new prop allowUserOptions = false

* add new prop loading = false which displays CircleSpinner if true (closes #39)
  • Loading branch information
janosh authored Feb 21, 2022
1 parent adfdc23 commit 39516a1
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 51 deletions.
58 changes: 35 additions & 23 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

## Recent breaking changes

- v2.0.0 added the ability to pass options as objects. As a result, `bind:selected` no longer returns simple strings but objects, even if you still pass in `options` as strings. To get the same stuff you would have gotten from `bind:selected` before, there's now `bind:selectedLabels` (and `bind:selectedValues`).
- v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.

```js
Expand All @@ -45,6 +44,10 @@

It also added a separate event type `removeAll` for when the user removes all currently selected options at once which previously fired a normal `remove`. The props `ulTokensClass` and `liTokenClass` were renamed to `ulSelectedClass` and `liSelectedClass`. Similarly, the CSS variable `--sms-token-bg` changed to `--sms-selected-bg`.

- v4.0.0 renamed the slots for customizing how selected options and dropdown list items are rendered:
- old: `<slot name="renderOptions" />`, new: `<slot name="option" />`
- old: `<slot name="renderSelected" />`, new: `<slot name="selected" />`

## Installation

```sh
Expand Down Expand Up @@ -88,22 +91,25 @@ Full list of props/bindable variables for this component:
<div class="table">

<!-- prettier-ignore -->
| name | default | description |
| :--------------- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `options` | required prop | Array of strings/numbers or `Option` objects that will be listed in the dropdown. See `src/lib/index.ts` for admissible fields. The `label` is the only mandatory one. It must also be unique. |
| `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/pre-selected options when binding/passing as props respectively. |
| `selectedLabels` | `[]` | Labels of currently selected options. |
| `selectedValues` | `[]` | Values of currently selected options. |
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
| `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
| `placeholder` | `undefined` | String shown in the text input when no option is selected. |
| `input` | `undefined` | Handle to the `<input>` DOM node. |
| `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>`. |
| name | default | description |
| :----------------- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `options` | required prop | Array of strings/numbers or `Option` objects that will be listed in the dropdown. See `src/lib/index.ts` for admissible fields. The `label` is the only mandatory one. It must also be unique. |
| `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/pre-selected options when binding/passing as props respectively. |
| `selectedLabels` | `[]` | Labels of currently selected options. |
| `selectedValues` | `[]` | Values of currently selected options. |
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
| `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
| `placeholder` | `undefined` | String shown in the text input when no option is selected. |
| `input` | `undefined` | Handle to the `<input>` DOM node. |
| `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>`. |
| `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. |
| `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. |

</div>

Expand All @@ -126,24 +132,27 @@ Full list of props/bindable variables for this component:

## Slots

`MultiSelect.svelte` accepts two named slots
`MultiSelect.svelte` has 3 named slots:

- `slot="renderOptions"`
- `slot="renderSelected"`
- `slot="option"`: Customize rendering of dropdown options. Receives as props the `option` object and the zero-indexed position (`idx`) it has in the dropdown.
- `slot="selected"`: Customize rendering selected tags. Receives as props the `option` object 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.

to customize rendering individual options in the dropdown and the list of selected tags, respectively. Each renderer receives the full `option` object along with the zero-indexed position (`idx`) in its list, both available via the `let:` directive:
Example:

```svelte
<MultiSelect options={[`Banana`, `Apple`, `Mango`]}>
<span let:idx let:option slot="renderOptions">
<span let:idx let:option slot="option">
{idx + 1}. {option.label}
{option.label === `Mango` ? `🎉` : ``}
</span>
<span let:idx let:option slot="renderSelected">
<span let:idx let:option slot="selected">
#{idx + 1}
{option.label}
</span>
<CustomSpinner slot="spinner">
</MultiSelect>
```

Expand Down Expand Up @@ -233,8 +242,11 @@ If you only want to make small adjustments, you can pass the following CSS varia
- `color: var(--sms-remove-x-hover-focus-color, lightskyblue)`: Color of the cross-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](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior).
- `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`
- `border-left: var(--sms-li-selected-border-left, 3pt solid var(--sms-selected-color, green))`
- `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
Expand Down
14 changes: 12 additions & 2 deletions src/components/Example.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
)
return label.includes(searchStr) || lang.includes(searchStr)
}
let loading = false
let searchText = ``
$: if (searchText) {
loading = true
setTimeout(() => (loading = false), 1000)
}
</script>

<section>
Expand Down Expand Up @@ -53,7 +60,9 @@
maxSelectMsg={(current, max) => `${current} of ${max} selected`}
options={mlFrameworks}
bind:selected={selectedML}
bind:searchText
{placeholder}
{loading}
/>
</section>

Expand Down Expand Up @@ -88,12 +97,13 @@
id="fruit-select"
options={[`Banana`, `Watermelon`, `Apple`, `Dates`, `Mango`]}
{placeholder}
allowUserOptions="append"
>
<span let:idx let:option slot="renderOptions">
<span let:idx let:option slot="option">
{idx + 1}. {option.label}
{option.label === `Mango` ? `🎉` : ``}
</span>
<span let:idx let:option slot="renderSelected">
<span let:idx let:option slot="selected">
#{idx + 1}
{option.label}
</span>
Expand Down
30 changes: 30 additions & 0 deletions src/lib/CircleSpinner.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script lang="ts">
export let color = `cornflowerblue`
export let duration = `1.5s`
export let size = `1em`
</script>

<div
style="--duration: {duration}"
style:border-color="{color} transparent {color}
{color}"
style:width={size}
style:height={size}
/>

<style>
div {
display: inline-block;
vertical-align: middle;
margin: 0 3pt;
border-width: calc(1em / 5);
border-style: solid;
border-radius: 50%;
animation: var(--duration) infinite rotate;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
</style>
Loading

0 comments on commit 39516a1

Please sign in to comment.