diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index 232dab0f5..910605d54 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -1051,23 +1051,100 @@ ui.section( | `title` | `str \| None` | The title of the section. | | `**props` | `Any` | Any other Section prop | -##### ui.picker +###### ui.list_action_group +A group of action buttons that can be used to create a list of actions. +This component should be used within the actions prop of a `ListView` component. + +```py +def list_action_group( + *children: ActionGroupItem, + on_action: Callable[[ActionKey, Key], None] | None = None, + on_selection_change: Callable[[Selection, Key], None] | None = None, + on_change: Callable[[Selection, Key], None] | None = None, + **props: Any +) -> ListActionGroupElement: +``` -A picker that can be used to select from a list. Children should be one of four types: -If children are of type `Item`, they are the dropdown options. -If children are of type `SectionElement`, they are the dropdown sections. -If children are of type `Table`, the values in the table are the dropdown options. There can only be one child, the `Table`. -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`. +###### Parameters +| Parameter | Type | Description | +|-------------------------|------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `*children` | `ActionGroupItem` | The actions to render within the action group. | +| `on_action` | `Callable[[ActionKey, Key], None] \| None` | Handler that is called when an item is pressed. The first argument is the key of the action, the second argument is the key of the list_view item. | +| `on_selection_change` | `Callable[[Selection, Key], None] \| None` | Handler that is called when the selection changes. The first argument is the selection, the second argument is the key of the list_view item. | +| `on_change` | `Callable[[Selection, Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the selection changes. The first argument is the selection, the second argument is the key of the list_view item. | +| `**props` | `Any` | Any other [ActionGroup](https://react-spectrum.adobe.com/react-spectrum/ActionGroup.html) prop. | + + +###### ui.list_action_menu +A group of action buttons that can be used to create a list of actions. +This component should be used within the actions prop of a `ListView` component. + +```py +def list_action_menu( + *children: ActionMenuItem, + on_action: Callable[[ActionKey, Key], None] | None = None, + on_open_change: Callable[[bool, Key], None] | None = None, + **props: Any +) -> ListActionMenuElement: +``` + +###### Parameters +| Parameter | Type | Description | +|-------------------------|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------| +| `*children` | `ActionMenuItem` | The options to render within the picker. | +| `on_action` | `Callable[[ActionKey, Key], None] \| None` | Handler that is called when an item is pressed. The first argument is the key of the action, the second argument is the key of the list_view item. | +| `on_open_change` | `Callable[[bool, Key], None] \| None` | The first argument is a boolean indicating if the menu is open, the second argument is the key of the list_view item. | +| `**props` | `Any` | Any other [ActionMenu](https://react-spectrum.adobe.com/react-spectrum/ActionMenu.html) prop. | + +##### ui.item_table_source + +An item table source wraps a Table or PartitionedTable to provide additional information for +creating complex items from a table. +A PartitionedTable is only supported if the component itself supports a PartitionedTable as a child. +A PartitionedTable passed here will lead to the same behavior as passing +the PartitionedTable directly to a component, such as creating sections from the partitions in the case of a Picker. ```py import deephaven.ui as ui -ui.picker( - *children: Item | SectionElement | Table | PartitionedTable, +ui.item_table_source( + table: Table | PartitionedTable, key_column: ColumnName | None = None, label_column: ColumnName | None = None, description_column: ColumnName | None = None, icon_column: ColumnName | None = None, title_column: ColumnName | None = None, + actions: ListActionGroupElement | ListActionMenuElement | None = None, +) -> ItemTableSource: +``` + +###### Parameters + +| Parameter | Type | Description | +| ---------------------- | ----------------------------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `*children` | `Item \| SectionElement \| Table \| PartitionedTable` | The options to render within the picker. | +| `key_column` | `ColumnName \| None` | The column of values to use as item keys. Defaults to the first column. | +| `label_column` | `ColumnName \| None` | The column of values to display as primary text. Defaults to the `key_column` value. | +| `description_column` | `ColumnName \| None` | The column of values to display as descriptions. | +| `icon_column` | `ColumnName \| None` | The column of values to map to icons. | +| `title_column` | `ColumnName \| None` | Only valid if table is of type `PartitionedTable`. The column of values to display as section names. Should be the same for all values in the constituent `Table`. If not specified, the section titles will be created from the `key_columns` of the `PartitionedTable`. | +| `actions` | `ListActionGroupElement \| ListActionMenuElement \| None` | The action group or menus to render for all elements within the component, if supported. | + +##### ui.picker + +A picker that can be used to select from a list. 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`. +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`. +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`. + +```py +import deephaven.ui as ui +ui.picker( + *children: Item | SectionElement | Table | PartitionedTable | ItemTableSource, default_selected_key: Key | None = None, selected_key: Key | None = None, on_selection_change: Callable[[Key], None] | None = None, @@ -1078,19 +1155,14 @@ ui.picker( ###### Parameters -| Parameter | Type | Description | -| ---------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `*children` | `Item \| SectionElement \| Table \| PartitionedTable` | The options to render within the picker. | -| `key_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to use as item keys. Defaults to the first column. | -| `label_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to display as primary text. Defaults to the `key_column` value. | -| `description_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to display as descriptions. | -| `icon_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to map to icons. | -| `title_column` | `ColumnName \| None` | Only valid if children is of type `PartitionedTable`. The column of values to display as section names. Should be the same for all values in the constituent `Table`. If not specified, the section titles will be created from the `key_columns` of the `PartitionedTable`. | -| `default_selected_key` | `Key \| None` | The initial selected key in the collection (uncontrolled). | -| `selected_key` | `Key \| None` | The currently selected key in the collection (controlled). | -| `on_selection_change` | `Callable[[Key], None] \| None` | Handler that is called when the selection changes. | -| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the selection changes. | -| `**props` | `Any` | Any other [Picker](https://react-spectrum.adobe.com/react-spectrum/Picker.html) prop, with the exception of `items`, `validate`, `errorMessage` (as a callback) and `onLoadMore` | +| Parameter | Type | Description | +| ---------------------- |--------------------------------------------------------------------------| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `*children` | `Item \| SectionElement \| Table \| PartitionedTable \| ItemTableSource` | The options to render within the picker. | +| `default_selected_key` | `Key \| None` | The initial selected key in the collection (uncontrolled). | +| `selected_key` | `Key \| None` | The currently selected key in the collection (controlled). | +| `on_selection_change` | `Callable[[Key], None] \| None` | Handler that is called when the selection changes. | +| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the selection changes. | +| `**props` | `Any` | Any other [Picker](https://react-spectrum.adobe.com/react-spectrum/Picker.html) prop, with the exception of `items`, `validate`, `errorMessage` (as a callback) and `onLoadMore` | ```py import deephaven.ui as ui @@ -1176,84 +1248,40 @@ partitioned_table = color_table.partition_by("Sections") color, set_color = ui.use_state("salmon") # this will create a picker with two sections, one for each partition -picker7 = ui.picker( +# in order to customize the columns used for the picker, use an item_table_source +source = ui.item_table_source( partitioned_table, key_column="Keys", label_column="Labels", description_column="Descriptions", icon_column="Icons", - title_column="SectionNames", + title_column="SectionNames" +) + +picker7 = ui.picker( + source, selected_key=color, on_selection_change=set_color ) ``` -###### ui.list_action_group - -A group of action buttons that can be used to create a list of actions. -This component should be used within the actions prop of a `ListView` component. - -```py -def list_action_group( - *children: ActionGroupItem, - on_action: Callable[[ActionKey, Key], None] | None = None, - on_selection_change: Callable[[Selection, Key], None] | None = None, - **props: Any -) -> ListActionGroupElement: -``` - -###### Parameters - -| Parameter | Type | Description | -| --------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `*children` | `ActionGroupItem` | The actions to render within the action group. | -| `on_action` | `Callable[[ActionKey, Key], None] \| None` | Handler that is called when an item is pressed. The first argument is the key of the action, the second argument is the key of the list_view item. | -| `on_selection_change` | `Callable[[Selection, Key], None] \| None` | Handler that is called when the selection changes. The first argument is the selection, the second argument is the key of the list_view item. | -| `**props` | `Any` | Any other [ActionGroup](https://react-spectrum.adobe.com/react-spectrum/ActionGroup.html) prop. | - -###### ui.list_action_menu - -A group of action buttons that can be used to create a list of actions. -This component should be used within the actions prop of a `ListView` component. - -```py -def list_action_menu( - *children: ActionMenuItem, - on_action: Callable[[ActionKey, Key], None] | None = None, - on_open_change: Callable[[bool, Key], None] | None = None, - **props: Any -) -> ListActionMenuElement: -``` - -###### Parameters - -| Parameter | Type | Description | -| ---------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `*children` | `ActionMenuItem` | The options to render within the picker. | -| `on_action` | `Callable[[ActionKey, Key], None] \| None` | Handler that is called when an item is pressed. The first argument is the key of the action, the second argument is the key of the list_view item. | -| `on_open_change` | `Callable[[bool, Key], None] \| None` | The first argument is a boolean indicating if the menu is open, the second argument is the key of the list_view item. | -| `**props` | `Any` | Any other [ActionMenu](https://react-spectrum.adobe.com/react-spectrum/ActionMenu.html) prop. | - ###### ui.list_view - -A list view that can be used to create a list of items. Children should be one of two types: - -1. If children are of type `Item`, they are the list items. -2. If children are of type `Table`, the values in the table are the list items. There can only be one child, the `Table`. +A list view that can be used to create a list of items. Children should be one of three types: +1. If children are of type `Item`, they are the list items. +2. If children are of type `Table`, the values in the table are the list items. There can only be one child, the `Table`. +3. If children are of type ItemTableSource, complex items are created from the table. +There can only be one child, the `ItemTableSource`. +Supported `ItemTableSource` arguments are `key_column`, `label_column`, `description_column`, +`icon_column`, and `actions`. ```py import deephaven.ui as ui ui.list_view( - *children: Item | Table, - key_column: ColumnName | None = None, - label_column: ColumnName | None = None, + *children: Item | Table | ItemTableSource, + selection_mode: SelectionMode | None = 'MULTIPLE', density: Density | None = "COMPACT", - description_column: ColumnName | None = None, - icon_column: ColumnName | None = None, - actions: ListActionGroupElement | ListActionMenuElement | None = None, default_selected_keys: Selection | None = None, selected_keys: Selection | None = None, - selection_mode: SelectionMode | None = "MULTIPLE", render_empty_state: Element | None = None, on_selection_change: Callable[[Selection], None] | None = None, on_change: Callable[[Selection], None] | None = None, @@ -1262,15 +1290,10 @@ ui.list_view( ``` ###### Parameters - | Parameter | Type | Description | -| ----------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `*children` | `Item \| Table` | The options to render within the list_view. | -| `key_column` | `ColumnName \| None` | Only valid if children are of type `Table`. The column of values to use as item keys. Defaults to the first column. | -| `label_column` | `ColumnName \| None` | Only valid if children are of type `Table`. The column of values to display as primary text. Defaults to the `key_column` value. | -| `description_column` | `ColumnName \| None` | Only valid if children are of type `Table`. The column of values to display as descriptions. | -| `icon_column` | `ColumnName \| None` | Only valid if children are of type `Table`. The column of values to map to icons. | -| `actions` | `ListActionGroupElement \| ListActionMenuElement \| None` | Only valid if children are of type Table. The action group or menus to render for all elements within the list view. | +|-------------------------|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `*children` | `Item \| Table \| ItemTableSource` | The options to render within the list_view. | +| `selection_mode` | `SelectionMode \| None` | By default `"MULTIPLE"`, which allows multiple selection. May also be `"SINGLE"` to allow only single selection, or `"None"`/`None` to allow no selection. | | `default_selected_keys` | `Selection \| None` | The initial selected keys in the collection (uncontrolled). | | `selected_keys` | `Selection \| None` | The currently selected keys in the collection (controlled). | | `render_empty_state` | `Element \| None` | Sets what the `list_view` should render when there is no content to display. | @@ -1336,28 +1359,25 @@ color_table = new_table([ colors, set_colors = ui.use_state(["salmon", "lemonchiffon"]) # this will create a controlled list_view with color_table -list_view5 = ui.list_view( +# In order to customize the columns and add table-based buttons for the list_view, use an item_table_source +# Note key is added to the on_press handler, but is not required. +on_button_action = lambda action_key, key: print(f"Action {action_key} was pressed for list item {key}") +button = ui.list_action_group("Print Item", on_action=on_button_action) + +source = ui.item_table_source( color_table, key_column="Keys", label_column="Labels", description_column="Descriptions", icon_column="Icons", + actions=button +) +list_view5 = ui.list_view( + source, selected_keys=colors, on_selection_change=set_colors ) - -# Buttons can be embedded in the list view. Note key is added to the on_press handler, but is not required. -on_button_action = lambda action_key, key: print(f"Action {action_key} was pressed for list item {key}") -button = ui.list_action_group("Print Item", on_action=on_button_action) - -list_view7 = ui.list_view( - "Option 1", - "Option 2", - "Option 3", - "Option 4", - actions=button, -) ``` ###### ui.date_picker @@ -1495,22 +1515,20 @@ date_picker8 = ui.date_picker( ##### ui.combo_box -A combo_box that can be used to search or select from a list. -Children should be one of four types: -If children are of type `Item`, they are the dropdown options. -If children are of type `SectionElement`, they are the dropdown sections. -If children are of type `Table`, the values in the table are the dropdown options. There can only be one child, the `Table`. -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`. +A combo_box that can be used to search or select from a list. 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`. +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`. +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`. ```py import deephaven.ui as ui ui.combo_box( - *children: Item | SectionElement | Table | PartitionedTable, - key_column: ColumnName | None = None, - label_column: ColumnName | None = None, - description_column: ColumnName | None = None, - icon_column: ColumnName | None = None, - title_column: ColumnName | None = None, + *children: Item | SectionElement | Table | PartitionedTable | ItemTableSource, default_selected_key: Key | None = None, selected_key: Key | None = None, input_value: str | None = None, @@ -1525,23 +1543,18 @@ ui.combo_box( ###### Parameters -| Parameter | Type | Description | -| ---------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `*children` | `Item \| SectionElement \| Table \| PartitionedTable` | The options to render within the combo_box. | -| `key_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to use as item keys. Defaults to the first column. | -| `label_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to display as primary text. Defaults to the `key_column` value. | -| `description_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to display as descriptions. | -| `icon_column` | `ColumnName \| None` | Only valid if children are of type `Table` or `PartitionedTable`. The column of values to map to icons. | -| `title_column` | `ColumnName \| None` | Only valid if children is of type `PartitionedTable`. The column of values to display as section names. Should be the same for all values in the constituent `Table`. If not specified, the section titles will be created from the `key_columns` of the `PartitionedTable`. | -| `default_selected_key` | `Key \| None` | The initial selected key in the collection (uncontrolled). | -| `selected_key` | `Key \| None` | The currently selected key in the collection (controlled). | -| `input_value` | `str \| None` | The value of the search input (controlled). | -| `default_input_value` | `str \| None` | The default value of the search input (uncontrolled). | -| `on_input_change` | `Callable[[str], None] \| None` | Handler that is called when the search input value changes. | -| `on_selection_change` | `Callable[[Key], None] \| None` | Handler that is called when the selection changes. | -| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the selection changes. | -| `on_open_change` | `Callable[[bool, MenuTriggerAction], None] \| None` | 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. | -| `**props` | `Any` | Any other [Combo_Box](https://react-spectrum.adobe.com/react-spectrum/ComboBox.html) prop, with the exception of `items`, `validate`, `errorMessage` (as a callback) and `onLoadMore` | +| Parameter | Type | Description | +| ---------------------- |--------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `*children` | `Item \| SectionElement \| Table \| PartitionedTable \| ItemTableSource` | The options to render within the combo_box. | +| `default_selected_key` | `Key \| None` | The initial selected key in the collection (uncontrolled). | +| `selected_key` | `Key \| None` | The currently selected key in the collection (controlled). | +| `input_value` | `str \| None` | The value of the search input (controlled). | +| `default_input_value` | `str \| None` | The default value of the search input (uncontrolled). | +| `on_input_change` | `Callable[[str], None] \| None` | Handler that is called when the search input value changes. | +| `on_selection_change` | `Callable[[Key], None] \| None` | Handler that is called when the selection changes. | +| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the selection changes. | +| `on_open_change` | `Callable[[bool, MenuTriggerAction], None] \| None` | 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. | +| `**props` | `Any` | Any other [Combo_Box](https://react-spectrum.adobe.com/react-spectrum/ComboBox.html) prop, with the exception of `items`, `validate`, `errorMessage` (as a callback) and `onLoadMore` | ```py import deephaven.ui as ui @@ -1644,24 +1657,19 @@ partitioned_table = color_table.partition_by("Sections") color, set_color = ui.use_state("salmon") # this will create a combo_box with two sections, one for each partition -combo_box9 = ui.combo_box( +# in order to customize the columns used for the combo_box, use an item_table_source + +source = ui.item_table_source( partitioned_table, key_column="Keys", label_column="Labels", description_column="Descriptions", icon_column="Icons", - title_column="SectionNames", - selected_key=color, - on_selection_change=set_color + title_column="SectionNames" ) -color, set_color = ui.use_state("salmon") - -# this will create a combo_box that matches against the start of the label when searching -combo_box10 = ui.combo_box( - color_table, - key_column="Keys", - search_type="STARTS_WITH", +combo_box9 = ui.combo_box( + source, selected_key=color, on_selection_change=set_color ) @@ -1671,7 +1679,7 @@ items = ["First Option", "Second Option", "Third Option", "Fourth Option"] filter_value, set_filter_value = ui.use_state('') filtered_items = ui.use_memo(lambda: filter(lambda item: item.startswith(filter_value), items), [filter_value, items]) -combo_box11 = ui.combo_box(*filtered_items, on_input_change=set_filter_value) +combo_box10 = ui.combo_box(*filtered_items, on_input_change=set_filter_value) ``` #### ui.table diff --git a/plugins/ui/docs/README.md b/plugins/ui/docs/README.md index 8bbfc3262..5d3abe99d 100644 --- a/plugins/ui/docs/README.md +++ b/plugins/ui/docs/README.md @@ -240,6 +240,91 @@ my_picker = ui_picker() ![Use a picker to select from a list of items](_assets/picker.png) +## Picker (table) + +A picker can also take a Table. It will use the first column as the key and label by default. + +```python +import deephaven.ui as ui +from deephaven import time_table +import datetime + +# Ticking table with initial row count of 200 that adds a row every second +initial_row_count = 200 +column_types = time_table( + "PT1S", + start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count), +).view( + [ + "Id=new Integer(i)", + "Display=new String(`Display `+i)", + ] +) + + +@ui.component +def ui_picker_table(): + value, set_value = ui.use_state("") + + pick_table = ui.picker( + column_types, + label="Text", + on_change=set_value, + selected_keys=value, + ) + + text = ui.text(f"Selection: {value}") + + return ui.flex(pick_table, text, direction="column", margin=10, gap=10) + + +pick_table = ui_picker_table() +``` +![Use a picker to select from a table](_assets/pick_table.png) + +## Picker (item table source) + +A picker can also take an `item_table_source`. It will use the columns specified. + +```python +import deephaven.ui as ui +from deephaven import time_table +import datetime + +# Ticking table with initial row count of 200 that adds a row every second +initial_row_count = 200 +column_types = time_table( + "PT1S", + start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count), +).view( + [ + "Id=new Integer(i)", + "Display=new String(`Display `+i)", + ] +) + + +@ui.component +def ui_picker_table_source(): + value, set_value = ui.use_state("") + + pick_table = ui.picker( + ui.item_table_source(column_types, key_column="Id", label_column="Display"), + label="Text", + on_change=set_value, + selected_keys=value, + ) + + text = ui.text(f"Selection: {value}") + + return ui.flex(pick_table, text, direction="column", margin=10, gap=10) + + +pick_table_source = ui_picker_table_source() +``` + +![Use a picker to select from a table source](_assets/pick_table_source.png) + ## ListView (string values) A list view that can be used to create a list of selectable items. Here's a basic example for selecting from a list of string values and displaying the selected key in a text field. @@ -280,6 +365,9 @@ lv = ui_list_view() ``` ## ListView (table) + +A ListView can also take a Table. It will use the first column as the key and label by default. + ```python from deephaven import time_table, ui import datetime @@ -299,12 +387,10 @@ column_types = time_table( @ui.component def ui_list_view_table(): - value, set_value = ui.use_state([2, 4, 5]) + value, set_value = ui.use_state([]) lv = ui.list_view( column_types, - key_column="Id", - label_column="Display", aria_label="List View", on_change=set_value, selected_keys=value, @@ -325,6 +411,57 @@ def ui_list_view_table(): lv_table = ui_list_view_table() ``` +![Use a list view to select from a table](_assets/lv_table.png) + +## ListView (item table source) + +A list view can also take an `item_table_source`. It will use the columns specified. + +```python +from deephaven import time_table, ui +import datetime + +# Ticking table with initial row count of 200 that adds a row every second +initial_row_count = 200 +column_types = time_table( + "PT1S", + start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count), +).update( + [ + "Id=new Integer(i)", + "Display=new String(`Display `+i)", + ] +) + + +@ui.component +def ui_list_view_table_source(): + value, set_value = ui.use_state([2, 4, 5]) + + lv = ui.list_view( + ui.item_table_source(column_types, key_column="Id", label_column="Display"), + aria_label="List View", + on_change=set_value, + selected_keys=value, + ) + + text = ui.text("Selection: " + ", ".join(map(str, value))) + + return ui.flex( + lv, + text, + direction="column", + margin=10, + gap=10, + # necessary to avoid overflowing container height + min_height=0, + ) + + +lv_table_source = ui_list_view_table_source() +``` + +![Use a list view to select from a table source](_assets/lv_table_source.png) ## Form (two variables) diff --git a/plugins/ui/docs/_assets/lv_table.png b/plugins/ui/docs/_assets/lv_table.png new file mode 100644 index 000000000..54e4c5a57 Binary files /dev/null and b/plugins/ui/docs/_assets/lv_table.png differ diff --git a/plugins/ui/docs/_assets/lv_table_source.png b/plugins/ui/docs/_assets/lv_table_source.png new file mode 100644 index 000000000..7ba0adc37 Binary files /dev/null and b/plugins/ui/docs/_assets/lv_table_source.png differ diff --git a/plugins/ui/docs/_assets/pick_table.png b/plugins/ui/docs/_assets/pick_table.png new file mode 100644 index 000000000..fdf7bfffa Binary files /dev/null and b/plugins/ui/docs/_assets/pick_table.png differ diff --git a/plugins/ui/docs/_assets/pick_table_source.png b/plugins/ui/docs/_assets/pick_table_source.png new file mode 100644 index 000000000..53a05d054 Binary files /dev/null and b/plugins/ui/docs/_assets/pick_table_source.png differ diff --git a/plugins/ui/src/deephaven/ui/_internal/utils.py b/plugins/ui/src/deephaven/ui/_internal/utils.py index 3ee0fe706..066b6bbcb 100644 --- a/plugins/ui/src/deephaven/ui/_internal/utils.py +++ b/plugins/ui/src/deephaven/ui/_internal/utils.py @@ -193,7 +193,7 @@ def wrap_callable(func: Callable) -> Callable: return func -def create_props(args: dict[str, Any]) -> tuple[tuple[Any], dict[str, Any]]: +def create_props(args: dict[str, Any]) -> tuple[tuple[Any, ...], dict[str, Any]]: """ Create props from the args. Combines the named props with the kwargs props. @@ -390,3 +390,29 @@ def convert_date_props( if not callable(props[key]): raise TypeError(f"{key} must be a callable") props[key] = _wrap_date_callable(props[key], converter) + + +def unpack_item_table_source( + children: tuple[Any, ...], + props: dict[str, Any], + supported_args: set[str], +) -> tuple[tuple[Any, ...], dict[str, Any]]: + """ + Unpack children and props if the children are of type dict + and merge the supported arguments into the props. + + Args: + children: The children to possibly unpack. + props: The props to unpack. + supported_args: The supported arguments for the ItemTableSource. + + Returns: + The unpacked children and props. + """ + if len(children) == 1 and isinstance(children[0], dict): + item_table_source = children[0].copy() + children = (item_table_source.pop("table"),) + for key in supported_args: + if key in item_table_source: + props[key] = item_table_source.pop(key) + return children, props diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index d18981f10..a1acce803 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -14,6 +14,7 @@ from .list_view import list_view from .list_action_group import list_action_group from .list_action_menu import list_action_menu +from .item_table_source import item_table_source from .date_picker import date_picker from . import html @@ -38,6 +39,7 @@ "icon", "icon_wrapper", "item", + "item_table_source", "illustrated_message", "list_view", "list_action_group", diff --git a/plugins/ui/src/deephaven/ui/components/item.py b/plugins/ui/src/deephaven/ui/components/item.py index d0daed2e0..b5b05e8e5 100644 --- a/plugins/ui/src/deephaven/ui/components/item.py +++ b/plugins/ui/src/deephaven/ui/components/item.py @@ -1,9 +1,10 @@ -from typing import Any +from typing import Any, Union from ..elements import BaseElement from ..types import Stringable ItemElement = BaseElement +Item = Union[Stringable, ItemElement] def item(*children: Stringable, **props: Any) -> ItemElement: diff --git a/plugins/ui/src/deephaven/ui/components/item_table_source.py b/plugins/ui/src/deephaven/ui/components/item_table_source.py new file mode 100644 index 000000000..f9d580396 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/item_table_source.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from typing import Union, TypedDict, cast + +from deephaven.table import Table, PartitionedTable + +from .item import ItemElement +from .list_action_group import ListActionGroupElement +from .list_action_menu import ListActionMenuElement +from ..elements import Element +from ..types import ColumnName, Stringable + +ListViewItem = Union[Stringable, ItemElement] +ListViewElement = Element + + +class ItemTableSource(TypedDict): + table: Table | PartitionedTable + key_column: ColumnName | None + label_column: ColumnName | None + description_column: ColumnName | None + icon_column: ColumnName | None + title_column: ColumnName | None + actions: ListActionGroupElement | ListActionMenuElement | None + + +def item_table_source( + table: Table | PartitionedTable, + key_column: ColumnName | None = None, + label_column: ColumnName | None = None, + description_column: ColumnName | None = None, + icon_column: ColumnName | None = None, + title_column: ColumnName | None = None, + actions: ListActionGroupElement | ListActionMenuElement | None = None, +) -> ItemTableSource: + """ + An item table source wraps a Table or PartitionedTable to provide additional information for + creating complex items from a table. + A PartitionedTable is only supported if the component itself supports a PartitionedTable as a child. + A PartitionedTable passed here will lead to the same behavior as passing + the PartitionedTable directly to a component, such as creating sections from the partitions in the case of a Picker. + + Args: + table: + The table to use as the source of items. + key_column: + The column of values to use as item keys. Defaults to the first column. + label_column: + The column of values to display as primary text. Defaults to the key_column value. + description_column: + The column of values to display as descriptions. + icon_column: + The column of values to map to icons. + title_column: + Only valid if table is of type PartitionedTable. + The column of values to display as section names. + Should be the same for all values in the constituent Table. + If not specified, the section titles will be created from the key_columns of the PartitionedTable. + actions: + The action group or menus to render for all elements within the component, if supported. + + Returns: + The item table source to pass as a child to a component that supports it. + """ + + return cast(ItemTableSource, locals()) diff --git a/plugins/ui/src/deephaven/ui/components/list_action_group.py b/plugins/ui/src/deephaven/ui/components/list_action_group.py index c7f9840db..a2a815eba 100644 --- a/plugins/ui/src/deephaven/ui/components/list_action_group.py +++ b/plugins/ui/src/deephaven/ui/components/list_action_group.py @@ -15,6 +15,7 @@ def list_action_group( *children: ActionGroupItem, on_action: Callable[[ActionKey, Key], None] | None = None, on_selection_change: Callable[[Selection, Key], None] | None = None, + on_change: Callable[[Selection, Key], None] | None = None, **props: Any, ) -> ListActionGroupElement: """ @@ -27,6 +28,9 @@ def list_action_group( The first argument is the key of the action, the second argument is the key of the list_view item. on_selection_change: Handler that is called when the selection changes. The first argument is the selection, the second argument is the key of the list_view item. + on_change: Alias of on_selection_change. + Handler that is called when the selection changes. + The first argument is the selection, the second argument is the key of the list_view item. **props: Any other ActionGroup prop. Returns: diff --git a/plugins/ui/src/deephaven/ui/components/list_view.py b/plugins/ui/src/deephaven/ui/components/list_view.py index a44353186..752d06f2a 100644 --- a/plugins/ui/src/deephaven/ui/components/list_view.py +++ b/plugins/ui/src/deephaven/ui/components/list_view.py @@ -1,28 +1,29 @@ from __future__ import annotations -from typing import Callable, Any, Union +from typing import Callable, Any from deephaven.table import Table -from .item import ItemElement -from .list_action_group import ListActionGroupElement -from .list_action_menu import ListActionMenuElement +from .item_table_source import ItemTableSource from ..elements import BaseElement, Element -from .._internal.utils import create_props -from ..types import ColumnName, Density, Stringable, Selection, SelectionMode +from .._internal.utils import create_props, unpack_item_table_source +from .item import Item +from ..types import Density, Selection, SelectionMode -ListViewItem = Union[Stringable, ItemElement] ListViewElement = Element +SUPPORTED_SOURCE_ARGS = { + "key_column", + "label_column", + "description_column", + "icon_column", + "actions", +} + def list_view( - *children: ListViewItem | Table, - key_column: ColumnName | None = None, - label_column: ColumnName | None = None, + *children: Item | Table | ItemTableSource, density: Density | None = "COMPACT", - description_column: ColumnName | None = None, - icon_column: ColumnName | None = None, - actions: ListActionGroupElement | ListActionMenuElement | None = None, default_selected_keys: Selection | None = None, selected_keys: Selection | None = None, selection_mode: SelectionMode | None = "MULTIPLE", @@ -32,34 +33,27 @@ def list_view( **props: Any, ) -> ListViewElement: """ - A list view that can be used to create a list of items. Children should be one of two types: - 1. If children are of type `ListViewItem`, they are the list items. + A list view that can be used to create a list of items. Children should be one of three types: + 1. If children are of type `Item`, they are the list items. 2. If children are of type `Table`, the values in the table are the list items. There can only be one child, the `Table`. - + The first column is used as the key and label by default. + 3. 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 `actions`. Args: *children: The options to render within the list_view. - key_column: - Only valid if children are of type Table. - The column of values to use as item keys. Defaults to the first column. - label_column: - Only valid if children are of type Table. - The column of values to display as primary text. Defaults to the key_column value. density: Sets the amount of vertical padding within each cell. - description_column: - Only valid if children are of type Table. - The column of values to display as descriptions. - icon_column: Only valid if children are of type Table. - The column of values to map to icons. - actions: - Only valid if children are of type Table. - The action group or menus to render for all elements within the list view. default_selected_keys: The initial selected keys in the collection (uncontrolled). selected_keys: The currently selected keys in the collection (controlled). + selection_mode: + By default `"MULTIPLE"`, which allows multiple selection. + May also be `"SINGLE"` to allow only single selection, or `"None"`/`None` to allow no selection. render_empty_state: Sets what the `list_view` should render when there is no content to display. on_selection_change: @@ -74,4 +68,6 @@ def list_view( """ children, props = create_props(locals()) + children, props = unpack_item_table_source(children, props, SUPPORTED_SOURCE_ARGS) + return BaseElement("deephaven.ui.components.ListView", *children, **props) diff --git a/plugins/ui/src/deephaven/ui/components/picker.py b/plugins/ui/src/deephaven/ui/components/picker.py index f060b2ee4..b5366ed4e 100644 --- a/plugins/ui/src/deephaven/ui/components/picker.py +++ b/plugins/ui/src/deephaven/ui/components/picker.py @@ -3,21 +3,25 @@ from typing import Callable, Any from deephaven.table import Table, PartitionedTable -from .section import SectionElement, PickerItem +from .section import SectionElement, Item +from .item_table_source import ItemTableSource from ..elements import BaseElement -from .._internal.utils import create_props -from ..types import ColumnName, Key +from .._internal.utils import create_props, unpack_item_table_source +from ..types import Key PickerElement = BaseElement +SUPPORTED_SOURCE_ARGS = { + "key_column", + "label_column", + "description_column", + "icon_column", + "title_column", +} + def picker( - *children: PickerItem | SectionElement | Table | PartitionedTable, - key_column: ColumnName | None = None, - label_column: ColumnName | None = None, - description_column: ColumnName | None = None, - icon_column: ColumnName | None = None, - title_column: ColumnName | None = None, + *children: Item | SectionElement | Table | PartitionedTable | ItemTableSource, default_selected_key: Key | None = None, selected_key: Key | None = None, on_selection_change: Callable[[Key], None] | None = None, @@ -25,32 +29,22 @@ def picker( **props: Any, ) -> PickerElement: """ - A picker that can be used to select from a list. Children should be one of four types: - If children are of type PickerItem, they are the dropdown options. - If children are of type SectionElement, they are the dropdown sections. - If children are of type Table, the values in the table are the dropdown options. - There can only be one child, the Table. - 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. + A picker that can be used to select from a list. 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 picker. - key_column: - Only valid if children are of type Table or PartitionedTable. - The column of values to use as item keys. Defaults to the first column. - label_column: - Only valid if children are of type Table or PartitionedTable. - The column of values to display as primary text. Defaults to the key_column value. - description_column: - Only valid if children are of type Table or PartitionedTable. - The column of values to display as descriptions. - icon_column: Only valid if children are of type Table or PartitionedTable. - The column of values to map to icons. - title_column: - Only valid if children is of type PartitionedTable. - The column of values to display as section names. - Should be the same for all values in the constituent Table. - If not specified, the section titles will be created from the key_columns of the PartitionedTable. default_selected_key: The initial selected key in the collection (uncontrolled). selected_key: @@ -67,4 +61,6 @@ def picker( """ children, props = create_props(locals()) + children, props = unpack_item_table_source(children, props, SUPPORTED_SOURCE_ARGS) + return BaseElement("deephaven.ui.components.Picker", *children, **props) diff --git a/plugins/ui/src/deephaven/ui/components/section.py b/plugins/ui/src/deephaven/ui/components/section.py index 4b78d28af..d3cb7eb03 100644 --- a/plugins/ui/src/deephaven/ui/components/section.py +++ b/plugins/ui/src/deephaven/ui/components/section.py @@ -1,19 +1,15 @@ from __future__ import annotations -from typing import Union, Any +from typing import Any from .._internal.utils import create_props from ..elements import Element, BaseElement -from ..types import Stringable -from .item import ItemElement +from .item import Item -PickerItem = Union[Stringable, ItemElement] SectionElement = Element -def section( - *children: PickerItem, title: str | None = None, **props: Any -) -> SectionElement: +def section(*children: Item, title: str | None = None, **props: Any) -> SectionElement: """ A section that can be added to a menu, such as a picker. Children are the dropdown options. diff --git a/plugins/ui/test/deephaven/ui/test_utils.py b/plugins/ui/test/deephaven/ui/test_utils.py index d98bca7e0..20a5fec14 100644 --- a/plugins/ui/test/deephaven/ui/test_utils.py +++ b/plugins/ui/test/deephaven/ui/test_utils.py @@ -1,4 +1,5 @@ import unittest + from .BaseTest import BaseTestCase @@ -261,6 +262,46 @@ def test_create_props(self): }, ) + def test_unpack_item_table_source(self): + from deephaven.ui._internal.utils import unpack_item_table_source + from deephaven.ui import item_table_source + + children = ("table",) + props = { + "test": "foo", + } + + expected_children = ("table",) + expected_props = { + "test": "foo", + } + + self.assertTupleEqual( + unpack_item_table_source(children, props, {}), + (expected_children, expected_props), + ) + + item_data_source = item_table_source( + table="table", key_column="key", actions="actions" + ) + + children = (item_data_source,) + props = { + "test": "foo", + } + + expected_children = ("table",) + + expected_props = { + "test": "foo", + "key_column": "key", + } + + self.assertTupleEqual( + unpack_item_table_source(children, props, {"table", "key_column"}), + (expected_children, expected_props), + ) + if __name__ == "__main__": unittest.main()