Skip to content

Commit

Permalink
Add Multiselect prop maxOptions: number (#243)
Browse files Browse the repository at this point in the history
* add Multiselect prop maxOptions: number | undefined = undefined

Positive integer to limit the number of options displayed in the dropdown. `undefined` means no limit.

* add maxOptions to countries example

* trigger GH pages deploy on PR to main

* fix build error : The following pages contain links to /#🔣-props, but no element with id="🔣-props" exists on /

* add test 'no more than maxOptions are rendered if a positive integer, all options are rendered undefined or 0'

* console.error on invalid maxOptions

* test maxOptions console.error
  • Loading branch information
janosh committed Jun 22, 2023
1 parent 06401e4 commit 355eee9
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: GitHub Pages
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
Expand Down
6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ Full list of props/bindable variables for this component. The `Option` type you

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`.

1. ```ts
maxOptions: number | undefined = undefined
```

Positive integer to limit the number of options displayed in the dropdown. `undefined` and 0 mean no limit.

1. ```ts
maxSelect: number | null = null
```
Expand Down
14 changes: 12 additions & 2 deletions src/lib/MultiSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
export let liSelectedClass: string = ``
export let loading: boolean = false
export let matchingOptions: Option[] = []
export let maxOptions: number | undefined = undefined
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 Down Expand Up @@ -135,6 +136,14 @@
`This prevents the "Add option" <span> from showing up, resulting in a confusing user experience.`
)
}
if (
maxOptions &&
(typeof maxOptions != `number` || maxOptions < 0 || maxOptions % 1 != 0)
) {
console.error(
`MultiSelect's maxOptions must be undefined or a positive integer, got ${maxOptions}`
)
}
const dispatch = createEventDispatcher<DispatchEvents<Option>>()
let option_msg_is_active: boolean = false // controls active state of <li>{createOptionMsg}</li>
Expand All @@ -147,6 +156,7 @@
// remove already selected options from dropdown list unless duplicate selections are allowed
(!selected.map(key).includes(key(opt)) || duplicates)
)
// raise if matchingOptions[activeIndex] does not yield a value
if (activeIndex !== null && !matchingOptions[activeIndex]) {
throw `Run time error, activeIndex=${activeIndex} is out of bounds, matchingOptions.length=${matchingOptions.length}`
Expand Down Expand Up @@ -590,7 +600,7 @@
{/if}
{/if}

<!-- only render options dropdown if options or searchText is not empty needed to avoid briefly flashing empty dropdown -->
<!-- only render options dropdown if options or searchText is not empty (needed to avoid briefly flashing empty dropdown) -->
{#if (searchText && noMatchingOptionsMsg) || options?.length > 0}
<ul
class:hidden={!open}
Expand All @@ -601,7 +611,7 @@
aria-disabled={disabled ? `true` : null}
bind:this={ul_options}
>
{#each matchingOptions as option, idx}
{#each matchingOptions.slice(0, Math.max(0, maxOptions ?? 0) || Infinity) as option, idx}
{@const {
label,
disabled = null,
Expand Down
16 changes: 16 additions & 0 deletions src/site/Examples.svx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@

let selected: string[]
// required={1} means form validation will prevent submission if no option selected
let maxOptions: number = 10
</script>

<MultiSelect
Expand All @@ -156,8 +157,23 @@
required={1}
minSelect={1}
maxSelect={1}
{maxOptions}
selected={[`Canada`]}
/>

<label>
maxOptions <input type="range" min=0 max={30} bind:value={maxOptions}>
{maxOptions} <small>(0 means no limit)</small>
</label>

<style>
label {
display: flex;
align-items: center;
gap: 5pt;
font-weight: normal;
}
</style>
```

<style>
Expand Down
6 changes: 6 additions & 0 deletions svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export default {
$root: `.`,
$site: `./src/site`,
},

prerender: {
handleMissingId: ({ id }) => {
if (![`🔣-props`].includes(id)) throw id
},
},
},

compilerOptions: {
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/MultiSelect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1117,3 +1117,42 @@ describe.each([[true], [false]])(`allowUserOptions=%s`, (allowUserOptions) => {
}
)
})

test.each([[0], [1], [2], [5], [undefined]])(
`no more than maxOptions are rendered if a positive integer, all options are rendered undefined or 0`,
async (maxOptions) => {
const options = [`foo`, `bar`, `baz`]

new MultiSelect({
target: document.body,
props: { options, maxOptions },
})

const input = doc_query<HTMLInputElement>(`input[autocomplete]`)
input.dispatchEvent(input_event)

await tick()

expect(document.querySelectorAll(`ul.options li`)).toHaveLength(
Math.min(options.length, maxOptions || options.length)
)
}
)

test.each([[true], [-1], [3.5], [`foo`], [{}]])(
`console.error when maxOptions=%s is not a positive integer or undefined`,
async (maxOptions) => {
console.error = vi.fn()

new MultiSelect({
target: document.body,
// @ts-expect-error test invalid maxOptions
props: { options: [1, 2, 3], maxOptions },
})

expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error).toHaveBeenCalledWith(
`MultiSelect's maxOptions must be undefined or a positive integer, got ${maxOptions}`
)
}
)

0 comments on commit 355eee9

Please sign in to comment.