feat: DH-19683: multi-select combo box component in deephaven.ui#1349
feat: DH-19683: multi-select combo box component in deephaven.ui#1349jnumainville wants to merge 18 commits into
Conversation
|
ui docs preview (Available for 14 days) |
| The `default_selected_key` is useful for simpler scenarios where you don't need to control the state externally. The `selected_key` is used for scenarios where the state should be managed by the parent component, providing control and flexibility over the selection of the combo box. | ||
| `default_selected_keys` is useful for simpler scenarios where you don't need to control the state externally. `selected_keys` is used for scenarios where the state should be managed by the parent component, providing control and flexibility over the selection of the combo box. | ||
|
|
||
| <!-- prettier-ignore --> |
There was a problem hiding this comment.
not sure why we need the prettier ignore here? is formatting messing with the note?
There was a problem hiding this comment.
Yes. It keeps collapsing any > [!NOTE] into one line on save.
|
@jnumainville As mentioned in our 1:1, I don't like how this is currently a breaking change. Looping @dsmmcken for some input.
Thoughts? |
|
My preference is 1, but If you've given it thought then I back your conclusion of 4. |
|
@jnumainville I think you concluded 4 would be more elegant as well. Unless you make a case for 1, I'd like to go with 4. Also, how big of tables have you tested this with? |
|
I prefer 4 as well. The implementation here is unpleasant, specifically the deprecation logic, and I think it's a case where if the deprecation logic is tricky to implement it's tricky to follow (when do I have a |
mofojed
left a comment
There was a problem hiding this comment.
A few updates to the docs, a few comments. Also we need to add an e2e test (probably should have one for combo box as well).
| from deephaven import ui | ||
|
|
||
|
|
||
| @ui.component | ||
| def ui_multi_select_form_examples(): | ||
| return [ | ||
| ui.form( | ||
| ui.multi_select( | ||
| ui.item("Chocolate"), | ||
| ui.item("Mint"), | ||
| ui.item("Vanilla"), | ||
| ui.item("Strawberry"), | ||
| ui.item("Cookies and Cream"), | ||
| ui.item("Coffee"), | ||
| ui.item("Mango"), | ||
| label="Ice cream flavors", | ||
| name="flavors", | ||
| ), | ||
| ui.button("Submit", type="submit"), | ||
| on_submit=lambda event: print(event), | ||
| ) | ||
| ] | ||
|
|
||
|
|
||
| my_multi_select_form_examples = ui_multi_select_form_examples() |
There was a problem hiding this comment.
This is just one example, don't need the array or the pluralization
| from deephaven import ui | |
| @ui.component | |
| def ui_multi_select_form_examples(): | |
| return [ | |
| ui.form( | |
| ui.multi_select( | |
| ui.item("Chocolate"), | |
| ui.item("Mint"), | |
| ui.item("Vanilla"), | |
| ui.item("Strawberry"), | |
| ui.item("Cookies and Cream"), | |
| ui.item("Coffee"), | |
| ui.item("Mango"), | |
| label="Ice cream flavors", | |
| name="flavors", | |
| ), | |
| ui.button("Submit", type="submit"), | |
| on_submit=lambda event: print(event), | |
| ) | |
| ] | |
| my_multi_select_form_examples = ui_multi_select_form_examples() | |
| from deephaven import ui | |
| @ui.component | |
| def ui_multi_select_form_example(): | |
| return ui.form( | |
| ui.multi_select( | |
| ui.item("Chocolate"), | |
| ui.item("Mint"), | |
| ui.item("Vanilla"), | |
| ui.item("Strawberry"), | |
| ui.item("Cookies and Cream"), | |
| ui.item("Coffee"), | |
| ui.item("Mango"), | |
| label="Ice cream flavors", | |
| name="flavors", | |
| ), | |
| ui.button("Submit", type="submit"), | |
| on_submit=lambda event: print(event), | |
| ) | |
| my_multi_select_form_example = ui_multi_select_form_example() |
| return [ | ||
| ui.multi_select( | ||
| ui.item("Option 1"), | ||
| ui.item("Option 2"), | ||
| ui.item("Option 3"), | ||
| ui.item("Option 4"), | ||
| ui.item("Option 5"), | ||
| ui.item("Option 6"), | ||
| ui.item("Option 7"), | ||
| ui.item("Option 8"), | ||
| ui.item("Option 9"), | ||
| input_value=input_value, | ||
| on_input_change=handle_input_change, | ||
| selected_keys=selection_state, | ||
| on_change=handle_selection_change, | ||
| label="Pick options", | ||
| ) | ||
| ] |
There was a problem hiding this comment.
| return [ | |
| ui.multi_select( | |
| ui.item("Option 1"), | |
| ui.item("Option 2"), | |
| ui.item("Option 3"), | |
| ui.item("Option 4"), | |
| ui.item("Option 5"), | |
| ui.item("Option 6"), | |
| ui.item("Option 7"), | |
| ui.item("Option 8"), | |
| ui.item("Option 9"), | |
| input_value=input_value, | |
| on_input_change=handle_input_change, | |
| selected_keys=selection_state, | |
| on_change=handle_selection_change, | |
| label="Pick options", | |
| ) | |
| ] | |
| return ui.multi_select( | |
| ui.item("Option 1"), | |
| ui.item("Option 2"), | |
| ui.item("Option 3"), | |
| ui.item("Option 4"), | |
| ui.item("Option 5"), | |
| ui.item("Option 6"), | |
| ui.item("Option 7"), | |
| ui.item("Option 8"), | |
| ui.item("Option 9"), | |
| input_value=input_value, | |
| on_input_change=handle_input_change, | |
| selected_keys=selection_state, | |
| on_change=handle_selection_change, | |
| label="Pick options", | |
| ) |
| return ( | ||
| // eslint-disable-next-line react/jsx-props-no-spreading | ||
| <DHMultiSelect loadingState="loading" {...multiSelectProps}> | ||
| {[]} |
There was a problem hiding this comment.
What's going on with these children here? At least use EMPTY_ARRAY, but is it required to specify children if we're in a loading/error state?
There was a problem hiding this comment.
This appears to be adapted directly from Combobox (and ListView and Picker)
There was a problem hiding this comment.
Here's some more context (AI traced but I did double check):
Yes, the empty arrays are necessary. The DHComboBox component from @deephaven/components has children as a required prop (typed as ItemOrSection | ItemOrSection[]). Since TypeScript would error if you omitted it, the empty array {[]} satisfies the type requirement while rendering no items — appropriate for the loading and error states where there's no real data to display.
Then I asked for more details:
The children prop flows into wrapItemChildren() which calls ensureArray(children). That utility treats non-array values by wrapping them in an array — so undefined becomes [undefined]. Then .map() processes each element, and since undefined isn't an Item or Section element, it falls through to the primitive branch which does String(undefined) → "undefined" and creates an <Item key="undefined" textValue="undefined">.
So passing undefined wouldn't crash, but it would silently render a phantom item labeled "undefined" in the dropdown. The empty array [] produces an empty .map() result and correctly renders nothing.
Here's the link to that function:
https://github.com/deephaven/web-client-ui/blob/211519a91f7bbdf4e8dee812a1061a989447d0a2/packages/components/src/spectrum/utils/itemWrapperUtils.tsx#L56
There was a problem hiding this comment.
I did switch to EMPTY_ARRAY though
There was a problem hiding this comment.
We could fix this in itemWrapperUtils.tsx, it's in our code. May as well add it to deephaven/web-client-ui#2685 ?
|
Waiting on e2e tests until I have deephaven/web-client-ui#2685 merged and released |
Add multi-select combobox component. Tested with (and required for) deephaven/deephaven-plugins#1349 --------- Co-authored-by: gzh2003 <germainzhanghoule@gmail.com> Co-authored-by: Mike Bender <mikebender@deephaven.io>
mofojed
left a comment
There was a problem hiding this comment.
Looks good but we'll need a web-client-ui release first
Add
newmulti_select component. Depends on deephaven/web-client-ui#2685, so won't pass or merge until that is merged.