Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/pl-data-table-default-sorting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@platforma-sdk/ui-vue": patch
---

Keep the default `sorting` passed to `createPlDataTableV3` alive

`resolveSorting` falls back to the block's default sorting only while the
persisted user sorting is `null` — `[]` means "the user cleared the sorting"
and deliberately suppresses the default. The grid state never produced that
`null`: AG Grid omits `sort` from its state while nothing is sorted, and
`convertAgSortingToPTableSorting` turned the absent state into `[]`.

`onStateUpdated` fires as soon as the grid initialises, so the first persisted
state entry stamped `sorting: []` and the block's default sorting was dropped
from that moment on — the sorted column stayed in the table, just unsorted.

The absent sort state now converts to `null`, and `normalizeSort` (next to the
existing `normalizeColumnVisibility`, which solves the same problem for hidden
columns) turns it into an explicit `{ sortModel: [] }` only once the grid has
reported an explicit sort model before — so clearing the sorting by hand still
suppresses the default. Existing projects recover on the next render without a
state migration: their stored `gridState.sort` is absent, which now reads as
"untouched".
47 changes: 43 additions & 4 deletions sdk/ui-vue/src/components/PlAgDataTable/PlAgDataTableV2.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const { gridApi, gridOptions } = useGrid({
let isReloading = false;
gridOptions.value.onGridPreDestroyed = (event) => {
if (!isReloading) {
gridOptions.value.initialState = gridState.value = normalizeColumnVisibility(
gridOptions.value.initialState = gridState.value = normalizeGridState(
makePartialState(event.api.getState()),
gridState.value,
event.api,
Expand All @@ -142,7 +142,7 @@ gridOptions.value.onRowDoubleClicked = (event) => {
if (event.data && event.data.axesKey) emit("rowDoubleClicked", event.data.axesKey);
};
gridOptions.value.onStateUpdated = (event) => {
const partialState = normalizeColumnVisibility(
const partialState = normalizeGridState(
makePartialState(event.state),
gridState.value,
event.api,
Expand Down Expand Up @@ -213,6 +213,42 @@ function makePartialState(state: GridState): PlDataTableGridStateCore {
};
}

// AG Grid omits parts of its state that carry no information — columnVisibility
// when every column is visible, sort when nothing is sorted. The model needs
// "no state yet" (fall back to the block's defaults) to stay distinguishable
// from "user explicitly cleared it" (store []), so both are normalized against
// the previous state before the state is persisted.
function normalizeGridState(
partialState: PlDataTableGridStateCore,
prevState: PlDataTableGridStateCore,
api: GridApi<PlAgDataTableV2Row>,
columnsMeta: PlDataTableColumnsMeta | undefined,
): PlDataTableGridStateCore {
return normalizeSort(
normalizeColumnVisibility(partialState, prevState, api, columnsMeta),
prevState,
);
}

// AG Grid returns sort: undefined when no column is sorted. Left as-is that
// erases the block's default sorting the moment any unrelated grid state is
// persisted, so the undefined is only turned into an explicit empty sort model
// once the user has actually sorted something before.
function normalizeSort(
partialState: PlDataTableGridStateCore,
prevState: PlDataTableGridStateCore,
): PlDataTableGridStateCore {
if (partialState.sort !== undefined) return partialState;

if (prevState.sort !== undefined) {
// Had an explicit sort model before → user cleared the sorting → store [].
return { ...partialState, sort: { sortModel: [] } };
}

// Never sorted → leave undefined so the model applies its default sorting.
return partialState;
}
Comment thread
AStaroverov marked this conversation as resolved.

// AG Grid returns columnVisibility: undefined when all columns are visible.
// We need to distinguish "no state yet" (use isColumnOptional defaults) from
// "user explicitly showed all columns" (store []). This function normalizes
Expand Down Expand Up @@ -264,11 +300,14 @@ function getDefaultHiddenColIds(
.map((col) => col.getColId() as PlTableColumnIdJson);
}

// Normalize columnVisibility for comparison: undefined and { hiddenColIds: [] } are equivalent.
// Normalize for comparison: an absent and an empty columnVisibility / sort mean
// the same thing to AG Grid, and must not count as a state change to reload on.
function stateForReloadCompare(state: PlDataTableGridStateCore): PlDataTableGridStateCore {
const cv = state.columnVisibility;
const normalizedCv = !cv || cv.hiddenColIds.length === 0 ? undefined : state.columnVisibility;
return { ...state, columnVisibility: normalizedCv };
const sort = state.sort;
const normalizedSort = !sort || sort.sortModel.length === 0 ? undefined : sort;
return { ...state, columnVisibility: normalizedCv, sort: normalizedSort };
}

// Reload AgGrid when new state arrives from server
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @vitest-environment happy-dom
import { describe, expect, it, vi } from "vitest";
import { computed } from "vue";

// The module pulls in uikit for `computedCached`; stub it so the test does not
// drag the whole component library (and its DOM-time side effects) in.
vi.mock("@milaboratories/uikit", () => ({
computedCached: computed,
}));

const { convertAgSortingToPTableSorting } = await import("./table-state-v2");

describe("convertAgSortingToPTableSorting", () => {
it("reports an absent sort state as untouched, so the model keeps its default sorting", () => {
expect(convertAgSortingToPTableSorting(undefined)).toBeNull();
});

it("reports an empty sort model as explicitly cleared, which suppresses the default", () => {
expect(convertAgSortingToPTableSorting({ sortModel: [] })).toEqual([]);
});

it("converts a sort model, taking NA/absent as least values only when ascending", () => {
const column = { type: "column", id: "colId" };
const colId = JSON.stringify(column) as never;
expect(
convertAgSortingToPTableSorting({
sortModel: [
{ colId, sort: "asc" },
{ colId, sort: "desc" },
],
}),
).toEqual([
{ column, ascending: true, naAndAbsentAreLeastValues: true },
{ column, ascending: false, naAndAbsentAreLeastValues: false },
]);
});
});
25 changes: 17 additions & 8 deletions sdk/ui-vue/src/components/PlAgDataTable/sources/table-state-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,23 @@ function convertPartitionFiltersToFilterSpec(
});
}

function convertAgSortingToPTableSorting(state: PlDataTableGridStateCore["sort"]): PTableSorting[] {
return (
state?.sortModel.map((item) => ({
column: parseJson<PTableColumnId>(item.colId),
ascending: item.sort === "asc",
naAndAbsentAreLeastValues: item.sort === "asc",
})) ?? []
);
/**
* AG Grid omits `sort` from its state while nothing is sorted, so the absent
* state must stay distinguishable from an explicitly emptied sort model: the
* model falls back to the block's default sorting on `null` only, and treats
* `[]` as "user cleared the sorting" (see `resolveSorting` in
* `createPlDataTableV3`). `normalizeSort` in `PlAgDataTableV2.vue` turns the
* absent state into `{ sortModel: [] }` once an explicit one has been seen.
*/
export function convertAgSortingToPTableSorting(
state: PlDataTableGridStateCore["sort"],
): PTableSorting[] | null {
if (isNil(state)) return null;
return state.sortModel.map((item) => ({
column: parseJson<PTableColumnId>(item.colId),
ascending: item.sort === "asc",
naAndAbsentAreLeastValues: item.sort === "asc",
}));
}

function getHiddenColIds(
Expand Down
Loading