Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 2 additions & 15 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,21 +441,8 @@ The above list of events are [Svelte `dispatch` events](https://svelte.dev/tutor

## 🦺   TypeScript

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

```svelte
Copy link
Contributor Author

@joelmukuthu joelmukuthu Dec 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't put an example here because demonstrating type-safety would probably require a screenshot. These can be used if needed:
Screenshot from 2022-12-29 10-51-08
Screenshot from 2022-12-29 10-51-16
Screenshot from 2022-12-29 10-51-23

Note that the import line doesn't say import MultiSelect from 'svelte-multiselect'; since this was a test I did locally.

<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>
```
The type of the `options` prop will be automatically inferred and type-safety
will be enforced for all related props (e.g. `selected`).

## ✨ &nbsp; Styling

Expand Down
22 changes: 12 additions & 10 deletions src/lib/MultiSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons'
import Wiggle from './Wiggle.svelte'

type InferredOption = $$Generic<Option>;

export let activeIndex: number | null = null
export let activeOption: Option | null = null
export let activeOption: InferredOption | null = null
export let addOptionMsg: string = `Create this option...`
export let allowUserOptions: boolean | 'append' = false
export let autocomplete: string = `off`
Expand All @@ -17,11 +19,11 @@
export let disabled: boolean = false
export let disabledInputTitle: string = `This input is disabled`
// case-insensitive equality comparison after string coercion (looking only at the `label` key of object options)
export let duplicateFunc: (op1: Option, op2: Option) => boolean = (op1, op2) =>
export let duplicateFunc: (op1: InferredOption, op2: InferredOption) => boolean = (op1, op2) =>
`${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
export let duplicateOptionMsg: string = `This option is already selected`
export let duplicates: boolean = false // whether to allow duplicate options
export let filterFunc = (op: Option, searchText: string): boolean => {
export let filterFunc = (op: InferredOption, searchText: string): boolean => {
if (!searchText) return true
return `${get_label(op)}`.toLowerCase().includes(searchText.toLowerCase())
}
Expand All @@ -36,7 +38,7 @@
export let liOptionClass: string = ``
export let liSelectedClass: string = ``
export let loading: boolean = false
export let matchingOptions: Option[] = []
export let matchingOptions: InferredOption[] = []
export let maxSelect: number | null = null // null means there is no upper limit for selected.length
export let maxSelectMsg: ((current: number, max: number) => string) | null = (
current: number,
Expand All @@ -46,7 +48,7 @@
export let name: string | null = null
export let noMatchingOptionsMsg: string = `No matching options`
export let open: boolean = false
export let options: Option[]
export let options: InferredOption[]
export let outerDiv: HTMLDivElement | null = null
export let outerDivClass: string = ``
export let parseLabelsAsHtml: boolean = false // should not be combined with allowUserOptions!
Expand All @@ -58,18 +60,18 @@
export let required: boolean | number = false
export let resetFilterOnAdd: boolean = true
export let searchText: string = ``
export let selected: Option[] =
export let selected: InferredOption[] =
options
?.filter((op) => (op as ObjectOption)?.preselected)
.slice(0, maxSelect ?? undefined) ?? []
export let sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
export let sortSelected: boolean | ((op1: InferredOption, op2: InferredOption) => number) = false
export let selectedOptionsDraggable: boolean = !sortSelected
export let ulOptionsClass: string = ``
export let ulSelectedClass: string = ``
export let value: Option | Option[] | null = null
export let value: InferredOption | InferredOption[] | null = null

// get the label key from an option object or the option itself if it's a string or number
const get_label = (op: Option) => (op instanceof Object ? op.label : op)
const get_label = (op: InferredOption) => (op instanceof Object ? op.label : op)

// if maxSelect=1, value is the single item in selected (or null if selected is empty)
// this solves both https://github.com/janosh/svelte-multiselect/issues/86 and
Expand Down Expand Up @@ -182,7 +184,7 @@
} else {
selected = [...selected, option]
if (sortSelected === true) {
selected = selected.sort((op1: Option, op2: Option) => {
selected = selected.sort((op1: InferredOption, op2: InferredOption) => {
const [label1, label2] = [get_label(op1), get_label(op2)]
// coerce to string if labels are numbers
return `${label1}`.localeCompare(`${label2}`)
Expand Down