The whole multiple-dropdown thing took me a while to figure out correctly, so I’ll quickly share my learnings here for anyone else running into these issues.
I’m still getting a bit confused by the fact that Spectrum is praised and recommended to be used while there’s so little documentation on it (the actual UXP implementation) that we kind of have to fish in muddy waters.
Anyways, the UXP examples show the use of onChange(evt => console.log(evt.target.selectedIndex)
to retrieve the index of the selected item. For multiple selectable options (sp-menu-items), this doesn’t help a lot as it always returns the index of the first selected option.
Logging evt.target.__proto__
finally gave me an idea of which properties the sp-dropdown object actually has:
So at least there’s a getter called selectedOptions
, which returns the sp-menu-item elements whose values we can the retrieve and parse.
If you’re using React, you’re probably storing selected options in some kind of state. In my case it looks like this:
const state = {
someObj: {
a: false,
b: false,
c: false,
}
}
This results in a setup like the following:
<WC onChange={e => {
const selectedKeys = e.target.selectedOptions.map(({value}) => value)
const updated = Object.keys(this.state.someObj).reduce((obj,key) => ({...obj, [key]:selectedKeys.includes(key)}),{});
this.setState({someObj: updated})
}}>
<sp-dropdown placeholder="Make a selection...">
<sp-menu multiple slot="options">
{
Object.keys(this.state.someObj).map(key => {
return <sp-menu-item selected={this.state.someObj[key] ? true : null} key={key} value={key}>{key}</sp-menu-item>
})
}
</sp-menu>
</sp-dropdown>
</WC>
However, if your state looks more like that (using arrays)…
const state = {
options: ["a", "b", "c"],
selectedOptions: [],
}
… the setup would probably be similar to:
<WC onChange={e => {
const selectedOptions = e.target.selectedOptions.map(({value}) => value)
this.setState({selectedOptions})
}}>
<sp-dropdown placeholder="Make a selection...">
<sp-menu multiple slot="options">
{
this.state.options.map(opt => {
return <sp-menu-item selected={this.state.selectedOptions.includes(opt) ? true : null} key={opt} value={opt}>{opt}</sp-menu-item>
})
}
</sp-menu>
</sp-dropdown>
</WC>
So far so good, but there’s still a problem:
The selected
property of a sp-menu-item seems to have been designed for non-multiple sp-menus only. It overwrites the current selected value so that there’s always just one selected item at a time, which prevents me from prefilling the dropdown with the initial values.
One more thought:
Maybe the sp-dropdown / sp-menu should expose more events than just a change
event? If it provided a more specific onSelect(opt)
, state updates would be a lot easier as only the single changed option would have to be updated.
Edit: Another thing I just noticed:
When there’s more than one option selected, the dropdown text will only display the first selected options name. From a usability point of view that’s quite problematic as the user has no visual feedback of what’s selected and if his selection worked, other than reopening the dropdown to double-check. Commonly it should show something like “Option1, Option2” or “2 Selected”.