From 5e3c5e24d854a84823f4cf56a0f8928ce2392259 Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Tue, 9 Jul 2024 14:49:51 -0500 Subject: [PATCH] feat: UI table layout hints (#587) Fixes #442 The layout hints will only be used as the initial state. Not sure how we'd want to try to combine a change in `ui.table` from the server w/ other changes the user may have made already. At least initially I think this is fine to give the same functionality as current `table.layout_hints` ```py from deephaven import ui from deephaven.plot import express as dx _stocks = dx.data.stocks() stocks_with_hints = ui.table( _stocks, front_columns=["exchange"], frozen_columns=["sym"], back_columns=['side'], hidden_columns=['dollars', 'SPet500'], column_groups=[{"name": "test_group", "children": ["size", "random"], "color": "lemonchiffon"}] ) ``` --- plugins/ui/DESIGN.md | 86 ++++++++------- .../ui/src/deephaven/ui/components/table.py | 23 +++- plugins/ui/src/deephaven/ui/types/types.py | 32 +++++- .../js/src/elements/UITable/JsTableProxy.ts | 100 ++++++++++++++++++ .../js/src/elements/{ => UITable}/UITable.tsx | 27 ++++- .../UITableContextMenuHandler.ts | 0 .../{utils => UITable}/UITableMouseHandler.ts | 0 .../{utils => UITable}/UITableUtils.tsx | 17 +-- plugins/ui/src/js/src/elements/index.ts | 2 +- plugins/ui/src/js/src/elements/utils/index.ts | 4 +- plugins/ui/test/deephaven/ui/test_ui_table.py | 69 ++++++++++++ 11 files changed, 302 insertions(+), 58 deletions(-) create mode 100644 plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts rename plugins/ui/src/js/src/elements/{ => UITable}/UITable.tsx (87%) rename plugins/ui/src/js/src/elements/{utils => UITable}/UITableContextMenuHandler.ts (100%) rename plugins/ui/src/js/src/elements/{utils => UITable}/UITableMouseHandler.ts (100%) rename plugins/ui/src/js/src/elements/{utils => UITable}/UITableUtils.tsx (92%) diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index 8c08ea348..2c4e29e34 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -1858,11 +1858,12 @@ Other props that can be passed into `ui.table` are defined below. ```py ui_table( table: Table, - always_fetch_columns: ColumnNameCombination | None, - back_columns: ColumnNameCombination | None, - freeze_columns: ColumnNameCombination | None, - front_columns: ColumnNameCombination | None, - hide_columns: ColumnNameCombination | None, + always_fetch_columns: list[ColumnName] | None, + front_columns: list[ColumnName] | None, + back_columns: list[ColumnName] | None, + frozen_columns: list[ColumnName] | None, + hidden_columns: list[ColumnName] | None, + column_groups: list[ColumnGroup] | None, quick_filters: dict[ColumnName, QuickFilterExpression] | None, show_search: bool, show_quick_filters: bool, @@ -1891,38 +1892,39 @@ ui_table( ) -> UITable ``` -| Parameter | Type | Description | -| ------------------------ | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `always_fetch_columns` | `ColumnNameCombination \| None` | The columns to always fetch from the server. May be a single column name. These will not be affected by the users current viewport/horizontal scrolling. Useful if you have a column with key value data that you want to always include in the data sent for row click operations. | -| `back_columns` | `ColumnNameCombination \| None` | The columns to show at the back of the table. May be a single column name. These will not be moveable in the UI. | -| `freeze_columns` | `ColumnNameCombination \| None` | The columns to freeze to the front of the table. May be a single column name. These will always be visible and not affected by horizontal scrolling. | -| `front_columns` | `ColumnNameCombination \| None` | The columns to show at the front of the table. May be a single column name. These will not be moveable in the UI. | -| `hide_columns` | `ColumnNameCombination \| None` | The columns to hide by default from the table. May be a single column name. The user can still resize the columns to view them. | -| `quick_filters` | `dict[ColumnName, QuickFilterExpression] \| None` | Quick filters for the UI to apply to the table. | -| `show_search` | `bool` | `True` to show the search bar by default, `False` to not. `False` by default. | -| `show_quick_filters` | `bool` | `True` to show the quick filters by default, `False` to not. `False` by default. | -| `show_column_headers` | `bool \| None` | `True` to show the column headers by default, `False` to not. | -| `selection_mode` | `SelectionMode \| None` | Can be `MULTIPLE` to allow multiple selection or `SINGLE` to not allow it. | -| `selection_area` | `SelectionArea \| None` | The unit that is selected on press. Can be `ROW`, `COLUMN`, or `CELL`. | -| `selection_style` | `SelectionStyleCombination \| None` | The style of the selection. Can be `HIGHLIGHT`, `CHECKBOX`, or a combination of those. | -| `selected_rows` | `RowIndexCombination \| None` | The rows that are selected by default. Only valid if `selection_area` is `ROW`. | -| `selected_columns` | `ColumnIndexCombination \| None` | The columns that are selected by default. Only valid if `selection_area` is `COLUMN`. | -| `selected_cells` | `CellIndexCombination \| None` | The cells that are selected by default. Only valid if `selection_area` is `CELL`. | -| `density` | `DensityMode \| None` | The density of the table. Can be `COMPACT`, `REGULAR`, or `SPACIOUS`. | -| `column_display_names` | `dict[ColumnName, ColumnNameCombination] \| None` | The display names. If a sequence of column names is provided for a column, the display name will be set to the longest column name that can be fully displayed. | -| `on_row_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is released (such as a click). The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | -| `on_row_double_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is double pressed. The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | -| `on_cell_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is released (such as a click). The first parameter is the cell index, and the second is the cell data. | -| `on_cell_double_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is double pressed. The first parameter is the cell index, and the second is the cell data. | -| `on_column_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is released (such as a click). The only parameter is the column name. | -| `on_column_double_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a cell in a column is double pressed. The only parameter is the column name. | -| `on_search` | `Callable[[str], None] \| None` | The callback function to run when the search bar is used. The only parameter is the search string. | -| `on_quick_filter` | `Callable[[ColumnName, QuickFilterExpression], None] \| None` | The callback function to run when a quick filter is applied. The first parameter is the column name, and the second is the quick filter expression. | -| `on_freeze_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is frozen. The only parameter is the frozen column name. | -| `on_hide_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is hidden. The only parameter is the hidden column name. | -| `on_sort` | `Callable[[ColumnName, LiteralSortDirection], None] \| None` | The callback function to run when a column is sorted. The first parameter is the column name, and the second is the sort direction. | -| `context_menu` | `ResolvableContextMenuItem \| list[ResolvableContextMenuItem] \| None` | The context menu items to show when right-clicking on a cell in the table. Can contain `ContextMenuSubmenuItem`s to define submenus. | -| `context_header_menu` | `ResolvableContextMenuItem \| list[ResolvableContextMenuItem] \| None` | The context menu items to show when right-clicking on the column header (i.e. column name). Can contain `ContextMenuSubmenuItem`s to define submenus. | +| Parameter | Type | Description | +| ------------------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `always_fetch_columns` | `list[ColumnName] \| None` | The columns to always fetch from the server. These will not be affected by the users current viewport/horizontal scrolling. Useful if you have a column with key value data that you want to always include in the data sent for row click operations. | +| `front_columns` | `list[ColumnName] \| None` | The columns to show at the front of the table. These will not be moveable in the UI. | +| `back_columns` | `list[ColumnName] \| None` | The columns to show at the back of the table. These will not be moveable in the UI. | +| `frozen_columns` | `list[ColumnName] \| None` | The columns to freeze by default to the front of the table. These will always be visible and not affected by horizontal scrolling. | +| `hidden_columns` | `list[ColumnName] \| None` | The columns to hide by default from the table. The user can still resize the columns to view them. | +| `column_groups` | `list[ColumnGroup] \| None` | The column groups to show in the table by default. Columns will be moved adjacent to the first child in the group. Groups will be shown in the table header. | +| `quick_filters` | `dict[ColumnName, QuickFilterExpression] \| None` | Quick filters for the UI to apply to the table. | +| `show_search` | `bool` | `True` to show the search bar by default, `False` to not. `False` by default. | +| `show_quick_filters` | `bool` | `True` to show the quick filters by default, `False` to not. `False` by default. | +| `show_column_headers` | `bool \| None` | `True` to show the column headers by default, `False` to not. | +| `selection_mode` | `SelectionMode \| None` | Can be `MULTIPLE` to allow multiple selection or `SINGLE` to not allow it. | +| `selection_area` | `SelectionArea \| None` | The unit that is selected on press. Can be `ROW`, `COLUMN`, or `CELL`. | +| `selection_style` | `SelectionStyleCombination \| None` | The style of the selection. Can be `HIGHLIGHT`, `CHECKBOX`, or a combination of those. | +| `selected_rows` | `RowIndexCombination \| None` | The rows that are selected by default. Only valid if `selection_area` is `ROW`. | +| `selected_columns` | `ColumnIndexCombination \| None` | The columns that are selected by default. Only valid if `selection_area` is `COLUMN`. | +| `selected_cells` | `CellIndexCombination \| None` | The cells that are selected by default. Only valid if `selection_area` is `CELL`. | +| `density` | `DensityMode \| None` | The density of the table. Can be `COMPACT`, `REGULAR`, or `SPACIOUS`. | +| `column_display_names` | `dict[ColumnName, ColumnNameCombination] \| None` | The display names. If a sequence of column names is provided for a column, the display name will be set to the longest column name that can be fully displayed. | +| `on_row_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is released (such as a click). The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | +| `on_row_double_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is double pressed. The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | +| `on_cell_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is released (such as a click). The first parameter is the cell index, and the second is the cell data. | +| `on_cell_double_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is double pressed. The first parameter is the cell index, and the second is the cell data. | +| `on_column_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is released (such as a click). The only parameter is the column name. | +| `on_column_double_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a cell in a column is double pressed. The only parameter is the column name. | +| `on_search` | `Callable[[str], None] \| None` | The callback function to run when the search bar is used. The only parameter is the search string. | +| `on_quick_filter` | `Callable[[ColumnName, QuickFilterExpression], None] \| None` | The callback function to run when a quick filter is applied. The first parameter is the column name, and the second is the quick filter expression. | +| `on_freeze_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is frozen. The only parameter is the frozen column name. | +| `on_hide_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is hidden. The only parameter is the hidden column name. | +| `on_sort` | `Callable[[ColumnName, LiteralSortDirection], None] \| None` | The callback function to run when a column is sorted. The first parameter is the column name, and the second is the sort direction. | +| `context_menu` | `ResolvableContextMenuItem \| list[ResolvableContextMenuItem] \| None` | The context menu items to show when right-clicking on a cell in the table. Can contain `ContextMenuSubmenuItem`s to define submenus. | +| `context_header_menu` | `ResolvableContextMenuItem \| list[ResolvableContextMenuItem] \| None` | The context menu items to show when right-clicking on the column header (i.e. column name). Can contain `ContextMenuSubmenuItem`s to define submenus. | `ui.table` will also support the below methods. @@ -2386,6 +2388,16 @@ class LinkPoint(TypedDict): # Column to link to column: str +# Typed dictionary for a column group +class ColumnGroup(TypedDict): + # Name of the group + name: str + + # Children columns/groups in the group + children: list[str] + + # Optional background color of the group header + color: NotRequired[str] class ContextMenuActionParams(TypedDict): """ diff --git a/plugins/ui/src/deephaven/ui/components/table.py b/plugins/ui/src/deephaven/ui/components/table.py index 41517079e..4319bc3fc 100644 --- a/plugins/ui/src/deephaven/ui/components/table.py +++ b/plugins/ui/src/deephaven/ui/components/table.py @@ -4,6 +4,7 @@ from ..elements import UITable from ..types import ( CellPressCallback, + ColumnGroup, ColumnName, ColumnPressCallback, QuickFilterExpression, @@ -24,6 +25,11 @@ def table( quick_filters: dict[ColumnName, QuickFilterExpression] | None = None, show_quick_filters: bool = False, show_search: bool = False, + front_columns: list[ColumnName] | None = None, + back_columns: list[ColumnName] | None = None, + frozen_columns: list[ColumnName] | None = None, + hidden_columns: list[ColumnName] | None = None, + column_groups: list[ColumnGroup] | None = None, context_menu: ( ResolvableContextMenuItem | list[ResolvableContextMenuItem] | None ) = None, @@ -53,12 +59,21 @@ def table( quick_filters: The quick filters to apply to the table. Dictionary of column name to filter value. show_quick_filters: Whether to show the quick filter bar by default. show_search: Whether to show the search bar by default. + front_columns: The columns to pin to the front of the table. These will not be movable by the user. + back_columns: The columns to pin to the back of the table. These will not be movable by the user. + frozen_columns: The columns to freeze by default at the front of the table. + These will always be visible regardless of horizontal scrolling. + The user may unfreeze columns or freeze additional columns. + hidden_columns: The columns to hide by default. Users may show the columns by expanding them. + column_groups: Columns to group together by default. The groups will be shown in the table header. + Group names must be unique within the column and group names. + Groups may be nested by providing the group name as a child of another group. context_menu: The context menu items to show when a cell is right clicked. - May contain action items or submenu items. - May also be a function that receives the cell data and returns the context menu items or None. + May contain action items or submenu items. + May also be a function that receives the cell data and returns the context menu items or None. context_header_menu: The context menu items to show when a column header is right clicked. - May contain action items or submenu items. - May also be a function that receives the column header data and returns the context menu items or None. + May contain action items or submenu items. + May also be a function that receives the column header data and returns the context menu items or None. """ props = locals() del props["table"] diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index fdfa4f896..dd770a023 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -23,6 +23,10 @@ from deephaven import SortDirection from deephaven.dtypes import DType +DeephavenColor = Literal["salmon", "lemonchiffon"] +HexColor = str +Color = Union[DeephavenColor, HexColor] + class CellData(TypedDict): """ @@ -61,6 +65,31 @@ class RowDataValue(CellData): """ +class ColumnGroup(TypedDict): + """ + Group of columns in a table. + Groups are displayed in the table header. + Groups may be nested. + """ + + name: str + """ + Name of the column group. + Must follow column naming rules and be unique within the column and group names. + """ + + children: List[str] + """ + List of child columns or groups in the group. + Names are other columns or groups. + """ + + color: Color + """ + Color for the group header. + """ + + class ContextMenuActionParams(TypedDict): """ Parameters given to a context menu action @@ -199,9 +228,6 @@ class SliderChange(TypedDict): "UNIQUE", "SKIP", ] -DeephavenColor = Literal["salmon", "lemonchiffon"] -HexColor = str -Color = Union[DeephavenColor, HexColor] ContextMenuModeOption = Literal["CELL", "ROW_HEADER", "COLUMN_HEADER"] ContextMenuMode = Union[ContextMenuModeOption, List[ContextMenuModeOption], None] DataBarAxis = Literal["PROPORTIONAL", "MIDDLE", "DIRECTIONAL"] diff --git a/plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts b/plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts new file mode 100644 index 000000000..db1b5cfa9 --- /dev/null +++ b/plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts @@ -0,0 +1,100 @@ +import type { dh } from '@deephaven/jsapi-types'; + +export interface UITableLayoutHints { + frontColumns?: string[]; + frozenColumns?: string[]; + backColumns?: string[]; + hiddenColumns?: string[]; + columnGroups?: dh.ColumnGroup[]; +} + +// This tricks TS into believing the class extends dh.Table +// Even though it is through a proxy +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface JsTableProxy extends dh.Table {} + +/** + * Class to proxy JsTable. + * Any methods implemented in this class will be utilized over the underlying JsTable methods. + * Any methods not implemented in this class will be proxied to the table. + */ +class JsTableProxy { + private table: dh.Table; + + layoutHints: dh.LayoutHints | null = null; + + constructor({ + table, + layoutHints, + }: { + table: dh.Table; + layoutHints: UITableLayoutHints; + }) { + this.table = table; + + const { + frontColumns = null, + frozenColumns = null, + backColumns = null, + hiddenColumns = null, + columnGroups = null, + } = layoutHints; + + this.layoutHints = { + frontColumns, + frozenColumns, + backColumns, + hiddenColumns, + columnGroups, + areSavedLayoutsAllowed: false, + }; + + // eslint-disable-next-line no-constructor-return + return new Proxy(this, { + // We want to use any properties on the proxy model if defined + // If not, then proxy to the underlying model + get(target, prop, receiver) { + // Does this class have a getter for the prop + // Getter functions are on the prototype + const proxyHasGetter = + Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop) + ?.get != null; + + if (proxyHasGetter) { + return Reflect.get(target, prop, receiver); + } + + // Does this class implement the property + const proxyHasProp = Object.prototype.hasOwnProperty.call(target, prop); + + // Does the class implement a function for the property + const proxyHasFn = Object.prototype.hasOwnProperty.call( + Object.getPrototypeOf(target), + prop + ); + + const trueTarget = proxyHasProp || proxyHasFn ? target : target.table; + const value = Reflect.get(trueTarget, prop, receiver); + + if (typeof value === 'function') { + return value.bind(trueTarget); + } + + return value; + }, + set(target, prop, value) { + const proxyHasSetter = + Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop) + ?.set != null; + + if (proxyHasSetter) { + return Reflect.set(target, prop, value, target); + } + + return Reflect.set(target.table, prop, value, target.table); + }, + }); + } +} + +export default JsTableProxy; diff --git a/plugins/ui/src/js/src/elements/UITable.tsx b/plugins/ui/src/js/src/elements/UITable/UITable.tsx similarity index 87% rename from plugins/ui/src/js/src/elements/UITable.tsx rename to plugins/ui/src/js/src/elements/UITable/UITable.tsx index b5dd1a111..56bb492a8 100644 --- a/plugins/ui/src/js/src/elements/UITable.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITable.tsx @@ -15,9 +15,10 @@ import type { dh } from '@deephaven/jsapi-types'; import Log from '@deephaven/log'; import { getSettings, RootState } from '@deephaven/redux'; import { GridMouseHandler } from '@deephaven/grid'; -import { UITableProps, wrapContextActions } from './utils/UITableUtils'; -import UITableMouseHandler from './utils/UITableMouseHandler'; -import UITableContextMenuHandler from './utils/UITableContextMenuHandler'; +import { UITableProps, wrapContextActions } from './UITableUtils'; +import UITableMouseHandler from './UITableMouseHandler'; +import JsTableProxy from './JsTableProxy'; +import UITableContextMenuHandler from './UITableContextMenuHandler'; const log = Log.module('@deephaven/js-plugin-ui/UITable'); @@ -34,6 +35,11 @@ export function UITable({ table: exportedTable, showSearch: showSearchBar, showQuickFilters, + frontColumns, + backColumns, + frozenColumns, + hiddenColumns, + columnGroups, contextMenu, contextHeaderMenu, }: UITableProps): JSX.Element | null { @@ -43,6 +49,13 @@ export function UITable({ const [columns, setColumns] = useState(); const utils = useMemo(() => new IrisGridUtils(dh), [dh]); const settings = useSelector(getSettings); + const [layoutHints] = useState({ + frontColumns, + backColumns, + frozenColumns, + hiddenColumns, + columnGroups, + }); const hydratedSorts = useMemo(() => { if (sorts !== undefined && columns !== undefined) { @@ -80,7 +93,11 @@ export function UITable({ let isCancelled = false; async function loadModel() { const reexportedTable = await exportedTable.reexport(); - const newTable = (await reexportedTable.fetch()) as dh.Table; + const table = await reexportedTable.fetch(); + const newTable = new JsTableProxy({ + table: table as dh.Table, + layoutHints, + }); const newModel = await IrisGridModelFactory.makeModel(dh, newTable); if (!isCancelled) { setColumns(newTable.columns); @@ -93,7 +110,7 @@ export function UITable({ return () => { isCancelled = true; }; - }, [dh, exportedTable]); + }, [dh, exportedTable, layoutHints]); const mouseHandlers = useMemo( () => diff --git a/plugins/ui/src/js/src/elements/utils/UITableContextMenuHandler.ts b/plugins/ui/src/js/src/elements/UITable/UITableContextMenuHandler.ts similarity index 100% rename from plugins/ui/src/js/src/elements/utils/UITableContextMenuHandler.ts rename to plugins/ui/src/js/src/elements/UITable/UITableContextMenuHandler.ts diff --git a/plugins/ui/src/js/src/elements/utils/UITableMouseHandler.ts b/plugins/ui/src/js/src/elements/UITable/UITableMouseHandler.ts similarity index 100% rename from plugins/ui/src/js/src/elements/utils/UITableMouseHandler.ts rename to plugins/ui/src/js/src/elements/UITable/UITableMouseHandler.ts diff --git a/plugins/ui/src/js/src/elements/utils/UITableUtils.tsx b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx similarity index 92% rename from plugins/ui/src/js/src/elements/utils/UITableUtils.tsx rename to plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx index 3eb873357..abccb9162 100644 --- a/plugins/ui/src/js/src/elements/utils/UITableUtils.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx @@ -1,5 +1,5 @@ import type { dh } from '@deephaven/jsapi-types'; -import type { +import { ColumnName, DehydratedSort, IrisGridContextMenuData, @@ -9,8 +9,9 @@ import type { ResolvableContextAction, } from '@deephaven/components'; import { ensureArray } from '@deephaven/utils'; -import { ELEMENT_KEY, ElementNode, isElementNode } from './ElementUtils'; -import { getIcon } from './IconElementUtils'; +import { ELEMENT_KEY, ElementNode, isElementNode } from '../utils/ElementUtils'; + +import { getIcon } from '../utils/IconElementUtils'; import { ELEMENT_NAME, ELEMENT_PREFIX, @@ -53,7 +54,7 @@ type ResolvableUIContextItem = params: UIContextItemParams ) => Promise); -export interface UITableProps { +export type UITableProps = { table: dh.WidgetExportedObject; onCellPress?: (data: CellData) => void; onCellDoublePress?: (data: CellData) => void; @@ -66,10 +67,14 @@ export interface UITableProps { sorts?: DehydratedSort[]; showSearch: boolean; showQuickFilters: boolean; + frontColumns?: string[]; + backColumns?: string[]; + frozenColumns?: string[]; + hiddenColumns?: string[]; + columnGroups?: dh.ColumnGroup[]; contextMenu?: ResolvableUIContextItem | ResolvableUIContextItem[]; contextHeaderMenu?: ResolvableUIContextItem | ResolvableUIContextItem[]; - [key: string]: unknown; -} +}; export type UITableNode = Required< ElementNode diff --git a/plugins/ui/src/js/src/elements/index.ts b/plugins/ui/src/js/src/elements/index.ts index f4f51ab81..804669056 100644 --- a/plugins/ui/src/js/src/elements/index.ts +++ b/plugins/ui/src/js/src/elements/index.ts @@ -18,5 +18,5 @@ export * from './Slider'; export * from './Tabs'; export * from './TabPanels'; export * from './TextField'; -export * from './UITable'; +export * from './UITable/UITable'; export * from './utils'; diff --git a/plugins/ui/src/js/src/elements/utils/index.ts b/plugins/ui/src/js/src/elements/utils/index.ts index 69419497a..9945404e1 100644 --- a/plugins/ui/src/js/src/elements/utils/index.ts +++ b/plugins/ui/src/js/src/elements/utils/index.ts @@ -2,5 +2,5 @@ export * from './ElementUtils'; export * from './EventUtils'; export * from './HTMLElementUtils'; export * from './IconElementUtils'; -export * from './UITableMouseHandler'; -export * from './UITableUtils'; +export * from '../UITable/UITableMouseHandler'; +export * from '../UITable/UITableUtils'; diff --git a/plugins/ui/test/deephaven/ui/test_ui_table.py b/plugins/ui/test/deephaven/ui/test_ui_table.py index 86a9713b7..1a232dd85 100644 --- a/plugins/ui/test/deephaven/ui/test_ui_table.py +++ b/plugins/ui/test/deephaven/ui/test_ui_table.py @@ -218,3 +218,72 @@ def test_sort(self): ) self.assertRaises(ValueError, ui_table.sort, ["X", "Y"], ["INVALID"]) + + def test_front_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, front_columns=["X"]) + + self.expect_render( + t, + { + "frontColumns": ["X"], + }, + ) + + def test_back_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, back_columns=["X"]) + + self.expect_render( + t, + { + "backColumns": ["X"], + }, + ) + + def test_frozen_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, frozen_columns=["X"]) + + self.expect_render( + t, + { + "frozenColumns": ["X"], + }, + ) + + def test_hidden_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, hidden_columns=["X"]) + + self.expect_render( + t, + { + "hiddenColumns": ["X"], + }, + ) + + def test_column_groups(self): + import deephaven.ui as ui + + t = ui.table( + self.source, + column_groups=[{"name": "Group", "children": ["X"], "color": "red"}], + ) + + self.expect_render( + t, + { + "columnGroups": [ + { + "name": "Group", + "children": ["X"], + "color": "red", + } + ], + }, + )