diff --git a/packages/viewer-datagrid/src/ts/custom_elements/datagrid.ts b/packages/viewer-datagrid/src/ts/custom_elements/datagrid.ts index 8e1597b430..7fd3d10e08 100644 --- a/packages/viewer-datagrid/src/ts/custom_elements/datagrid.ts +++ b/packages/viewer-datagrid/src/ts/custom_elements/datagrid.ts @@ -29,6 +29,7 @@ import { sourceColumn } from "@perspective-dev/viewer/src/ts/column-format.js"; import type { View, ViewWindow } from "@perspective-dev/client"; import type { + HTMLPerspectiveViewerElement, IPerspectiveViewerPlugin, PluginStaticConfig, } from "@perspective-dev/viewer"; @@ -73,6 +74,50 @@ export class HTMLPerspectiveViewerDatagridPluginElement _reset_scroll_left?: boolean; _reset_select?: boolean; _reset_column_size?: boolean; + _columns_config: ColumnsConfig = {}; + + private _persist_column_sizes = (): void => { + if (!this.model || this.model._config.split_by?.length > 0) { + return; + } + + const columns_config = structuredClone(this._columns_config); + for (const column of [...this.model._column_paths, "__ROW_PATH__"]) { + if (columns_config[column]) { + delete columns_config[column].column_size_override; + } + } + + const overrides = save_column_size_overrides.call(this); + for (const [column, width] of Object.entries(overrides)) { + if (width !== undefined) { + columns_config[column] ??= {}; + columns_config[column].column_size_override = width; + } + } + + this._columns_config = columns_config; + const viewer = this.parentElement as HTMLPerspectiveViewerElement; + void viewer?.restore?.( + { columns_config: JSON.parse(JSON.stringify(columns_config)) }, + { panel: this.model._panel }, + ); + }; + + private _on_column_resize = (event: MouseEvent): void => { + const is_resize = event.composedPath().some((target) => { + return ( + target instanceof HTMLElement && + target.classList.contains("rt-column-resize") + ); + }); + + if (is_resize) { + document.addEventListener("mouseup", this._persist_column_sizes, { + once: true, + }); + } + }; constructor() { super(); @@ -101,6 +146,11 @@ export class HTMLPerspectiveViewerDatagridPluginElement } connectedCallback(): void { + this.regular_table.addEventListener( + "mousedown", + this._on_column_resize, + ); + if (!this._toolbar) { this._toolbar = document.createElement( "perspective-viewer-datagrid-toolbar", @@ -113,6 +163,11 @@ export class HTMLPerspectiveViewerDatagridPluginElement } disconnectedCallback(): void { + this.regular_table.removeEventListener( + "mousedown", + this._on_column_resize, + ); + document.removeEventListener("mouseup", this._persist_column_sizes); this._toolbar?.parentElement?.removeChild?.(this._toolbar); } @@ -173,7 +228,11 @@ export class HTMLPerspectiveViewerDatagridPluginElement group: string | undefined, column_name: string, current_value: Record | null, - viewer_config?: { group_by?: string[]; group_rollup_mode?: string }, + viewer_config?: { + group_by?: string[]; + split_by?: string[]; + group_rollup_mode?: string; + }, column_stats?: { abs_max: number }, ): ColumnConfigSchema { return column_config_schema.call( diff --git a/packages/viewer-datagrid/src/ts/model/column_overrides.ts b/packages/viewer-datagrid/src/ts/model/column_overrides.ts index 69eb90dcf6..6f7f64e2cf 100644 --- a/packages/viewer-datagrid/src/ts/model/column_overrides.ts +++ b/packages/viewer-datagrid/src/ts/model/column_overrides.ts @@ -34,14 +34,14 @@ export function restore_column_size_overrides( old_sizes: ColumnOverrides, cache = false, ): void { - if (!this._initialized) { - return; - } - if (cache) { this._cached_column_sizes = old_sizes; } + if (!this._initialized) { + return; + } + const regular_table = this.regular_table as RegularTableWithOverrides; const overrides: Record = { ...regular_table.saveColumnSizes(), diff --git a/packages/viewer-datagrid/src/ts/plugin/column_config_schema.ts b/packages/viewer-datagrid/src/ts/plugin/column_config_schema.ts index b0a7800a09..4c83239a6b 100644 --- a/packages/viewer-datagrid/src/ts/plugin/column_config_schema.ts +++ b/packages/viewer-datagrid/src/ts/plugin/column_config_schema.ts @@ -15,6 +15,7 @@ import type { ColumnConfig, DatagridPluginElement } from "../types.js"; interface ViewerConfigLike { group_by?: string[]; + split_by?: string[]; group_rollup_mode?: string; } @@ -53,6 +54,15 @@ export default function column_config_schema( ): ColumnConfigSchema { const fields: ControlSpec[] = []; + if ((viewer_config?.split_by?.length ?? 0) === 0) { + fields.push({ + kind: "Number", + key: "column_size_override" satisfies keyof ColumnConfig, + default: 0, + min: 1, + }); + } + if (type === "integer" || type === "float") { const pos_fg = this.model!._pos_fg_color[0]; const neg_fg = this.model!._neg_fg_color[0]; diff --git a/packages/viewer-datagrid/src/ts/plugin/draw.ts b/packages/viewer-datagrid/src/ts/plugin/draw.ts index db486b3fb1..32a886c8b0 100644 --- a/packages/viewer-datagrid/src/ts/plugin/draw.ts +++ b/packages/viewer-datagrid/src/ts/plugin/draw.ts @@ -62,6 +62,9 @@ export async function draw( restore_column_size_overrides.call(this, old_sizes); await drawPromise; + if (Object.keys(old_sizes).length > 0) { + restore_column_size_overrides.call(this, old_sizes); + } this._toolbar?.classList.toggle( "aggregated", diff --git a/packages/viewer-datagrid/src/ts/plugin/restore.ts b/packages/viewer-datagrid/src/ts/plugin/restore.ts index 6379033f0d..5f77b3a520 100644 --- a/packages/viewer-datagrid/src/ts/plugin/restore.ts +++ b/packages/viewer-datagrid/src/ts/plugin/restore.ts @@ -27,7 +27,6 @@ import type { } from "../types.js"; interface RestoreToken { - columns?: Record; edit_mode?: EditMode; scroll_lock?: boolean; } @@ -59,15 +58,18 @@ export function restore( columns = JSON.parse(JSON.stringify(columns)); const overrides: ColumnOverrides = {}; - if (token.columns) { - for (const [col, value] of Object.entries(token.columns)) { - if (value.column_size_override !== undefined) { + for (const [col, value] of Object.entries(columns)) { + if (value.column_size_override !== undefined) { + if (!this.model?._config.split_by?.length) { overrides[col] = value.column_size_override; - delete value["column_size_override"]; } + + delete value.column_size_override; } } + this._columns_config = structuredClone(columns); + const styles: Record = {}; if (columns) { for (const [col_name, controls] of Object.entries(columns)) { diff --git a/packages/viewer-datagrid/src/ts/plugin/save.ts b/packages/viewer-datagrid/src/ts/plugin/save.ts index da3cbca184..13825153e3 100644 --- a/packages/viewer-datagrid/src/ts/plugin/save.ts +++ b/packages/viewer-datagrid/src/ts/plugin/save.ts @@ -10,7 +10,6 @@ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -import { save_column_size_overrides } from "../model/column_overrides.js"; import type { DatagridPluginElement, DatagridPluginConfig } from "../types.js"; export function save( @@ -18,23 +17,10 @@ export function save( ): DatagridPluginConfig | Record { if (this.regular_table) { const token: DatagridPluginConfig = { - columns: {}, scroll_lock: !!this._is_scroll_lock, edit_mode: this._edit_mode, }; - const column_size_overrides = save_column_size_overrides.call(this); - - for (const col of Object.keys(column_size_overrides || {})) { - if (!token.columns?.[col]) { - token.columns = token.columns || {}; - token.columns[col] = {}; - } - - token.columns[col].column_size_override = - column_size_overrides[col]; - } - return JSON.parse(JSON.stringify(token)); } diff --git a/packages/viewer-datagrid/src/ts/types.ts b/packages/viewer-datagrid/src/ts/types.ts index b4b66ab312..1276164027 100644 --- a/packages/viewer-datagrid/src/ts/types.ts +++ b/packages/viewer-datagrid/src/ts/types.ts @@ -198,12 +198,6 @@ export type ColumnsConfig = Record; * `save()`/`restore()` token). */ export interface DatagridPluginConfig { - /** - * Per-column state keyed by column name, e.g. - * `{ "Sales": { "column_size_override": 180 } }`. - */ - columns?: ColumnsConfig; - /** Legacy alias for `edit_mode: "EDIT"`. */ editable?: boolean; @@ -342,6 +336,7 @@ export type SortRotationOrder = Record; export interface DatagridPluginElement extends HTMLElement { regular_table: RegularTableElement; model?: DatagridModel; + _columns_config: ColumnsConfig; _toolbar?: DatagridToolbarElement; _edit_button?: HTMLElement; _scroll_lock?: HTMLElement; diff --git a/packages/viewer-datagrid/test/js/column_settings.spec.ts b/packages/viewer-datagrid/test/js/column_settings.spec.ts index 771cc6f0d7..4116b8cb8d 100644 --- a/packages/viewer-datagrid/test/js/column_settings.spec.ts +++ b/packages/viewer-datagrid/test/js/column_settings.spec.ts @@ -27,7 +27,7 @@ test.describe("Datagrid Column Styles", function () { }); }); - test.skip("Interacting with column settings does not override column width", async function ({ + test("Interacting with column settings does not override column width", async function ({ page, }) { const view = new PspViewer(page); @@ -41,6 +41,14 @@ test.describe("Datagrid Column Styles", function () { await page.mouse.down(); await page.mouse.move(pos!.x + 100, pos!.y + 5); await page.mouse.up(); + await page.waitForFunction(async () => { + const viewer = document.querySelector("perspective-viewer")!; + const token = await viewer.save(); + return ( + token.columns_config?.["Row ID"]?.column_size_override !== + undefined + ); + }); const editBtn = view.dataGrid.regularTable.editBtnRow .locator("th.psp-menu-enabled span") @@ -54,16 +62,64 @@ test.describe("Datagrid Column Styles", function () { const token = await view.save(); test.expect(token.columns_config).toEqual({ "Row ID": { + column_size_override: 150, number_string_format: { style: "percent", }, }, }); - test.expect(token.plugin_config.columns).toEqual({ - "Row ID": { - column_size_override: 150, - }, + test.expect(token.plugin_config.columns).toBeUndefined(); + }); + + test("First restore applies and preserves column width overrides", async function ({ + page, + }) { + const result = await page.evaluate(async () => { + const viewer = document.querySelector("perspective-viewer")!; + await viewer.restore({ + plugin: "Datagrid", + columns: ["Row ID", "Sales"], + columns_config: { + Sales: { column_size_override: 311.1875 }, + }, + }); + + const plugin = document.querySelector( + "perspective-viewer-datagrid", + ) as any; + const index = plugin.model._column_paths.indexOf("Sales"); + const widths = plugin.regular_table.saveColumnSizes(); + + return { + config: (await viewer.save()).columns_config, + width: widths[index], + }; + }); + + expect(result.config).toEqual({ + Sales: { column_size_override: 311.1875 }, + }); + expect(result.width).toBe(311.1875); + }); + + test("Column width persistence is disabled with split-by", async function ({ + page, + }) { + const result = await page.evaluate(async () => { + const viewer = document.querySelector("perspective-viewer")!; + await viewer.restore({ + plugin: "Datagrid", + columns: ["Row ID", "Sales"], + split_by: ["Category"], + columns_config: { + Sales: { column_size_override: 311.1875 }, + }, + }); + + return (await viewer.save()).columns_config?.Sales; }); + + expect(result?.column_size_override).toBeUndefined(); }); });