diff --git a/plugins/ui/docs/components/combo_box.md b/plugins/ui/docs/components/combo_box.md index 65e4e9c20..f7e463ffe 100644 --- a/plugins/ui/docs/components/combo_box.md +++ b/plugins/ui/docs/components/combo_box.md @@ -36,14 +36,15 @@ my_combo_box_basic = ui_combo_box_basic() Recommendations for creating clear and effective combo boxes: 1. The combo box's text input simplifies searching through large lists. For lists with fewer than 6 items, use radio buttons. For lists with more than 6 items, assess if the list is complex enough to need searching and filtering, and if not, use a picker instead. -2. Every combo box should have a label specified. Without one, the combo box is ambiguous and not accessible. -3. Options in the combo box should be kept short and concise; multiple lines are strongly discouraged. If more than one line is needed, consider using a description to add context to the option. -4. Choose a `width` for your combo boxes that can accommodate most of the available options. -5. The field labels, menu items, and placeholder text should all be in sentence case. -6. Identify which combo boxes are required or optional, and use the `is_required` field or the `necessity_indicator` to mark them accordingly. -7. A combo box's help text should provide actionable guidance on what to select and how to select it, offering additional context without repeating the placeholder text. -8. When an error occurs, the help text specified in a combo box is replaced by error text; thus, ensure both help and error text convey the same essential information to maintain consistent messaging and prevent loss of critical details. -9. Write error messages in a clear, concise, and helpful manner, guiding users to resolve the issue without ambiguity; ideally, they should be 1-2 short, complete sentences. +2. For selecting multiple options, use a [multi-select](multi_select.md) instead. +3. Every combo box should have a label specified. Without one, the combo box is ambiguous and not accessible. +4. Options in the combo box should be kept short and concise; multiple lines are strongly discouraged. If more than one line is needed, consider using a description to add context to the option. +5. Choose a `width` for your combo boxes that can accommodate most of the available options. +6. The field labels, menu items, and placeholder text should all be in sentence case. +7. Identify which combo boxes are required or optional, and use the `is_required` field or the `necessity_indicator` to mark them accordingly. +8. A combo box's help text should provide actionable guidance on what to select and how to select it, offering additional context without repeating the placeholder text. +9. When an error occurs, the help text specified in a combo box is replaced by error text; thus, ensure both help and error text convey the same essential information to maintain consistent messaging and prevent loss of critical details. +10. Write error messages in a clear, concise, and helpful manner, guiding users to resolve the issue without ambiguity; ideally, they should be 1-2 short, complete sentences. ## Data sources @@ -767,7 +768,7 @@ my_combo_box_alignment_direction_examples = ui_combo_box_alignment_direction_exa ## How to create a multi-select component -By leveraging the `on_change` handler of `ui.combo_box` to dynamically generate items, you can pair it with `ui.tag_group` to build a multi-select component. +It's recommended to use [`multi_select`](multi_select.md) for `multi-select` use cases, but if you want the `combo_box` separate from the tags you can also use a `tag_group` to show selected items, and use the `on_input_change` and `on_change` events to manage the state between them. ```python from deephaven import ui diff --git a/plugins/ui/docs/components/multi_select.md b/plugins/ui/docs/components/multi_select.md new file mode 100644 index 000000000..3be857332 --- /dev/null +++ b/plugins/ui/docs/components/multi_select.md @@ -0,0 +1,757 @@ +# Multi Select + +Multi select displays selected items as tags inside the input area and presents a filterable dropdown list for multi-selection. + +## Example + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_basic(): + selected, set_selected = ui.use_state([]) + + return ui.multi_select( + ui.item("red panda"), + ui.item("cat"), + ui.item("dog"), + ui.item("aardvark"), + ui.item("kangaroo"), + ui.item("snake"), + ui.item("ant"), + label="Favorite Animals", + selected_keys=selected, + on_change=set_selected, + ) + + +my_multi_select_basic = ui_multi_select_basic() +``` + +## UI Recommendations + +Recommendations for creating clear and effective multi selects: + +1. The multi select's text input simplifies searching through large lists. For lists with fewer than 6 items, use a checkbox group. +2. For selecting only one option, use a [`combo_box`](combo_box.md) instead. +3. Every multi select should have a label specified. Without one, the multi select is ambiguous and not accessible. +4. Options in the multi select should be kept short and concise; multiple lines are strongly discouraged. If more than one line is needed, consider using a description to add context to the option. +5. Choose a `width` for your multi selects that can accommodate most of the available options. +6. The field labels, menu items, and placeholder text should all be in sentence case. +7. Identify which multi selects are required or optional, and use the `is_required` field or the `necessity_indicator` to mark them accordingly. +8. A multi select's help text should provide actionable guidance on what to select and how to select it, offering additional context without repeating the placeholder text. +9. When an error occurs, the help text specified in a multi select is replaced by error text; thus, ensure both help and error text convey the same essential information to maintain consistent messaging and prevent loss of critical details. +10. Write error messages in a clear, concise, and helpful manner, guiding users to resolve the issue without ambiguity; ideally, they should be 1-2 short, complete sentences. + +## Data sources + +For multi selects, we can use a Deephaven table or [URI](uri.md) as a data source to populate the options. When using a table, it automatically uses the first column as both the key and label. If there are any duplicate keys, an error will be thrown; to avoid this, a `select_distinct` can be used on the table prior to using it as a multi select data source. + +```python order=my_multi_select_table_source_example,countries +from deephaven import ui +from deephaven.plot import express as dx + + +countries = dx.data.gapminder().select_distinct("Country") + + +my_multi_select_table_source_example = ui.multi_select(countries, label="Sample Multi Select") +``` + +## Item table sources + +If you wish to manually specify the keys and labels, use a `ui.item_table_source` to dynamically derive the options from a table. + +```python order=my_multi_select_item_table_source_example,column_types +from deephaven import ui, empty_table + +account_icon = "vsAccount" +columns = [ + "Key=new Integer(i)", + "Label=new String(`Display `+i)", + "Icon=(String) account_icon", +] +column_types = empty_table(20).update(columns) + + +item_table_source = ui.item_table_source( + column_types, + key_column="Key", + label_column="Label", + icon_column="Icon", +) + + +my_multi_select_item_table_source_example = ui.multi_select( + item_table_source, label="User Multi Select" +) +``` + +## Custom Value + +By default, when a multi select loses focus, it resets its input value. To allow users to enter custom values as tags, use the `allows_custom_value` prop. Pressing Enter when no item is focused adds the typed text as a custom tag. If the typed text matches an existing item's label, that item's key is used instead. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_custom_value_example(): + selected, set_selected = ui.use_state([]) + return ui.multi_select( + ui.item("Option 1"), + ui.item("Option 2"), + ui.item("Option 3"), + ui.item("Option 4"), + ui.item("Option 5"), + allows_custom_value=True, + selected_keys=selected, + on_change=set_selected, + label="Select or type options", + ) + + +my_multi_select_custom_value_example = ui_multi_select_custom_value_example() +``` + +## HTML Forms + +Multi selects can support a `name` prop for integration with HTML forms, allowing for easy identification of a value on form submission. The `form_value` prop determines whether comma-joined keys or labels of the selected items are submitted via the hidden form input. + +```python +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() +``` + +## Labeling + +The multi select can be labeled using the `label` prop, and if no label is provided, an `aria_label` must be provided to identify the control for accessibility purposes. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_label_examples(): + 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"), + label="Pick options", + ), + 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"), + aria_label="Pick options", + ), + ] + + +my_multi_select_label_examples = ui_multi_select_label_examples() +``` + +The `is_required` prop and the `necessity_indicator` props can be used to show whether selecting an option in the multi select is required or optional. + +When the `necessity_indicator` prop is set to "label", a localized string will be generated for "(required)" or "(optional)" automatically. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_required_examples(): + 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"), + label="Pick options", + is_required=True, + ), + 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"), + label="Pick options", + is_required=True, + necessity_indicator="label", + ), + 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"), + label="Pick options", + necessity_indicator="label", + ), + ] + + +my_multi_select_required_examples = ui_multi_select_required_examples() +``` + +## Selection + +Use `selected_keys` or `default_selected_keys` to set the selected options. + +`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 multi select. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_selected_keys_examples(): + options, set_options = ui.use_state(["Option 1", "Option 3"]) + 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"), + default_selected_keys=["Option 2", "Option 4"], + label="Pick options (uncontrolled)", + ), + 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"), + selected_keys=options, + on_change=set_options, + label="Pick options (controlled)", + ), + ] + + +my_multi_select_selected_keys_examples = ui_multi_select_selected_keys_examples() +``` + +## Sections + +Multi selects support sections to group options. Sections can be used by wrapping groups of items in a Section element. Each Section takes a title and key prop. + +Note that, when searching for options, searching by section will not result in the respective options within that section appearing. + +Also, sections can only be used directly, not from a table data source. + +```python +from deephaven import ui + + +my_multi_select_section_example = ui.multi_select( + ui.section( + 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"), + title="Section 1", + ), + ui.section( + ui.item("Option 9"), + ui.item("Option 10"), + ui.item("Option 11"), + ui.item("Option 12"), + ui.item("Option 13"), + ui.item("Option 14"), + ui.item("Option 15"), + ui.item("Option 16"), + title="Section 2", + ), + label="Pick options", +) +``` + +## Events + +Multi selects support selection via mouse, keyboard, and touch. You can handle all these via the `on_change` prop. Additionally, multi selects accept an `on_input_change` prop, which is triggered whenever the search value is edited by the user, whether through typing or option selection. + +Each interaction done in the multi select will trigger its associated event handler. For instance, typing in the input field will only trigger the `on_input_change`, not the `on_change`. + +Note, this is not the case for selections; when a selection is made, both the `on_change` and `on_input_change` are triggered. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_events_example(): + input_value, set_input_value = ui.use_state("") + selection_state, set_selection_state = ui.use_state([]) + + def handle_input_change(new_value): + set_input_value(new_value) + print(f"Text changed to {new_value}") + + def handle_selection_change(new_value): + set_selection_state(new_value) + print(f"Selection changed to {new_value}") + + 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", + ) + + +my_multi_select_events_example = ui_multi_select_events_example() +``` + +## Complex items + +Items within a multi select can include additional content to better convey options. You can add icons, avatars, and descriptions to the children of an `ui.item`. When adding a description, set the `slot` prop to "description" to differentiate between the text elements. + +```python +from deephaven import ui + + +my_multi_select_complex_items_example = ui.multi_select( + ui.item( + ui.icon("vsGithubAlt"), + ui.text("Github"), + ui.text("Github Option", slot="description"), + text_value="Github", + ), + ui.item( + ui.icon("vsAzureDevops"), + ui.text("Azure"), + ui.text("Azure Option", slot="description"), + text_value="Azure", + ), + label="Pick services", +) +``` + +## Validation + +The `is_required` prop ensures that the user selects an option. The related `validation_behaviour` prop allows the user to specify aria or native verification. + +When the prop is set to "native", the validation errors block form submission and are displayed as help text automatically. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_validation_behaviour_example(): + return ui.form( + ui.multi_select( + ui.section(ui.item("Option 1"), ui.item("Option 2"), title="Section 1"), + validation_behavior="aria", + is_required=True, + label="Pick options", + ) + ) + + +my_multi_select_validation_behaviour_example = ( + ui_multi_select_validation_behaviour_example() +) +``` + +## Trigger Options + +By default, the multi select's menu opens when the user types into the input field ("input"). This behavior can be changed to open on focus ("focus") or only when the field button is clicked ("manual") using the `menu_trigger` prop. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_trigger_option_examples(): + 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"), + label="Select Options", + ), + 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"), + label="Select Options", + menu_trigger="focus", + ), + 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"), + label="Select Options", + menu_trigger="manual", + ), + ] + + +my_multi_select_trigger_option_examples = ui_multi_select_trigger_option_examples() +``` + +## Label position + +By default, the position of a multi select's label is above the multi select, but it can be moved to the side using the `label_position` prop. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_label_position_examples(): + 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"), + label="Test Label", + ), + 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"), + label="Test Label", + label_position="side", + ), + ] + + +my_multi_select_label_position_examples = ui_multi_select_label_position_examples() +``` + +## Quiet State + +The `is_quiet` prop makes a multi select "quiet". This can be useful when the multi select and its corresponding styling should not distract users from surrounding content. + +```python +from deephaven import ui + + +my_multi_select_is_quiet_example = 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"), + is_quiet=True, + label="Pick options", +) +``` + +## Disabled State + +The `is_disabled` prop disables a multi select to prevent user interaction. This is useful when the multi select should be visible but unavailable for selection. + +```python +from deephaven import ui + + +my_multi_select_is_disabled_example = 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"), + is_disabled=True, + label="Pick options", +) +``` + +## Read-only State + +The `is_read_only` prop prevents user input in a multi select, but the selected options should be visible. + +```python +from deephaven import ui + + +my_multi_select_is_read_only_example = ui.multi_select( + ui.item("Option 1", key="Option 1"), + ui.item("Option 2", key="Option 2"), + ui.item("Option 3", key="Option 3"), + ui.item("Option 4", key="Option 4"), + ui.item("Option 5", key="Option 5"), + ui.item("Option 6", key="Option 6"), + ui.item("Option 7", key="Option 7"), + ui.item("Option 8", key="Option 8"), + default_selected_keys=["Option 1", "Option 3"], + is_read_only=True, + label="Pick options", +) +``` + +## Help text + +A multi select can have both a `description` and an `error_message`. The description remains visible at all times. Use the error message to offer specific guidance on how to correct the input. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_help_text_examples(): + return [ + ui.multi_select( + ui.section( + ui.item("Option 1", key="Option 1"), + ui.item("Option 2", key="Option 2"), + ui.item("Option 3", key="Option 3"), + ui.item("Option 4", key="Option 4"), + ui.item("Option 5", key="Option 5"), + ui.item("Option 6", key="Option 6"), + ui.item("Option 7", key="Option 7"), + ui.item("Option 8", key="Option 8"), + title="Section 1", + ), + label="Sample Label", + description="Select one or more options.", + ), + ui.multi_select( + ui.section( + ui.item("Option 1", key="Option 1"), + ui.item("Option 2", key="Option 2"), + ui.item("Option 3", key="Option 3"), + ui.item("Option 4", key="Option 4"), + ui.item("Option 5", key="Option 5"), + ui.item("Option 6", key="Option 6"), + ui.item("Option 7", key="Option 7"), + ui.item("Option 8", key="Option 8"), + title="Section 1", + ), + label="Sample Label", + validation_state="invalid", + error_message="Sample invalid error message.", + ), + ] + + +my_multi_select_help_text_examples = ui_multi_select_help_text_examples() +``` + +## Contextual Help + +Using the `contextual_help` prop, a `ui.contextual_help` can be placed next to the label to provide additional information about the multi select. + +```python +from deephaven import ui + + +my_multi_select_contextual_help_example = ui.multi_select( + ui.section( + 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"), + title="Section 1", + ), + label="Sample Label", + contextual_help=ui.contextual_help( + ui.heading("Content tips"), ui.content("Tips for the content.") + ), +) +``` + +## Custom width + +The `width` prop adjusts the width of a multi select, and the `max_width` prop enforces a maximum width. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_width_examples(): + return [ + ui.multi_select( + ui.item("Option 1", key="Option 1"), + ui.item("Option 2", key="Option 2"), + ui.item("Option 3", key="Option 3"), + ui.item("Option 4", key="Option 4"), + ui.item("Option 5", key="Option 5"), + ui.item("Option 6", key="Option 6"), + ui.item("Option 7", key="Option 7"), + ui.item("Option 8", key="Option 8"), + width="size-3600", + ), + ui.multi_select( + ui.item("Option 1", key="Option 1"), + ui.item("Option 2", key="Option 2"), + ui.item("Option 3", key="Option 3"), + ui.item("Option 4", key="Option 4"), + ui.item("Option 5", key="Option 5"), + ui.item("Option 6", key="Option 6"), + ui.item("Option 7", key="Option 7"), + ui.item("Option 8", key="Option 8"), + width="size-3600", + max_width="100%", + ), + ] + + +my_multi_select_width_examples = ui_multi_select_width_examples() +``` + +## Align and Direction + +The `align` prop sets the text alignment of the options in the multi select, while the `direction` prop specifies which direction the menu will open. + +```python +from deephaven import ui + + +@ui.component +def ui_multi_select_alignment_direction_examples(): + return ui.view( + ui.flex( + 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"), + align="end", + menu_width="size-3000", + ), + 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"), + direction="top", + ), + gap="size-150", + direction="column", + ), + padding=40, + ) + + +my_multi_select_alignment_direction_examples = ( + ui_multi_select_alignment_direction_examples() +) +``` + +## API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.multi_select +``` diff --git a/plugins/ui/docs/sidebar.json b/plugins/ui/docs/sidebar.json index 41a688a24..2d7c4e19a 100644 --- a/plugins/ui/docs/sidebar.json +++ b/plugins/ui/docs/sidebar.json @@ -326,6 +326,10 @@ "label": "meter", "path": "components/meter.md" }, + { + "label": "multi_select", + "path": "components/multi_select.md" + }, { "label": "number_field", "path": "components/number_field.md" @@ -359,7 +363,7 @@ "path": "components/range_slider.md" }, { - "label": "route", + "label": "router", "path": "components/router.md" }, { diff --git a/plugins/ui/docs/snapshots/0761e766ac976e28061e143e034b23bf.json b/plugins/ui/docs/snapshots/0761e766ac976e28061e143e034b23bf.json index 62b851727..ef787579e 100644 --- a/plugins/ui/docs/snapshots/0761e766ac976e28061e143e034b23bf.json +++ b/plugins/ui/docs/snapshots/0761e766ac976e28061e143e034b23bf.json @@ -1 +1 @@ -{"file":"components/combo_box.md","objects":{"my_combo_box_selected_key_examples":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":[{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","defaultSelectedKey":"Option 2","validationBehavior":"aria","label":"Pick an option (uncontrolled)","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","selectedKey":"Option 1","validationBehavior":"aria","label":"Pick an option (controlled)","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]},"__dhElemName":"__main__.ui_combo_box_selected_key_examples"},"state":"{\"state\": {\"0\": \"Option 1\"}}"}}}} \ No newline at end of file +{"file":"components/combo_box.md","objects":{"my_combo_box_selected_key_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_combo_box_selected_key_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","defaultSelectedKey":"Option 2","validationBehavior":"aria","label":"Pick an option (uncontrolled)","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","selectedKey":"Option 1","validationBehavior":"aria","label":"Pick an option (controlled)","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]}},"state":"{\"state\": {\"0\": \"Option 1\"}}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/16d416b14e0b60ca58840768ebb3377f.json b/plugins/ui/docs/snapshots/16d416b14e0b60ca58840768ebb3377f.json new file mode 100644 index 000000000..8b043ad0e --- /dev/null +++ b/plugins/ui/docs/snapshots/16d416b14e0b60ca58840768ebb3377f.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_contextual_help_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Sample Label","labelPosition":"top","contextualHelp":{"__dhElemName":"deephaven.ui.components.ContextualHelp","props":{"heading":{"__dhElemName":"deephaven.ui.components.Heading","props":{"children":["Content tips"],"level":3}},"content":{"__dhElemName":"deephaven.ui.components.Content","props":{"children":["Tips for the content."]}},"variant":"help","placement":"bottom start"}},"children":{"__dhElemName":"deephaven.ui.components.Section","props":{"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}}]}}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/2de457dcb8fd02dae37ae4d35d28a023.json b/plugins/ui/docs/snapshots/2de457dcb8fd02dae37ae4d35d28a023.json new file mode 100644 index 000000000..0395334b8 --- /dev/null +++ b/plugins/ui/docs/snapshots/2de457dcb8fd02dae37ae4d35d28a023.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"column_types":{"type":"Table","data":{"columns":[{"name":"Key","type":"int"},{"name":"Label","type":"java.lang.String"},{"name":"Icon","type":"java.lang.String"}],"rows":[[{"value":"0"},{"value":"Display 0"},{"value":"vsAccount"}],[{"value":"1"},{"value":"Display 1"},{"value":"vsAccount"}],[{"value":"2"},{"value":"Display 2"},{"value":"vsAccount"}],[{"value":"3"},{"value":"Display 3"},{"value":"vsAccount"}],[{"value":"4"},{"value":"Display 4"},{"value":"vsAccount"}],[{"value":"5"},{"value":"Display 5"},{"value":"vsAccount"}],[{"value":"6"},{"value":"Display 6"},{"value":"vsAccount"}],[{"value":"7"},{"value":"Display 7"},{"value":"vsAccount"}],[{"value":"8"},{"value":"Display 8"},{"value":"vsAccount"}],[{"value":"9"},{"value":"Display 9"},{"value":"vsAccount"}],[{"value":"10"},{"value":"Display 10"},{"value":"vsAccount"}],[{"value":"11"},{"value":"Display 11"},{"value":"vsAccount"}],[{"value":"12"},{"value":"Display 12"},{"value":"vsAccount"}],[{"value":"13"},{"value":"Display 13"},{"value":"vsAccount"}],[{"value":"14"},{"value":"Display 14"},{"value":"vsAccount"}],[{"value":"15"},{"value":"Display 15"},{"value":"vsAccount"}],[{"value":"16"},{"value":"Display 16"},{"value":"vsAccount"}],[{"value":"17"},{"value":"Display 17"},{"value":"vsAccount"}],[{"value":"18"},{"value":"Display 18"},{"value":"vsAccount"}],[{"value":"19"},{"value":"Display 19"},{"value":"vsAccount"}]]}},"my_multi_select_item_table_source_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"User Multi Select","labelPosition":"top","labelColumn":"Label","iconColumn":"Icon","keyColumn":"Key","children":{"__dhObid":0}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/388471963fdcbabcc15e590d509e0263.json b/plugins/ui/docs/snapshots/388471963fdcbabcc15e590d509e0263.json new file mode 100644 index 000000000..2d952a883 --- /dev/null +++ b/plugins/ui/docs/snapshots/388471963fdcbabcc15e590d509e0263.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_complex_items_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Pick services","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"textValue":"Github","children":[{"__dhElemName":"deephaven.ui.icons.vsGithubAlt","props":{"name":"vsGithubAlt","slot":"icon"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Github"],"slot":"text"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Github Option"],"slot":"description"}}]}},{"__dhElemName":"deephaven.ui.components.Item","props":{"textValue":"Azure","children":[{"__dhElemName":"deephaven.ui.icons.vsAzureDevops","props":{"name":"vsAzureDevops","slot":"icon"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Azure"],"slot":"text"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Azure Option"],"slot":"description"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/3e1db3f10151d0f7fddf9f4259d62447.json b/plugins/ui/docs/snapshots/3e1db3f10151d0f7fddf9f4259d62447.json new file mode 100644 index 000000000..f7a7998ec --- /dev/null +++ b/plugins/ui/docs/snapshots/3e1db3f10151d0f7fddf9f4259d62447.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_selected_keys_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_selected_keys_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","defaultSelectedKeys":["Option 2","Option 4"],"validationBehavior":"aria","label":"Pick options (uncontrolled)","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","selectedKeys":["Option 1","Option 3"],"validationBehavior":"aria","label":"Pick options (controlled)","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/415bf47be017adfd4bc2f5c6cba8a5fe.json b/plugins/ui/docs/snapshots/415bf47be017adfd4bc2f5c6cba8a5fe.json new file mode 100644 index 000000000..e8d1af4ca --- /dev/null +++ b/plugins/ui/docs/snapshots/415bf47be017adfd4bc2f5c6cba8a5fe.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_is_disabled_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","isDisabled":true,"validationBehavior":"aria","label":"Pick options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/41b900591efc757b23440b1380150047.json b/plugins/ui/docs/snapshots/41b900591efc757b23440b1380150047.json new file mode 100644 index 000000000..4bd0a5d1a --- /dev/null +++ b/plugins/ui/docs/snapshots/41b900591efc757b23440b1380150047.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_label_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_label_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Pick options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","labelPosition":"top","aria-label":"Pick options","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/4d5923035666817d4436260b412c39bf.json b/plugins/ui/docs/snapshots/4d5923035666817d4436260b412c39bf.json new file mode 100644 index 000000000..b09d3cd00 --- /dev/null +++ b/plugins/ui/docs/snapshots/4d5923035666817d4436260b412c39bf.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_label_position_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_label_position_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Test Label","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Test Label","labelPosition":"side","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/52d601a9bcb4a2a8f8be03c3992b8d63.json b/plugins/ui/docs/snapshots/52d601a9bcb4a2a8f8be03c3992b8d63.json new file mode 100644 index 000000000..df416ae26 --- /dev/null +++ b/plugins/ui/docs/snapshots/52d601a9bcb4a2a8f8be03c3992b8d63.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_alignment_direction_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_alignment_direction_examples","props":{"children":{"__dhElemName":"deephaven.ui.components.View","props":{"children":[{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-150","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"menuWidth":"size-3000","formValue":"text","validationBehavior":"aria","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"top","shouldFlip":true,"formValue":"text","validationBehavior":"aria","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}}]}}]}}],"padding":40}}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/560d89953fdb8d1b95ca2898778b9519.json b/plugins/ui/docs/snapshots/560d89953fdb8d1b95ca2898778b9519.json new file mode 100644 index 000000000..06e22fe45 --- /dev/null +++ b/plugins/ui/docs/snapshots/560d89953fdb8d1b95ca2898778b9519.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_help_text_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_help_text_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Sample Label","description":"Select one or more options.","labelPosition":"top","children":{"__dhElemName":"deephaven.ui.components.Section","props":{"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 1","children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 2","children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 3","children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 4","children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 5","children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 6","children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 7","children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 8","children":"Option 8"}}]}}}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Sample Label","errorMessage":"Sample invalid error message.","validationState":"invalid","labelPosition":"top","children":{"__dhElemName":"deephaven.ui.components.Section","props":{"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 1","children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 2","children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 3","children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 4","children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 5","children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 6","children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 7","children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 8","children":"Option 8"}}]}}}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/5d3ed3fecda2d0bd4ae4d8825d6fbea9.json b/plugins/ui/docs/snapshots/5d3ed3fecda2d0bd4ae4d8825d6fbea9.json new file mode 100644 index 000000000..abf8a23dc --- /dev/null +++ b/plugins/ui/docs/snapshots/5d3ed3fecda2d0bd4ae4d8825d6fbea9.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_width_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_width_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","labelPosition":"top","width":"size-3600","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 1","children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 2","children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 3","children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 4","children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 5","children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 6","children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 7","children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 8","children":"Option 8"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","labelPosition":"top","width":"size-3600","maxWidth":"100%","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 1","children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 2","children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 3","children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 4","children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 5","children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 6","children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 7","children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 8","children":"Option 8"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/607ba14453021687f50ec3ebd1fd4c77.json b/plugins/ui/docs/snapshots/607ba14453021687f50ec3ebd1fd4c77.json new file mode 100644 index 000000000..fd56b06ce --- /dev/null +++ b/plugins/ui/docs/snapshots/607ba14453021687f50ec3ebd1fd4c77.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"countries":{"type":"Table","data":{"columns":[{"name":"Country","type":"java.lang.String"}],"rows":[[{"value":"Afghanistan"}],[{"value":"Albania"}],[{"value":"Algeria"}],[{"value":"Angola"}],[{"value":"Argentina"}],[{"value":"Australia"}],[{"value":"Austria"}],[{"value":"Bahrain"}],[{"value":"Bangladesh"}],[{"value":"Belgium"}],[{"value":"Benin"}],[{"value":"Bolivia"}],[{"value":"Bosnia and Herzegovina"}],[{"value":"Botswana"}],[{"value":"Brazil"}],[{"value":"Bulgaria"}],[{"value":"Burkina Faso"}],[{"value":"Burundi"}],[{"value":"Cambodia"}],[{"value":"Cameroon"}],[{"value":"Canada"}],[{"value":"Central African Republic"}],[{"value":"Chad"}],[{"value":"Chile"}],[{"value":"China"}],[{"value":"Colombia"}],[{"value":"Comoros"}],[{"value":"Congo, Dem. Rep."}],[{"value":"Congo, Rep."}],[{"value":"Costa Rica"}],[{"value":"Cote d'Ivoire"}],[{"value":"Croatia"}],[{"value":"Cuba"}],[{"value":"Czech Republic"}],[{"value":"Denmark"}],[{"value":"Djibouti"}],[{"value":"Dominican Republic"}],[{"value":"Ecuador"}],[{"value":"Egypt"}],[{"value":"El Salvador"}],[{"value":"Equatorial Guinea"}],[{"value":"Eritrea"}],[{"value":"Ethiopia"}],[{"value":"Finland"}],[{"value":"France"}],[{"value":"Gabon"}],[{"value":"Gambia"}],[{"value":"Germany"}],[{"value":"Ghana"}],[{"value":"Greece"}],[{"value":"Guatemala"}],[{"value":"Guinea"}],[{"value":"Guinea-Bissau"}],[{"value":"Haiti"}],[{"value":"Honduras"}],[{"value":"Hong Kong, China"}],[{"value":"Hungary"}],[{"value":"Iceland"}],[{"value":"India"}],[{"value":"Indonesia"}],[{"value":"Iran"}],[{"value":"Iraq"}],[{"value":"Ireland"}],[{"value":"Israel"}],[{"value":"Italy"}],[{"value":"Jamaica"}],[{"value":"Japan"}],[{"value":"Jordan"}],[{"value":"Kenya"}],[{"value":"Korea, Dem. Rep."}],[{"value":"Korea, Rep."}],[{"value":"Kuwait"}],[{"value":"Lebanon"}],[{"value":"Lesotho"}],[{"value":"Liberia"}],[{"value":"Libya"}],[{"value":"Madagascar"}],[{"value":"Malawi"}],[{"value":"Malaysia"}],[{"value":"Mali"}],[{"value":"Mauritania"}],[{"value":"Mauritius"}],[{"value":"Mexico"}],[{"value":"Mongolia"}],[{"value":"Montenegro"}],[{"value":"Morocco"}],[{"value":"Mozambique"}],[{"value":"Myanmar"}],[{"value":"Namibia"}],[{"value":"Nepal"}],[{"value":"Netherlands"}],[{"value":"New Zealand"}],[{"value":"Nicaragua"}],[{"value":"Niger"}],[{"value":"Nigeria"}],[{"value":"Norway"}],[{"value":"Oman"}],[{"value":"Pakistan"}],[{"value":"Panama"}],[{"value":"Paraguay"}]]}},"my_multi_select_table_source_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Sample Multi Select","labelPosition":"top","children":{"__dhObid":0}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/6b92d2a00b4d446f687c171939f4549c.json b/plugins/ui/docs/snapshots/6b92d2a00b4d446f687c171939f4549c.json new file mode 100644 index 000000000..ebc28d478 --- /dev/null +++ b/plugins/ui/docs/snapshots/6b92d2a00b4d446f687c171939f4549c.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_section_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Pick options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Section","props":{"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}}]}},{"__dhElemName":"deephaven.ui.components.Section","props":{"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 10"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 11"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 12"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 13"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 14"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 15"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 16"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/6d5a83c0b867d4737036ba0c7df3d06a.json b/plugins/ui/docs/snapshots/6d5a83c0b867d4737036ba0c7df3d06a.json new file mode 100644 index 000000000..462654c41 --- /dev/null +++ b/plugins/ui/docs/snapshots/6d5a83c0b867d4737036ba0c7df3d06a.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_validation_behaviour_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_validation_behaviour_example","props":{"children":{"__dhElemName":"deephaven.ui.components.Form","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","isRequired":true,"validationBehavior":"aria","label":"Pick options","labelPosition":"top","children":{"__dhElemName":"deephaven.ui.components.Section","props":{"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}}]}}}}],"validationBehavior":"aria","labelPosition":"top"}}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/8c0650bf02ecad7705b427d2ba3dcae9.json b/plugins/ui/docs/snapshots/8c0650bf02ecad7705b427d2ba3dcae9.json new file mode 100644 index 000000000..83a6df562 --- /dev/null +++ b/plugins/ui/docs/snapshots/8c0650bf02ecad7705b427d2ba3dcae9.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_events_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_events_example","props":{"children":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","inputValue":"","selectedKeys":[],"validationBehavior":"aria","label":"Pick options","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"onInputChange":{"__dhCbid":"cb1"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}}},"state":"{\"state\": {\"0\": \"\"}}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/8f831cd9142e5f4d10792383db92ffb8.json b/plugins/ui/docs/snapshots/8f831cd9142e5f4d10792383db92ffb8.json new file mode 100644 index 000000000..fec855f76 --- /dev/null +++ b/plugins/ui/docs/snapshots/8f831cd9142e5f4d10792383db92ffb8.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_is_read_only_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","defaultSelectedKeys":["Option 1","Option 3"],"isReadOnly":true,"validationBehavior":"aria","label":"Pick options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 1","children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 2","children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 3","children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 4","children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 5","children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 6","children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 7","children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 8","children":"Option 8"}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/94ab46014a1ec4241a826e7b08b163ef.json b/plugins/ui/docs/snapshots/94ab46014a1ec4241a826e7b08b163ef.json index cc1802ac2..243247a7b 100644 --- a/plugins/ui/docs/snapshots/94ab46014a1ec4241a826e7b08b163ef.json +++ b/plugins/ui/docs/snapshots/94ab46014a1ec4241a826e7b08b163ef.json @@ -1 +1 @@ -{"file":"components/combo_box.md","objects":{"my_combo_box_is_read_only_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","defaultSelectedKey":"Option 1","isReadOnly":true,"validationBehavior":"aria","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 1","children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 2","children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 3","children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 4","children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 5","children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 6","children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 7","children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 8","children":"Option 8"}}]},"__dhElemName":"deephaven.ui.components.ComboBox"},"state":"{}"}}}} \ No newline at end of file +{"file":"components/combo_box.md","objects":{"my_combo_box_is_read_only_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","defaultSelectedKey":"Option 1","isReadOnly":true,"validationBehavior":"aria","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 1","children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 2","children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 3","children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 4","children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 5","children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 6","children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 7","children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"key":"Option 8","children":"Option 8"}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/9bf473151e912a23a38ae067fbcd5d60.json b/plugins/ui/docs/snapshots/9bf473151e912a23a38ae067fbcd5d60.json new file mode 100644 index 000000000..88b320ef3 --- /dev/null +++ b/plugins/ui/docs/snapshots/9bf473151e912a23a38ae067fbcd5d60.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_basic":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_basic","props":{"children":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","selectedKeys":[],"validationBehavior":"aria","label":"Favorite Animals","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"red panda"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"cat"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"dog"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"aardvark"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"kangaroo"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"snake"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"ant"}}]}}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/b895d93116f94b87b7b2d8a4ced584ef.json b/plugins/ui/docs/snapshots/b895d93116f94b87b7b2d8a4ced584ef.json new file mode 100644 index 000000000..2c47abe8c --- /dev/null +++ b/plugins/ui/docs/snapshots/b895d93116f94b87b7b2d8a4ced584ef.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_required_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_required_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","isRequired":true,"validationBehavior":"aria","label":"Pick options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","isRequired":true,"validationBehavior":"aria","label":"Pick options","labelPosition":"top","necessityIndicator":"label","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Pick options","labelPosition":"top","necessityIndicator":"label","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/ba962573b1353e4b0230301ecf71f981.json b/plugins/ui/docs/snapshots/ba962573b1353e4b0230301ecf71f981.json new file mode 100644 index 000000000..662385143 --- /dev/null +++ b/plugins/ui/docs/snapshots/ba962573b1353e4b0230301ecf71f981.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_custom_value_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_custom_value_example","props":{"children":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","allowsCustomValue":true,"selectedKeys":[],"validationBehavior":"aria","label":"Select or type options","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}}]}}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/d90e47400a6a9800a0dcf18d62cfa277.json b/plugins/ui/docs/snapshots/d90e47400a6a9800a0dcf18d62cfa277.json new file mode 100644 index 000000000..cb3f90984 --- /dev/null +++ b/plugins/ui/docs/snapshots/d90e47400a6a9800a0dcf18d62cfa277.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_form_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_form_example","props":{"children":{"__dhElemName":"deephaven.ui.components.Form","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Ice cream flavors","name":"flavors","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Chocolate"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Mint"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Vanilla"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Strawberry"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Cookies and Cream"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Coffee"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Mango"}}]}},{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"submit","children":"Submit"}}],"validationBehavior":"aria","labelPosition":"top","onSubmit":{"__dhCbid":"cb0"}}}}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/f1891462b585b31dabf2e7d96395c968.json b/plugins/ui/docs/snapshots/f1891462b585b31dabf2e7d96395c968.json index 861e7edad..b9d3df24e 100644 --- a/plugins/ui/docs/snapshots/f1891462b585b31dabf2e7d96395c968.json +++ b/plugins/ui/docs/snapshots/f1891462b585b31dabf2e7d96395c968.json @@ -1 +1 @@ -{"file":"components/combo_box.md","objects":{"my_combo_box_control_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":[{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","inputValue":"","selectedKey":"","validationBehavior":"aria","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"onInputChange":{"__dhCbid":"cb1"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]},"__dhElemName":"__main__.ui_combo_box_control_example"},"state":"{\"state\": {\"0\": \"\", \"1\": \"\"}}"}}}} \ No newline at end of file +{"file":"components/combo_box.md","objects":{"my_combo_box_control_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_combo_box_control_example","props":{"children":[{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","inputValue":"","selectedKey":"","validationBehavior":"aria","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"onInputChange":{"__dhCbid":"cb1"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]}},"state":"{\"state\": {\"0\": \"\", \"1\": \"\"}}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/f51c43058b3f9e3badc91be0005f516b.json b/plugins/ui/docs/snapshots/f51c43058b3f9e3badc91be0005f516b.json new file mode 100644 index 000000000..cb697b537 --- /dev/null +++ b/plugins/ui/docs/snapshots/f51c43058b3f9e3badc91be0005f516b.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_trigger_option_examples":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_multi_select_trigger_option_examples","props":{"children":[{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Select Options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"focus","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Select Options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"manual","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Select Options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/fa4c431cba8e02a50fb02b9cbf6dd6de.json b/plugins/ui/docs/snapshots/fa4c431cba8e02a50fb02b9cbf6dd6de.json index 626eb4f37..f640bfcb1 100644 --- a/plugins/ui/docs/snapshots/fa4c431cba8e02a50fb02b9cbf6dd6de.json +++ b/plugins/ui/docs/snapshots/fa4c431cba8e02a50fb02b9cbf6dd6de.json @@ -1 +1 @@ -{"file":"components/combo_box.md","objects":{"my_combo_box_basic":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","selectedKey":"","validationBehavior":"aria","label":"Favorite Animal","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"red panda"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"cat"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"dog"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"aardvark"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"kangaroo"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"snake"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"ant"}}]}}},"__dhElemName":"__main__.ui_combo_box_basic"},"state":"{\"state\": {\"0\": \"\"}}"}}}} \ No newline at end of file +{"file":"components/combo_box.md","objects":{"my_combo_box_basic":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"__main__.ui_combo_box_basic","props":{"children":{"__dhElemName":"deephaven.ui.components.ComboBox","props":{"menuTrigger":"input","align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","selectedKey":"","validationBehavior":"aria","label":"Favorite Animal","labelPosition":"top","onChange":{"__dhCbid":"cb0"},"children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"red panda"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"cat"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"dog"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"aardvark"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"kangaroo"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"snake"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"ant"}}]}}}},"state":"{\"state\": {\"0\": \"\"}}"}}}} \ No newline at end of file diff --git a/plugins/ui/docs/snapshots/fe8c8adb28d909d50fb4fef51e2ab1c9.json b/plugins/ui/docs/snapshots/fe8c8adb28d909d50fb4fef51e2ab1c9.json new file mode 100644 index 000000000..af3e3b6bd --- /dev/null +++ b/plugins/ui/docs/snapshots/fe8c8adb28d909d50fb4fef51e2ab1c9.json @@ -0,0 +1 @@ +{"file":"components/multi_select.md","objects":{"my_multi_select_is_quiet_example":{"type":"deephaven.ui.Element","data":{"document":{"__dhElemName":"deephaven.ui.components.MultiSelect","props":{"menuTrigger":"input","isQuiet":true,"align":"end","direction":"bottom","shouldFlip":true,"formValue":"text","validationBehavior":"aria","label":"Pick options","labelPosition":"top","children":[{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 1"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 2"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 3"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 4"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 5"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 6"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 7"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 8"}},{"__dhElemName":"deephaven.ui.components.Item","props":{"children":"Option 9"}}]}},"state":"{}"}}}} \ No newline at end of file diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index 26f9eae1b..ed6a631a6 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -53,6 +53,7 @@ from .menu import menu from .menu_trigger import menu_trigger from .meter import meter +from .multi_select import multi_select from .number_field import number_field from .panel import panel from .picker import picker @@ -143,6 +144,7 @@ "menu", "menu_trigger", "meter", + "multi_select", "number_field", "panel", "picker", diff --git a/plugins/ui/src/deephaven/ui/components/multi_select.py b/plugins/ui/src/deephaven/ui/components/multi_select.py new file mode 100644 index 000000000..af494decc --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/multi_select.py @@ -0,0 +1,257 @@ +from __future__ import annotations + +from typing import Callable, Any, Sequence + +from .types import ( + FocusEventCallable, + KeyboardEventCallable, + LayoutFlex, + DimensionValue, + AlignSelf, + JustifySelf, + Position, + CSSProperties, + LabelPosition, + ValidationBehavior, + NecessityIndicator, + ValidationState, + MenuTriggerAction, + Align, + MenuDirection, + LoadingState, + FormValue, + Alignment, +) + +from deephaven.table import Table, PartitionedTable +from .section import SectionElement +from .item import Item +from .item_table_source import ItemTableSource +from ..elements import BaseElement, Element, NodeType +from .._internal.utils import create_props, unpack_item_table_source +from ..types import Key, Selection +from .basic import component_element + + +MultiSelectElement = BaseElement + +SUPPORTED_SOURCE_ARGS = { + "key_column", + "label_column", + "description_column", + "icon_column", + "title_column", +} + + +def multi_select( + *children: Item | SectionElement | Table | PartitionedTable | ItemTableSource, + menu_trigger: MenuTriggerAction | None = "input", + is_quiet: bool | None = None, + align: Align | None = "end", + direction: MenuDirection | None = "bottom", + loading_state: LoadingState | None = None, + should_flip: bool = True, + menu_width: DimensionValue | None = None, + form_value: FormValue | None = "text", + should_focus_wrap: bool | None = None, + input_value: str | None = None, + default_input_value: str | None = None, + allows_custom_value: bool | None = None, + disabled_keys: list[Key] | None = None, + selected_keys: Selection | None = None, + default_selected_keys: Selection | None = None, + is_disabled: bool | None = None, + is_read_only: bool | None = None, + is_required: bool | None = None, + validation_behavior: ValidationBehavior = "aria", + auto_focus: bool | None = None, + label: NodeType = None, + description: Element | None = None, + error_message: Element | None = None, + name: str | None = None, + validation_state: ValidationState | None = None, + label_position: LabelPosition = "top", + label_align: Alignment | None = None, + necessity_indicator: NecessityIndicator | None = None, + contextual_help: Element | None = None, + on_open_change: Callable[[bool, MenuTriggerAction], None] | None = None, + on_selection_change: Callable[[Selection], None] | None = None, + on_change: Callable[[Selection], None] | None = None, + on_input_change: Callable[[str], None] | None = None, + on_focus: Callable[[FocusEventCallable], None] | None = None, + on_blur: Callable[[FocusEventCallable], None] | None = None, + on_focus_change: Callable[[bool], None] | None = None, + on_key_down: Callable[[KeyboardEventCallable], None] | None = None, + on_key_up: Callable[[KeyboardEventCallable], None] | None = None, + flex: LayoutFlex | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, + flex_basis: DimensionValue | None = None, + align_self: AlignSelf | None = None, + justify_self: JustifySelf | None = None, + order: int | None = None, + grid_area: str | None = None, + grid_row: str | None = None, + grid_row_start: str | None = None, + grid_row_end: str | None = None, + grid_column: str | None = None, + grid_column_start: str | None = None, + grid_column_end: str | None = None, + margin: DimensionValue | None = None, + margin_top: DimensionValue | None = None, + margin_bottom: DimensionValue | None = None, + margin_start: DimensionValue | None = None, + margin_end: DimensionValue | None = None, + margin_x: DimensionValue | None = None, + margin_y: DimensionValue | None = None, + width: DimensionValue | None = None, + height: DimensionValue | None = None, + min_width: DimensionValue | None = None, + min_height: DimensionValue | None = None, + max_width: DimensionValue | None = None, + max_height: DimensionValue | None = None, + position: Position | None = None, + top: DimensionValue | None = None, + bottom: DimensionValue | None = None, + start: DimensionValue | None = None, + end: DimensionValue | None = None, + left: DimensionValue | None = None, + right: DimensionValue | None = None, + z_index: int | None = None, + is_hidden: bool | None = None, + id: str | None = None, + aria_label: str | None = None, + aria_labelledby: str | None = None, + aria_describedby: str | None = None, + aria_details: str | None = None, + UNSAFE_class_name: str | None = None, + UNSAFE_style: CSSProperties | None = None, + key: str | None = None, +) -> MultiSelectElement: + """ + A multi-select component that displays selected items as tags inside the input area + and presents a filterable dropdown list for multi-selection. + + Children should be one of five types: + + 1. If children are of type `Item`, they are the dropdown options. + 2. If children are of type `SectionElement`, they are the dropdown sections. + 3. If children are of type `Table`, the values in the table are the dropdown options. + There can only be one child, the `Table`. + The first column is used as the key and label by default. + 4. If children are of type `PartitionedTable`, the values in the table are the dropdown options + and the partitions create multiple sections. There can only be one child, the `PartitionedTable`. + The first column is used as the key and label by default. + 5. If children are of type `ItemTableSource`, complex items are created from the source. + There can only be one child, the `ItemTableSource`. + Supported ItemTableSource arguments are `key_column`, `label_column`, `description_column`, + `icon_column`, and `title_column`. + + Args: + *children: The options to render within the multi-select. + menu_trigger: The interaction required to display the menu. + is_quiet: Whether the component should be displayed with a quiet style. + align: Alignment of the menu relative to the input target. + direction: Direction the menu will render relative to the component. + loading_state: The current loading state. + Determines whether or not the progress circle should be shown. + should_flip: Whether the menu should automatically flip direction when space is limited. + menu_width: Width of the menu. By default, matches width of the component. + Note that the minimum width of the dropdown is always equal to the component's width. + form_value: Whether the text or key of the selected items is submitted as part of an HTML form. + Controls whether comma-joined keys or labels are submitted via the hidden form input. + should_focus_wrap: Whether keyboard navigation is circular. + input_value: The value of the search input (controlled). + default_input_value: The default value of the search input (uncontrolled). + allows_custom_value: Whether the component allows a non-item matching input value to be set. + Pressing Enter when no item is focused adds the typed text as a custom tag. + If the typed text matches an existing item's label, that item's key is used instead. + disabled_keys: The item keys that are disabled. + These items cannot be selected, focused, or otherwise interacted with. + selected_keys: The currently selected keys in the collection (controlled). + default_selected_keys: The initial selected keys in the collection (uncontrolled). + is_disabled: Whether the input is disabled. + is_read_only: Whether the input can be selected but not changed by the user. + is_required: Whether user input is required on the input before form submission. + validation_behavior: Whether to use native HTML form validation to prevent + form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA. + auto_focus: Whether the element should receive focus on render. + label: The content to display as the label. + description: A description for the field. Provides a hint such as specific requirements for what to choose. + error_message: An error message for the field. + name: The name of the input element, used when submitting an HTML form. + validation_state: Whether the input should display its "valid" or "invalid" visual styling. + label_position: The label's overall position relative to the element it is labeling. + label_align: The label's horizontal alignment relative to the element it is labeling. + necessity_indicator: Whether the required state should be shown as an icon or text. + contextual_help: A ContextualHelp element to place next to the label. + on_open_change: Method that is called when the open state of the menu changes. + Returns the new open state and the action that caused the opening of the menu. + on_selection_change: Handler that is called when the selection changes. + Receives a `Selection` (list of keys). + on_change: Alias of `on_selection_change`. Handler that is called when the selection changes. + Receives a `Selection` (list of keys). + on_input_change: Handler that is called when the input value changes. + on_focus: Handler that is called when the element receives focus. + on_blur: Handler that is called when the element loses focus. + on_focus_change: Handler that is called when the element's focus status changes. + on_key_down: Handler that is called when a key is pressed. + on_key_up: Handler that is called when a key is released. + flex: When used in a flex layout, specifies how the element will grow or shrink to fit the space available. + flex_grow: When used in a flex layout, specifies how much the element will grow to fit the space available. + flex_shrink: When used in a flex layout, specifies how much the element will shrink to fit the space available. + flex_basis: When used in a flex layout, specifies the initial size of the element. + align_self: Overrides the align_items property of a flex or grid container. + justify_self: Specifies how the element is justified inside a flex or grid container. + order: The layout order for the element within a flex or grid container. + grid_area: The name of the grid area to place the element in. + grid_row: The name of the grid row to place the element in. + grid_row_start: The name of the grid row to start the element in. + grid_row_end: The name of the grid row to end the element in. + grid_column: The name of the grid column to place the element in. + grid_column_start: The name of the grid column to start the element in. + grid_column_end: The name of the grid column to end the element in. + margin: The margin to apply around the element. + margin_top: The margin to apply above the element. + margin_bottom: The margin to apply below the element. + margin_start: The margin to apply before the element. + margin_end: The margin to apply after the element. + margin_x: The margin to apply to the left and right of the element. + margin_y: The margin to apply to the top and bottom of the element. + width: The width of the element. + height: The height of the element. + min_width: The minimum width of the element. + min_height: The minimum height of the element. + max_width: The maximum width of the element. + max_height: The maximum height of the element. + position: Specifies how the element is positioned. + top: The distance from the top of the containing element. + bottom: The distance from the bottom of the containing element. + start: The distance from the start of the containing element. + end: The distance from the end of the containing element. + left: The distance from the left of the containing element. + right: The distance from the right of the containing element. + z_index: The stack order of the element. + is_hidden: Whether the element is hidden. + id: A unique identifier for the element. + aria_label: The label for the element. + aria_labelledby: The id of the element that labels the element. + aria_describedby: The id of the element that describes the element. + aria_details: The details for the element. + UNSAFE_class_name: A CSS class to apply to the element. + UNSAFE_style: A CSS style to apply to the element. + key: A unique identifier used by React to render elements in a list. + + Returns: + The rendered MultiSelect. + """ + children, props = create_props(locals()) + + children, props = unpack_item_table_source(children, props, SUPPORTED_SOURCE_ARGS) + + return component_element( + "MultiSelect", + *children, + **props, + ) diff --git a/plugins/ui/src/js/package.json b/plugins/ui/src/js/package.json index 28e8a1f5e..230e04ecc 100644 --- a/plugins/ui/src/js/package.json +++ b/plugins/ui/src/js/package.json @@ -40,25 +40,25 @@ "react-dom": "^18.0.0 || ^19.0.0" }, "dependencies": { - "@deephaven/chart": "^1.17.0", - "@deephaven/components": "^1.17.0", - "@deephaven/console": "^1.17.0", - "@deephaven/dashboard": "^1.17.1", - "@deephaven/dashboard-core-plugins": "^1.18.0", - "@deephaven/golden-layout": "^1.17.1", - "@deephaven/grid": "^1.18.0", + "@deephaven/chart": "^1.21.0", + "@deephaven/components": "^1.21.0", + "@deephaven/console": "^1.21.0", + "@deephaven/dashboard": "^1.21.0", + "@deephaven/dashboard-core-plugins": "^1.21.0", + "@deephaven/golden-layout": "^1.21.0", + "@deephaven/grid": "^1.21.0", "@deephaven/icons": "^1.2.0", - "@deephaven/iris-grid": "^1.18.0", - "@deephaven/jsapi-bootstrap": "^1.17.0", - "@deephaven/jsapi-components": "^1.17.0", + "@deephaven/iris-grid": "^1.21.0", + "@deephaven/jsapi-bootstrap": "^1.21.0", + "@deephaven/jsapi-components": "^1.21.0", "@deephaven/jsapi-types": "^1.0.0-dev0.39.6", - "@deephaven/jsapi-utils": "^1.16.0", + "@deephaven/jsapi-utils": "^1.21.0", "@deephaven/log": "^1.8.0", - "@deephaven/plugin": "^1.18.0", - "@deephaven/react-hooks": "^1.14.0", - "@deephaven/redux": "^1.17.0", + "@deephaven/plugin": "^1.21.0", + "@deephaven/react-hooks": "^1.21.0", + "@deephaven/redux": "^1.21.0", "@deephaven/test-utils": "^1.8.0", - "@deephaven/utils": "^1.10.0", + "@deephaven/utils": "^1.21.0", "@fortawesome/react-fontawesome": "^0.2.0", "@internationalized/date": "^3.5.5", "classnames": "^2.5.1", diff --git a/plugins/ui/src/js/src/elements/ComboBox.tsx b/plugins/ui/src/js/src/elements/ComboBox.tsx index a322f6fa0..0db613082 100644 --- a/plugins/ui/src/js/src/elements/ComboBox.tsx +++ b/plugins/ui/src/js/src/elements/ComboBox.tsx @@ -48,17 +48,13 @@ export function ComboBox( {...pickerProps} errorMessage={message} validationState="invalid" - > - {[]} - + /> ); } if (isLoading || table == null || api == null) { return ( // eslint-disable-next-line react/jsx-props-no-spreading - - {[]} - + ); } return ( diff --git a/plugins/ui/src/js/src/elements/ListView.tsx b/plugins/ui/src/js/src/elements/ListView.tsx index 44741c92f..68e39f471 100644 --- a/plugins/ui/src/js/src/elements/ListView.tsx +++ b/plugins/ui/src/js/src/elements/ListView.tsx @@ -38,9 +38,7 @@ export function ListView(props: SerializedListViewProps): JSX.Element | null { // eslint-disable-next-line react/jsx-props-no-spreading {...listViewProps} renderEmptyState={() => } - > - {[]} - + /> ); } if (isLoading || table == null || api == null) { @@ -49,9 +47,7 @@ export function ListView(props: SerializedListViewProps): JSX.Element | null { // eslint-disable-next-line react/jsx-props-no-spreading {...listViewProps} loadingState="loading" - > - {[]} - + /> ); } return ( diff --git a/plugins/ui/src/js/src/elements/MultiSelect.test.tsx b/plugins/ui/src/js/src/elements/MultiSelect.test.tsx new file mode 100644 index 000000000..7018b8a0c --- /dev/null +++ b/plugins/ui/src/js/src/elements/MultiSelect.test.tsx @@ -0,0 +1,169 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { MultiSelect } from './MultiSelect'; +import type { SerializedMultiSelectProps } from './hooks/useMultiSelectProps'; + +// Mock ObjectView and UriObjectView before they trigger deep dependency chains +jest.mock('./ObjectView', () => jest.fn(() => null)); +jest.mock('./UriObjectView', () => jest.fn(() => null)); +jest.mock('../widget/WidgetErrorUtils', () => ({ + getErrorShortMessage: jest.fn((e: Error) => e.message), +})); + +// Mock all heavy dependencies +jest.mock('react-redux', () => ({ + useSelector: jest.fn(() => ({})), +})); + +jest.mock('./hooks/useMultiSelectProps', () => ({ + useMultiSelectProps: jest.fn((props: Record) => { + const { + onChange, + onSelectionChange, + onFocus, + onBlur, + onKeyDown, + onKeyUp, + ...rest + } = props; + return rest; + }), +})); + +jest.mock('./hooks/useObjectViewObject', () => ({ + useObjectViewObject: jest.fn(() => ({ + widget: null, + api: null, + isLoading: false, + error: null, + })), +})); + +jest.mock('@deephaven/components', () => ({ + MultiSelect: jest.fn( + ({ children }: { children?: React.ReactNode; [key: string]: unknown }) => ( +
{children}
+ ) + ), +})); + +jest.mock('@deephaven/jsapi-components', () => ({ + MultiSelect: jest.fn(() =>
), +})); + +jest.mock('@deephaven/react-hooks', () => ({ + isElementOfType: jest.fn(() => false), +})); + +jest.mock('@deephaven/jsapi-bootstrap', () => ({ + ApiContext: { + Provider: ({ children }: { children: React.ReactNode }) => children, + }, +})); + +jest.mock('@deephaven/redux', () => ({ + getSettings: jest.fn(() => ({})), +})); + +describe('MultiSelect', () => { + it('renders DHMultiSelect with children when not an ObjectView', () => { + const props = { + children: ['Option A', 'Option B'], + label: 'Test', + } as unknown as SerializedMultiSelectProps; + + const { getByTestId } = render( + + ); + expect(getByTestId('dh-multi-select')).toBeTruthy(); + }); + + it('renders loading state when ObjectView with no table', () => { + const { isElementOfType } = jest.requireMock('@deephaven/react-hooks'); + isElementOfType.mockReturnValue(true); + + const { useObjectViewObject } = jest.requireMock( + './hooks/useObjectViewObject' + ); + useObjectViewObject.mockReturnValue({ + widget: null, + api: null, + isLoading: true, + error: null, + }); + + const props = { + children: React.createElement('div'), + label: 'Loading test', + } as unknown as SerializedMultiSelectProps; + + const { getByTestId } = render( + + ); + const el = getByTestId('dh-multi-select'); + expect(el).toBeTruthy(); + }); + + it('renders error state when ObjectView has error', () => { + const { isElementOfType } = jest.requireMock('@deephaven/react-hooks'); + isElementOfType.mockReturnValue(true); + + const { useObjectViewObject } = jest.requireMock( + './hooks/useObjectViewObject' + ); + useObjectViewObject.mockReturnValue({ + widget: null, + api: null, + isLoading: false, + error: new Error('Test error'), + }); + + const props = { + children: React.createElement('div'), + label: 'Error test', + } as unknown as SerializedMultiSelectProps; + + const { getByTestId } = render( + + ); + const el = getByTestId('dh-multi-select'); + expect(el).toBeTruthy(); + }); + + it('renders JSApi MultiSelect when ObjectView has table and api', () => { + const { isElementOfType } = jest.requireMock('@deephaven/react-hooks'); + isElementOfType.mockReturnValue(true); + + const { useObjectViewObject } = jest.requireMock( + './hooks/useObjectViewObject' + ); + useObjectViewObject.mockReturnValue({ + widget: {}, + api: {}, + isLoading: false, + error: null, + }); + + const props = { + children: React.createElement('div'), + label: 'JSApi test', + } as unknown as SerializedMultiSelectProps; + + const { getByTestId } = render( + + ); + expect(getByTestId('dh-multi-select-jsapi')).toBeTruthy(); + }); +}); diff --git a/plugins/ui/src/js/src/elements/MultiSelect.tsx b/plugins/ui/src/js/src/elements/MultiSelect.tsx new file mode 100644 index 000000000..b89bdec42 --- /dev/null +++ b/plugins/ui/src/js/src/elements/MultiSelect.tsx @@ -0,0 +1,67 @@ +import { useSelector } from 'react-redux'; +import { MultiSelect as DHMultiSelect } from '@deephaven/components'; +import { MultiSelect as DHMultiSelectJSApi } from '@deephaven/jsapi-components'; +import { isElementOfType } from '@deephaven/react-hooks'; +import type { dh } from '@deephaven/jsapi-types'; +import { ApiContext } from '@deephaven/jsapi-bootstrap'; +import { getSettings, type RootState } from '@deephaven/redux'; +import { + type SerializedMultiSelectProps, + useMultiSelectProps, +} from './hooks/useMultiSelectProps'; +import ObjectView from './ObjectView'; +import { useObjectViewObject } from './hooks/useObjectViewObject'; +import UriObjectView from './UriObjectView'; +import { getErrorShortMessage } from '../widget/WidgetErrorUtils'; + +export function MultiSelect( + props: SerializedMultiSelectProps +): JSX.Element | null { + const settings = useSelector(getSettings); + const { children, ...multiSelectProps } = useMultiSelectProps(props); + + const isObjectView = + isElementOfType(children, ObjectView) || + isElementOfType(children, UriObjectView); + const { widget: table, api, error } = useObjectViewObject(children); + + if (isObjectView) { + if (error != null) { + const message = getErrorShortMessage(error); + return ( + + ); + } + // Don't gate on `isLoading` as it flips true on server round-trips and + // would unmount/remount the spectrum MultiSelect, closing any open + // popover. + if (table == null || api == null) { + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ); + } + return ( + + + + ); + } + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + {children} + ); +} + +export default MultiSelect; diff --git a/plugins/ui/src/js/src/elements/Picker.tsx b/plugins/ui/src/js/src/elements/Picker.tsx index 59e5ce501..9998c07d0 100644 --- a/plugins/ui/src/js/src/elements/Picker.tsx +++ b/plugins/ui/src/js/src/elements/Picker.tsx @@ -44,17 +44,13 @@ export function Picker( const message = getErrorShortMessage(error); return ( // eslint-disable-next-line react/jsx-props-no-spreading - - {[]} - + ); } if (isLoading || table == null || api == null) { return ( // eslint-disable-next-line react/jsx-props-no-spreading - - {[]} - + ); } return ( diff --git a/plugins/ui/src/js/src/elements/hooks/useMultiSelectProps.test.ts b/plugins/ui/src/js/src/elements/hooks/useMultiSelectProps.test.ts new file mode 100644 index 000000000..54c1540b9 --- /dev/null +++ b/plugins/ui/src/js/src/elements/hooks/useMultiSelectProps.test.ts @@ -0,0 +1,127 @@ +import { renderHook, act } from '@testing-library/react'; +import { useMultiSelectProps } from './useMultiSelectProps'; +import type { SerializedMultiSelectProps } from './useMultiSelectProps'; + +describe('useMultiSelectProps', () => { + it('passes through other props unchanged', () => { + const props = { + label: 'Test Label', + isDisabled: true, + selectedKeys: ['a', 'b'], + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + expect(result.current).toMatchObject({ + label: 'Test Label', + isDisabled: true, + selectedKeys: ['a', 'b'], + }); + }); + + it('deserializes onChange into a function', () => { + const onChange = jest.fn(); + const props = { + onChange, + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + expect(result.current.onChange).toBeDefined(); + expect(typeof result.current.onChange).toBe('function'); + }); + + it('deserializes onSelectionChange into a function', () => { + const onSelectionChange = jest.fn(); + const props = { + onSelectionChange, + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + expect(result.current.onSelectionChange).toBeDefined(); + expect(typeof result.current.onSelectionChange).toBe('function'); + }); + + it('serializes Set selection to array when onChange fires', () => { + const onChange = jest.fn(); + const props = { + onChange, + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + act(() => { + result.current.onChange?.(new Set(['a', 'b'])); + }); + + expect(onChange).toHaveBeenCalledWith(['a', 'b']); + }); + + it('serializes Set selection to array when onSelectionChange fires', () => { + const onSelectionChange = jest.fn(); + const props = { + onSelectionChange, + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + act(() => { + result.current.onSelectionChange?.(new Set(['x', 'y'])); + }); + + expect(onSelectionChange).toHaveBeenCalledWith(['x', 'y']); + }); + + it('passes "all" selection through unchanged', () => { + const onChange = jest.fn(); + const props = { + onChange, + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + act(() => { + result.current.onChange?.('all'); + }); + + expect(onChange).toHaveBeenCalledWith('all'); + }); + + it('deserializes focus and blur callbacks', () => { + const onFocus = jest.fn(); + const onBlur = jest.fn(); + const props = { + onFocus, + onBlur, + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + expect(result.current.onFocus).toBeDefined(); + expect(result.current.onBlur).toBeDefined(); + }); + + it('deserializes keyboard callbacks', () => { + const onKeyDown = jest.fn(); + const onKeyUp = jest.fn(); + const props = { + onKeyDown, + onKeyUp, + } as unknown as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + expect(result.current.onKeyDown).toBeDefined(); + expect(result.current.onKeyUp).toBeDefined(); + }); + + it('returns undefined for omitted callbacks', () => { + const props = {} as SerializedMultiSelectProps; + + const { result } = renderHook(() => useMultiSelectProps(props)); + + expect(result.current.onChange).toBeUndefined(); + expect(result.current.onSelectionChange).toBeUndefined(); + }); +}); diff --git a/plugins/ui/src/js/src/elements/hooks/useMultiSelectProps.ts b/plugins/ui/src/js/src/elements/hooks/useMultiSelectProps.ts new file mode 100644 index 000000000..62119083d --- /dev/null +++ b/plugins/ui/src/js/src/elements/hooks/useMultiSelectProps.ts @@ -0,0 +1,63 @@ +import type { MultiSelectProps as DHMultiSelectProps } from '@deephaven/components'; +import type { MultiSelectProps as DHMultiSelectJSApiProps } from '@deephaven/jsapi-components'; +import { + type SerializedSelectionProps, + useSelectionProps, +} from './useSelectionProps'; +import type { + SerializedPickerEventProps, + WrappedDHPickerJSApiProps, +} from './usePickerProps'; +import { useFocusEventCallback } from './useFocusEventCallback'; +import { useKeyboardEventCallback } from './useKeyboardEventCallback'; + +type WrappedDHMultiSelectJSApiProps = + WrappedDHPickerJSApiProps; + +export type SerializedMultiSelectProps = ( + | DHMultiSelectProps + | WrappedDHMultiSelectJSApiProps +) & + SerializedSelectionProps & + SerializedPickerEventProps; + +/** + * Wrap MultiSelect props with the appropriate serialized event callbacks. + * @param props Props to wrap + * @returns Wrapped props + */ +export function useMultiSelectProps({ + onChange: serializedOnChange, + onSelectionChange: serializedOnSelectionChange, + onFocus, + onBlur, + onKeyDown, + onKeyUp, + ...otherProps +}: SerializedMultiSelectProps): + | DHMultiSelectProps + | WrappedDHMultiSelectJSApiProps { + const { onChange, onSelectionChange } = useSelectionProps({ + onChange: serializedOnChange, + onSelectionChange: serializedOnSelectionChange, + }); + + const deserializedOnFocus = useFocusEventCallback(onFocus); + const deserializedOnBlur = useFocusEventCallback(onBlur); + const deserializedOnKeyDown = useKeyboardEventCallback(onKeyDown); + const deserializedOnKeyUp = useKeyboardEventCallback(onKeyUp); + + return { + onChange, + onSelectionChange, + onFocus: deserializedOnFocus, + onBlur: deserializedOnBlur, + onKeyDown: deserializedOnKeyDown, + onKeyUp: deserializedOnKeyUp, + // The @deephaven/components `MultiSelect` has its own normalization logic + // that handles primitive children types (string, number, boolean). It also + // handles nested children inside of `Item` and `Section` components, so + // we are intentionally not wrapping `otherProps` in `mapSpectrumProps` + ...otherProps, + }; +} diff --git a/plugins/ui/src/js/src/elements/index.ts b/plugins/ui/src/js/src/elements/index.ts index ecb7ad07c..dcd60ceae 100644 --- a/plugins/ui/src/js/src/elements/index.ts +++ b/plugins/ui/src/js/src/elements/index.ts @@ -29,6 +29,7 @@ export * from './LogicButton'; export * from './Markdown'; export * from './Menu'; export * from './Meter'; +export * from './MultiSelect'; export * from './model'; export * from './ObjectView'; export * from './Picker'; diff --git a/plugins/ui/src/js/src/elements/model/ElementConstants.ts b/plugins/ui/src/js/src/elements/model/ElementConstants.ts index 928da2331..c8447b82b 100644 --- a/plugins/ui/src/js/src/elements/model/ElementConstants.ts +++ b/plugins/ui/src/js/src/elements/model/ElementConstants.ts @@ -71,6 +71,7 @@ export const ELEMENT_NAME = { menu: uiComponentName('Menu'), menuTrigger: uiComponentName('MenuTrigger'), meter: uiComponentName('Meter'), + multiSelect: uiComponentName('MultiSelect'), numberField: uiComponentName('NumberField'), picker: uiComponentName('Picker'), progressBar: uiComponentName('ProgressBar'), diff --git a/plugins/ui/src/js/src/widget/WidgetUtils.tsx b/plugins/ui/src/js/src/widget/WidgetUtils.tsx index 136b32f30..2172f0025 100644 --- a/plugins/ui/src/js/src/widget/WidgetUtils.tsx +++ b/plugins/ui/src/js/src/widget/WidgetUtils.tsx @@ -87,6 +87,7 @@ import { Markdown, Menu, Meter, + MultiSelect, Picker, ProgressBar, ProgressCircle, @@ -186,6 +187,7 @@ export const elementComponentMap: Record, unknown> = { [ELEMENT_NAME.menu]: Menu, [ELEMENT_NAME.menuTrigger]: MenuTrigger, [ELEMENT_NAME.meter]: Meter, + [ELEMENT_NAME.multiSelect]: MultiSelect, [ELEMENT_NAME.numberField]: NumberField, [ELEMENT_NAME.picker]: Picker, [ELEMENT_NAME.progressBar]: ProgressBar, diff --git a/plugins/ui/test/deephaven/ui/test_combo_box.py b/plugins/ui/test/deephaven/ui/test_combo_box.py new file mode 100644 index 000000000..9efa6db91 --- /dev/null +++ b/plugins/ui/test/deephaven/ui/test_combo_box.py @@ -0,0 +1,35 @@ +import unittest + +from .BaseTest import BaseTestCase + + +class ComboBoxTest(BaseTestCase): + def test_renders_combo_box(self): + from deephaven.ui import combo_box + + result = combo_box(label="Test") + self.assertEqual(result.name, "deephaven.ui.components.ComboBox") + + def test_selected_key(self): + from deephaven.ui import combo_box + + result = combo_box(selected_key="a", label="Test") + self.assertEqual(result.name, "deephaven.ui.components.ComboBox") + + +class MultiSelectTest(BaseTestCase): + def test_renders_multi_select(self): + from deephaven.ui import multi_select + + result = multi_select(label="Test") + self.assertEqual(result.name, "deephaven.ui.components.MultiSelect") + + def test_accepts_selected_keys(self): + from deephaven.ui import multi_select + + result = multi_select(selected_keys=["a", "b"], label="Test") + self.assertEqual(result.name, "deephaven.ui.components.MultiSelect") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/app.d/tests.app b/tests/app.d/tests.app index a508c3393..f10ec43d8 100644 --- a/tests/app.d/tests.app +++ b/tests/app.d/tests.app @@ -19,4 +19,6 @@ file_12=theme_demo.py file_13=ui_nested_dashboard.py file_14=ui_query_params.py file_15=ui_home_screen.py -file_16=ui_routing.py \ No newline at end of file +file_16=ui_routing.py +file_17=ui_combo_box.py +file_18=ui_multi_select.py \ No newline at end of file diff --git a/tests/app.d/ui_combo_box.py b/tests/app.d/ui_combo_box.py new file mode 100644 index 000000000..3e583c048 --- /dev/null +++ b/tests/app.d/ui_combo_box.py @@ -0,0 +1,38 @@ +from deephaven import ui + + +@ui.component +def ui_combo_box_basic(): + value, set_value = ui.use_state(None) + return ui.flex( + ui.combo_box( + "Option A", + "Option B", + "Option C", + label="Select an option", + on_change=set_value, + ), + ui.text(f"Selected: {value}"), + direction="column", + ) + + +@ui.component +def ui_combo_box_controlled(): + value, set_value = ui.use_state("Option B") + return ui.flex( + ui.combo_box( + "Option A", + "Option B", + "Option C", + label="Controlled", + selected_key=value, + on_change=set_value, + ), + ui.text(f"Selected: {value}"), + direction="column", + ) + + +cb_basic = ui_combo_box_basic() +cb_controlled = ui_combo_box_controlled() diff --git a/tests/app.d/ui_multi_select.py b/tests/app.d/ui_multi_select.py new file mode 100644 index 000000000..70f883619 --- /dev/null +++ b/tests/app.d/ui_multi_select.py @@ -0,0 +1,40 @@ +from deephaven import ui + + +@ui.component +def ui_multi_select_basic(): + values, set_values = ui.use_state(None) + return ui.flex( + ui.multi_select( + "Option A", + "Option B", + "Option C", + "Option D", + label="Select options", + on_change=set_values, + ), + ui.text(f"Count: {len(values) if values else 0}"), + direction="column", + ) + + +@ui.component +def ui_multi_select_controlled(): + values, set_values = ui.use_state(["Option A", "Option C"]) + return ui.flex( + ui.multi_select( + "Option A", + "Option B", + "Option C", + "Option D", + label="Controlled", + selected_keys=values, + on_change=set_values, + ), + ui.text(f"Count: {len(values) if values else 0}"), + direction="column", + ) + + +ms_basic = ui_multi_select_basic() +ms_controlled = ui_multi_select_controlled() diff --git a/tests/ui_combo_box.spec.ts b/tests/ui_combo_box.spec.ts new file mode 100644 index 000000000..852beeef9 --- /dev/null +++ b/tests/ui_combo_box.spec.ts @@ -0,0 +1,39 @@ +import { expect, test } from '@playwright/test'; +import { openPanel, gotoPage, SELECTORS } from './utils'; + +test.describe('UI combo_box', () => { + test('renders basic combo box', async ({ page }) => { + await gotoPage(page, ''); + await openPanel(page, 'cb_basic', SELECTORS.REACT_PANEL_VISIBLE); + + const panel = page.locator(SELECTORS.REACT_PANEL_VISIBLE); + await expect(panel.getByText('Selected: None')).toBeVisible(); + await expect(panel).toHaveScreenshot(); + }); + + test('renders controlled combo box with initial value', async ({ page }) => { + await gotoPage(page, ''); + await openPanel(page, 'cb_controlled', SELECTORS.REACT_PANEL_VISIBLE); + + const panel = page.locator(SELECTORS.REACT_PANEL_VISIBLE); + await expect(panel.getByText('Selected: Option B')).toBeVisible(); + }); + + test('selects an option', async ({ page }) => { + await gotoPage(page, ''); + await openPanel(page, 'cb_basic', SELECTORS.REACT_PANEL_VISIBLE); + + const panel = page.locator(SELECTORS.REACT_PANEL_VISIBLE); + + // Click the combo box trigger button to open the dropdown + await panel.getByRole('button', { name: 'Show suggestions' }).click(); + + // Wait for the listbox to appear and select an option + const listbox = page.getByRole('listbox'); + await expect(listbox).toBeVisible(); + await listbox.getByRole('option', { name: 'Option A' }).click(); + + // Verify the selection was applied + await expect(panel.getByText('Selected: Option A')).toBeVisible(); + }); +}); diff --git a/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-chromium-linux.png b/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-chromium-linux.png new file mode 100644 index 000000000..250ab645e Binary files /dev/null and b/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-chromium-linux.png differ diff --git a/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-firefox-linux.png b/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-firefox-linux.png new file mode 100644 index 000000000..53c5b5623 Binary files /dev/null and b/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-firefox-linux.png differ diff --git a/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-webkit-linux.png b/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-webkit-linux.png new file mode 100644 index 000000000..55f8503a7 Binary files /dev/null and b/tests/ui_combo_box.spec.ts-snapshots/UI-combo-box-renders-basic-combo-box-1-webkit-linux.png differ diff --git a/tests/ui_multi_select.spec.ts b/tests/ui_multi_select.spec.ts new file mode 100644 index 000000000..dd75c6252 --- /dev/null +++ b/tests/ui_multi_select.spec.ts @@ -0,0 +1,22 @@ +import { expect, test } from '@playwright/test'; +import { openPanel, gotoPage, SELECTORS } from './utils'; + +test.describe('UI multi_select', () => { + test('renders basic multi select', async ({ page }) => { + await gotoPage(page, ''); + await openPanel(page, 'ms_basic', SELECTORS.REACT_PANEL_VISIBLE); + + const panel = page.locator(SELECTORS.REACT_PANEL_VISIBLE); + await expect(panel).toHaveScreenshot(); + }); + + test('renders controlled multi select with initial values', async ({ + page, + }) => { + await gotoPage(page, ''); + await openPanel(page, 'ms_controlled', SELECTORS.REACT_PANEL_VISIBLE); + + const panel = page.locator(SELECTORS.REACT_PANEL_VISIBLE); + await expect(panel).toHaveScreenshot(); + }); +}); diff --git a/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-chromium-linux.png b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-chromium-linux.png new file mode 100644 index 000000000..247b14e67 Binary files /dev/null and b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-chromium-linux.png differ diff --git a/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-firefox-linux.png b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-firefox-linux.png new file mode 100644 index 000000000..d9bbb095a Binary files /dev/null and b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-firefox-linux.png differ diff --git a/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-webkit-linux.png b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-webkit-linux.png new file mode 100644 index 000000000..45c082e8f Binary files /dev/null and b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-basic-multi-select-1-webkit-linux.png differ diff --git a/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-chromium-linux.png b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-chromium-linux.png new file mode 100644 index 000000000..247b14e67 Binary files /dev/null and b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-chromium-linux.png differ diff --git a/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-firefox-linux.png b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-firefox-linux.png new file mode 100644 index 000000000..d9bbb095a Binary files /dev/null and b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-firefox-linux.png differ diff --git a/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-webkit-linux.png b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-webkit-linux.png new file mode 100644 index 000000000..45c082e8f Binary files /dev/null and b/tests/ui_multi_select.spec.ts-snapshots/UI-multi-select-renders-controlled-multi-select-with-initial-values-1-webkit-linux.png differ