Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What should bind:selected be when maxSelect={1}? #86

Closed
janosh opened this issue Jun 24, 2022 · 7 comments · Fixed by #123
Closed

What should bind:selected be when maxSelect={1}? #86

janosh opened this issue Jun 24, 2022 · 7 comments · Fixed by #123
Labels
discussion Gathering feedback on open questions dx Developer experience enhancement New feature or request

Comments

@janosh
Copy link
Owner

janosh commented Jun 24, 2022

Length-1 array or the value? Currently gives a length-1 array.

This issue arose out of private discord communication with fnAki dated 21/06/2022. Copied here so I don't forget about it.

I decided to throw previous packages and use svelte-multiselect for my dashboard. Love it so far (the only multi-select package that ACTUALLY allows you to change its style). Few things however,

  1. the single select acts as a multi select unfortunately, so I can't really bind it directly to my settings store and would need to do more work and listen to the change event (basically make my own component on top of MultiSelect)
    A: I think i know what you mean. bind:selected gives an array even if maxSelect={1}, is that it? i've found that annoying myself a few times. although it can also make it easier to know that bind:selected will always be an array. needs less safety checks and can avoid indexing errors. happy to take a PR to change it though if you think it not being an array is generally better

  2. when you have required=true in a single select, the remove icon for the single item selected should not appear for the item selected, so that there can always be one required value
    A: Sometimes I find it annoying not to be able to empty a select input. But i see your point. We should maybe add a prop allowRemoveLast or sth so devs can control this behavior.

  3. I'm not sure what the purpose of the loading state is? I don't think there's a way I can perhaps run an async function when the client tries to search?
    A: That's correct, it's purely a UI thing to display to the user something is currently happening in the background. you can do whatever you want with that, e.g.

let loading = false
function search_handler() {
  loading = true // will cause MultiSelect to show a loading spinner to the user
  // do some work like fetching options to display to the user from a server
  loading = false // reset to normal state when finished
}

<MultiSelect on:enter={search_handler} bind:loading

2 todos from this:

  1. Add allowRemoveLast prop
  2. Decide whether bind:selected should return a single value (not as a length-1 array) when maxSelect={1}.
@janosh janosh added enhancement New feature or request dx Developer experience discussion Gathering feedback on open questions labels Jun 24, 2022
@janosh janosh changed the title What should bind:selected give when maxSelect={1}? Length-1 array or the value? What should bind:selected be when maxSelect={1}? Jun 25, 2022
@wd-David
Copy link
Contributor

wd-David commented Jul 19, 2022

+1 for this.

I need to handle single select like this at this moment:

import type { DispatchEvents } from 'svelte-multiselect'

const updateSelected = ( event: CustomEvent<DispatchEvents['change']> ): string => {
  const { type, option } = event.detail
  if (type === 'add') return option as string
  else if (type === 'remove') return ''
}
<MultiSelect
  required
  maxSelect={1}
  options={myOptionArray}
  on:change={(event) => (myBindSelected = updateSelected(event))}
/>

@janosh
Copy link
Owner Author

janosh commented Jul 19, 2022

@davipon Good to know there's interest.

For now, wouldn't this be a simpler workaround for your use case?

<script>
  import MultiSelect from 'svelte-multiselect'

  const myOptionArray = [`...`]

  let selected = []
	
  $: value = selected[0] ?? null
</script>

<pre><code>value = {value}</code></pre>

<MultiSelect options={myOptionArray} bind:selected maxSelect={1} />

@wd-David
Copy link
Contributor

@davipon Good to know there's interest.

For now, wouldn't this be a simpler workaround for your use case?

<script>
  import MultiSelect from 'svelte-multiselect'

  const myOptionArray = [`...`]

  let selected = []
	
  $: value = selected[0] ?? null
</script>

<pre><code>value = {value}</code></pre>

<MultiSelect options={myOptionArray} bind:selected maxSelect={1} />

Yes, this was my first attempt, but I changed to use my previous approach.

The reason is that I have a form that has multiple single-select and multi-select:

interface myForm {
  singleSelect: string
  multiSelect: string[]
}

We can't get selected values by submitting the form so far. I need to create an object like myForm to store them whenever there is a change.

<MultiSelect
  name="singleSelect"
  required
  maxSelect={1}
  options={myOptionArray1}
  on:change={(event) => (myForm.singleSelect = updateSelected(event))}
/>
<MultiSelect
  name="multiSelect"
  required
  options={myOptionArray2}
  bind:selected={myForm.multiSelect}
/>

@janosh
Copy link
Owner Author

janosh commented Jul 20, 2022

How about binding the selected values into a store?

import { writable } from 'svelte/store'

const formData = writable({})

and then in your form component

<script>
  import MultiSelect from 'svelte-multiselect'
  import { formData } from '../stores'

  const myOptionArray = [`...`]

  let selected = []

  const fieldName = `mySingleSelect`
	
  $: $formData[fieldName] = selected[0] ?? null
</script>

<MultiSelect options={myOptionArray} bind:selected maxSelect={1} />

@sebiko3
Copy link

sebiko3 commented Aug 27, 2022

How about binding the selected values into a store?

import { writable } from 'svelte/store'

const formData = writable({})

and then in your form component

<script>
  import MultiSelect from 'svelte-multiselect'
  import { formData } from '../stores'

  const myOptionArray = [`...`]

  let selected = []

  const fieldName = `mySingleSelect`
	
  $: $formData[fieldName] = selected[0] ?? null
</script>

<MultiSelect options={myOptionArray} bind:selected maxSelect={1} />

And how would that work if I want to set values from a store?

$: selected = $formData.fieldName

I am currently trying to get multiple select components running on the same page but that doesn't seem to work.

@janosh
Copy link
Owner Author

janosh commented Sep 2, 2022

@magicbyt3 If you want to set values from a store, it's best to bind directly to the store. In that case, the store value needs to be an array. There's currently no way around that if you want to keep the ability to update the component via the store.

See https://github.com/janosh/awesome-sveltekit/blob/47bad49c72/site/src/components/Filters.svelte#L26 for a real-world example.

@sebiko3
Copy link

sebiko3 commented Sep 4, 2022

@magicbyt3 If you want to set values from a store, it's best to bind directly to the store. In that case, the store value needs to be an array. There's currently no way around that if you want to keep the ability to update the component via the store.

See https://github.com/janosh/awesome-sveltekit/blob/47bad49c72/site/src/components/Filters.svelte#L26 for a real-world example.

Thanks for that. For my purpose it was better to create an own multi select component. Not ready to publish yet but works so far. Your implementation inspired me on the way. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Gathering feedback on open questions dx Developer experience enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants