@platforma-sdk/ui-vue 1.84.1 → 1.84.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +12 -11
- package/.turbo/turbo-formatter$colon$check.log +2 -2
- package/.turbo/turbo-linter$colon$check.log +2 -2
- package/.turbo/turbo-types$colon$check.log +1 -1
- package/CHANGELOG.md +120 -0
- package/dist/components/PlAgDataTable/PlAgDataTableV2.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.style.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js +175 -180
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js.map +1 -1
- package/dist/components/PlAgDataTable/__tests__/PlAgDataTableV2.reload.jsdomtest.d.ts +2 -0
- package/dist/components/PlAgDataTable/__tests__/PlAgDataTableV2.reload.jsdomtest.d.ts.map +1 -0
- package/dist/components/PlAgDataTable/sources/grid-state.d.ts +22 -0
- package/dist/components/PlAgDataTable/sources/grid-state.d.ts.map +1 -0
- package/dist/components/PlAgDataTable/sources/grid-state.js +39 -0
- package/dist/components/PlAgDataTable/sources/grid-state.js.map +1 -0
- package/dist/components/PlAgDataTable/sources/grid-state.test.d.ts +2 -0
- package/dist/components/PlAgDataTable/sources/grid-state.test.d.ts.map +1 -0
- package/dist/components/PlAgDataTable/sources/table-state-v2.d.ts.map +1 -1
- package/dist/components/PlAgDataTable/sources/table-state-v2.js +1 -0
- package/dist/components/PlAgDataTable/sources/table-state-v2.js.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js.map +1 -1
- package/package.json +8 -8
- package/src/components/PlAgDataTable/PlAgDataTableV2.vue +28 -16
- package/src/components/PlAgDataTable/__tests__/PlAgDataTableV2.reload.jsdomtest.ts +222 -0
- package/src/components/PlAgDataTable/sources/grid-state.test.ts +94 -0
- package/src/components/PlAgDataTable/sources/grid-state.ts +110 -0
- package/src/components/PlAgDataTable/sources/table-state-v2.ts +7 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The component recreates the whole AG Grid (`:key="reloadKey"`) when the stored
|
|
3
|
+
* grid state disagrees with the grid's own state, which is meant to converge
|
|
4
|
+
* after a single remount.
|
|
5
|
+
*
|
|
6
|
+
* It did not converge when the stored state travelled back through the platform:
|
|
7
|
+
* the write is debounced, and a block that is running has its ui state re-pushed
|
|
8
|
+
* meanwhile, so the state flipped between what the grid had and what the project
|
|
9
|
+
* still held. Every flip re-armed the comparison and the grid was recreated ~20
|
|
10
|
+
* times a second — the symptom being a strobing header and a console full of AG
|
|
11
|
+
* Grid licence banners.
|
|
12
|
+
*/
|
|
13
|
+
import { mount } from "@vue/test-utils";
|
|
14
|
+
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
15
|
+
import { defineComponent, h, onUnmounted, ref } from "vue";
|
|
16
|
+
|
|
17
|
+
const hoisted = vi.hoisted(() => ({ driver: {} as Record<string, unknown> }));
|
|
18
|
+
|
|
19
|
+
// The CSV exporter reaches the pf-spec WASM module, which cannot initialise
|
|
20
|
+
// under jsdom; nothing here exercises export.
|
|
21
|
+
vi.mock("@milaboratories/pf-spec-driver", () => ({ SpecDriver: class {} }));
|
|
22
|
+
vi.mock("@milaboratories/columns-collection-driver", () => ({
|
|
23
|
+
ColumnsCollectionDriverImpl: class {},
|
|
24
|
+
}));
|
|
25
|
+
vi.mock("@platforma-sdk/model", async (importOriginal) => {
|
|
26
|
+
const actual = await importOriginal<Record<string, unknown>>();
|
|
27
|
+
return { ...actual, getRawPlatformaInstance: () => ({ pFrameDriver: hoisted.driver }) };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const { activateAgGrid } = await import("../../../composition/AgGrid");
|
|
31
|
+
const { canonicalizeJson } = await import("@platforma-sdk/model");
|
|
32
|
+
const PlAgDataTableV2 = (await import("../PlAgDataTableV2.vue")).default;
|
|
33
|
+
|
|
34
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
35
|
+
type Any = Record<string, any>;
|
|
36
|
+
|
|
37
|
+
const axisSpec = { name: "sampleId", type: "String", annotations: { "pl7.app/label": "Sample" } };
|
|
38
|
+
const colSpec = (name: string): Any => ({
|
|
39
|
+
kind: "PColumn",
|
|
40
|
+
name,
|
|
41
|
+
valueType: "Int",
|
|
42
|
+
axesSpec: [axisSpec],
|
|
43
|
+
annotations: { "pl7.app/label": name },
|
|
44
|
+
});
|
|
45
|
+
const specs: Any[] = [
|
|
46
|
+
{ type: "axis", id: { name: "sampleId", type: "String" }, spec: axisSpec },
|
|
47
|
+
{ type: "column", id: "col-a", spec: colSpec("A") },
|
|
48
|
+
{ type: "column", id: "col-b", spec: colSpec("B") },
|
|
49
|
+
];
|
|
50
|
+
const columnId = (spec: Any) =>
|
|
51
|
+
canonicalizeJson({ type: "column", id: spec.id } as never) as string;
|
|
52
|
+
|
|
53
|
+
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
54
|
+
|
|
55
|
+
/** Counts AG Grid instantiations: every `createGrid` inserts one root wrapper. */
|
|
56
|
+
function countGrids() {
|
|
57
|
+
let created = 0;
|
|
58
|
+
const observer = new MutationObserver((records) => {
|
|
59
|
+
for (const record of records)
|
|
60
|
+
for (const node of record.addedNodes)
|
|
61
|
+
if (node instanceof Element && node.classList?.contains("ag-root-wrapper")) created++;
|
|
62
|
+
});
|
|
63
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
64
|
+
return { get: () => created, stop: () => observer.disconnect() };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Stands in for the block app plus the platform: a write from the table does not
|
|
69
|
+
* land in the prop straight away (it is debounced, then travels to the project
|
|
70
|
+
* state and back), and while a block runs the platform keeps re-pushing the
|
|
71
|
+
* state it currently holds.
|
|
72
|
+
*/
|
|
73
|
+
const Host = defineComponent({
|
|
74
|
+
props: {
|
|
75
|
+
settings: { type: Object, required: true },
|
|
76
|
+
initial: { type: Object, required: true },
|
|
77
|
+
echoMs: { type: Number, required: true },
|
|
78
|
+
pushEveryMs: { type: Number, required: true },
|
|
79
|
+
},
|
|
80
|
+
setup(props) {
|
|
81
|
+
const clone = (value: Any) => JSON.parse(JSON.stringify(value)) as Any;
|
|
82
|
+
const platform = { value: clone(props.initial) };
|
|
83
|
+
const pushed = ref(clone(platform.value));
|
|
84
|
+
const timer = setInterval(() => (pushed.value = clone(platform.value)), props.pushEveryMs);
|
|
85
|
+
onUnmounted(() => clearInterval(timer));
|
|
86
|
+
return () =>
|
|
87
|
+
h(PlAgDataTableV2 as Any, {
|
|
88
|
+
modelValue: pushed.value,
|
|
89
|
+
"onUpdate:modelValue": (state: Any) => {
|
|
90
|
+
const snapshot = clone(state);
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
platform.value = snapshot;
|
|
93
|
+
pushed.value = clone(snapshot);
|
|
94
|
+
}, props.echoMs);
|
|
95
|
+
},
|
|
96
|
+
settings: props.settings,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
beforeAll(() => activateAgGrid());
|
|
102
|
+
|
|
103
|
+
describe("PlAgDataTableV2", () => {
|
|
104
|
+
it("takes the loading overlay down when the model goes between handles", async () => {
|
|
105
|
+
// What a block re-running does to the table: it is already loading against one
|
|
106
|
+
// set of handles when the model drops them for a moment on its way to the next.
|
|
107
|
+
// That settings change starts no calculation of its own, so the one in flight is
|
|
108
|
+
// the only thing left that can clear the overlay.
|
|
109
|
+
hoisted.driver.getSpec = async () => {
|
|
110
|
+
await wait(150);
|
|
111
|
+
return specs;
|
|
112
|
+
};
|
|
113
|
+
hoisted.driver.getShape = async () => ({ rows: 0, columns: specs.length });
|
|
114
|
+
hoisted.driver.getData = async () => [];
|
|
115
|
+
|
|
116
|
+
const sourceId = "src-1";
|
|
117
|
+
const withHandles = (n: number) => ({
|
|
118
|
+
sourceId,
|
|
119
|
+
sheets: [],
|
|
120
|
+
model: {
|
|
121
|
+
sourceId,
|
|
122
|
+
fullTableHandle: `full-${n}`,
|
|
123
|
+
visibleTableHandle: `visible-${n}`,
|
|
124
|
+
fullPframeHandle: `pf-${n}`,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
const betweenHandles = { sourceId, sheets: [], model: { sourceId } };
|
|
128
|
+
|
|
129
|
+
const wrapper = mount(Host, {
|
|
130
|
+
attachTo: document.body,
|
|
131
|
+
props: {
|
|
132
|
+
settings: withHandles(1),
|
|
133
|
+
initial: {
|
|
134
|
+
version: 8,
|
|
135
|
+
stateCache: [],
|
|
136
|
+
pTableParams: {
|
|
137
|
+
sourceId,
|
|
138
|
+
hiddenColIds: null,
|
|
139
|
+
sorting: null,
|
|
140
|
+
filters: null,
|
|
141
|
+
defaultFilters: null,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
echoMs: 0,
|
|
145
|
+
pushEveryMs: 1000,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await wait(50); // the first calculation is in flight
|
|
150
|
+
await wrapper.setProps({ settings: betweenHandles });
|
|
151
|
+
await wait(900);
|
|
152
|
+
|
|
153
|
+
const overlay = (document.body.textContent || "").includes("Loading data...");
|
|
154
|
+
wrapper.unmount();
|
|
155
|
+
document.body.innerHTML = "";
|
|
156
|
+
expect(overlay).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("stops recreating the grid once the stored state has been applied", async () => {
|
|
160
|
+
hoisted.driver.getSpec = async () => {
|
|
161
|
+
await wait(40);
|
|
162
|
+
return specs;
|
|
163
|
+
};
|
|
164
|
+
hoisted.driver.getShape = async () => ({ rows: 0, columns: specs.length });
|
|
165
|
+
hoisted.driver.getData = async () => [];
|
|
166
|
+
|
|
167
|
+
const sourceId = "src-1";
|
|
168
|
+
const counter = countGrids();
|
|
169
|
+
const wrapper = mount(Host, {
|
|
170
|
+
attachTo: document.body,
|
|
171
|
+
props: {
|
|
172
|
+
// A block that has just been rebuilt: still running, model not there yet.
|
|
173
|
+
settings: { sourceId, sheets: [], model: undefined },
|
|
174
|
+
initial: {
|
|
175
|
+
version: 8,
|
|
176
|
+
stateCache: [
|
|
177
|
+
{
|
|
178
|
+
sourceId,
|
|
179
|
+
// A sort the user left behind in a previous session.
|
|
180
|
+
gridState: { sort: { sortModel: [{ colId: columnId(specs[1]), sort: "asc" }] } },
|
|
181
|
+
sheetsState: [],
|
|
182
|
+
filtersState: null,
|
|
183
|
+
defaultFiltersState: null,
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
pTableParams: {
|
|
187
|
+
sourceId,
|
|
188
|
+
hiddenColIds: null,
|
|
189
|
+
sorting: null,
|
|
190
|
+
filters: null,
|
|
191
|
+
defaultFilters: null,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
echoMs: 400,
|
|
195
|
+
pushEveryMs: 50,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await wait(20);
|
|
200
|
+
await wrapper.setProps({
|
|
201
|
+
settings: {
|
|
202
|
+
sourceId,
|
|
203
|
+
sheets: [],
|
|
204
|
+
model: {
|
|
205
|
+
sourceId,
|
|
206
|
+
fullTableHandle: "full-1",
|
|
207
|
+
visibleTableHandle: "visible-1",
|
|
208
|
+
fullPframeHandle: "pf-1",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
await wait(1200);
|
|
213
|
+
|
|
214
|
+
const created = counter.get();
|
|
215
|
+
counter.stop();
|
|
216
|
+
wrapper.unmount();
|
|
217
|
+
document.body.innerHTML = "";
|
|
218
|
+
|
|
219
|
+
// One grid for the mount, at most one more to apply the stored sort.
|
|
220
|
+
expect(created).toBeLessThanOrEqual(2);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { PlDataTableGridStateCore, PlTableColumnIdJson } from "@platforma-sdk/model";
|
|
3
|
+
import { isStoredStateApplied } from "./grid-state";
|
|
4
|
+
|
|
5
|
+
const colA = '{"id":"a","type":"column"}' as PlTableColumnIdJson;
|
|
6
|
+
const colB = '{"id":"b","type":"column"}' as PlTableColumnIdJson;
|
|
7
|
+
const dropped = '{"id":"gone","type":"column"}' as PlTableColumnIdJson;
|
|
8
|
+
|
|
9
|
+
const grid = new Set<PlTableColumnIdJson>([colA, colB]);
|
|
10
|
+
const noColumns = new Set<PlTableColumnIdJson>();
|
|
11
|
+
|
|
12
|
+
const order = (...ids: PlTableColumnIdJson[]): PlDataTableGridStateCore => ({
|
|
13
|
+
columnOrder: { orderedColIds: ids },
|
|
14
|
+
});
|
|
15
|
+
const hidden = (...ids: PlTableColumnIdJson[]): PlDataTableGridStateCore => ({
|
|
16
|
+
columnVisibility: { hiddenColIds: ids },
|
|
17
|
+
});
|
|
18
|
+
const sorted = (id: PlTableColumnIdJson): PlDataTableGridStateCore => ({
|
|
19
|
+
sort: { sortModel: [{ colId: id, sort: "asc" }] },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("isStoredStateApplied", () => {
|
|
23
|
+
// Every case below is one a rebuild could not satisfy, so counting it as a
|
|
24
|
+
// disagreement makes the reload watch rebuild the grid forever.
|
|
25
|
+
describe("does not ask for what a rebuild cannot deliver", () => {
|
|
26
|
+
it("ignores a field the stored state says nothing about", () => {
|
|
27
|
+
expect(
|
|
28
|
+
isStoredStateApplied(hidden(colA), { ...hidden(colA), ...order(colA, colB) }, grid),
|
|
29
|
+
).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("ignores a column order naming a column the grid no longer has", () => {
|
|
33
|
+
expect(isStoredStateApplied(order(colA, dropped, colB), order(colA, colB), grid)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("ignores a hidden column the grid no longer has", () => {
|
|
37
|
+
expect(isStoredStateApplied(hidden(colA, dropped), hidden(colA), grid)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("ignores a sort on a column the grid no longer has", () => {
|
|
41
|
+
expect(isStoredStateApplied(sorted(dropped), {}, grid)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("asks for nothing while the grid has no columns at all", () => {
|
|
45
|
+
expect(isStoredStateApplied({ ...order(colA, colB), ...hidden(colA) }, {}, noColumns)).toBe(
|
|
46
|
+
true,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("asks for nothing when the stored state is empty", () => {
|
|
51
|
+
expect(isStoredStateApplied({}, order(colA), grid)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("still asks for what a rebuild can deliver", () => {
|
|
56
|
+
it("reloads for a different hidden set", () => {
|
|
57
|
+
expect(isStoredStateApplied(hidden(colA), hidden(colB), grid)).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("reloads for a different order of columns it knows", () => {
|
|
61
|
+
expect(isStoredStateApplied(order(colA, colB), order(colB, colA), grid)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("reloads for a different sort", () => {
|
|
65
|
+
expect(isStoredStateApplied(sorted(colA), sorted(colB), grid)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("ignores where the grid puts columns the stored order does not mention", () => {
|
|
69
|
+
expect(isStoredStateApplied(order(colA, colB), order(colA, dropped, colB), grid)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("an explicitly cleared stored state is an opinion, not silence", () => {
|
|
74
|
+
it("reloads when the stored state cleared the sorting and the grid is still sorted", () => {
|
|
75
|
+
expect(isStoredStateApplied({ sort: { sortModel: [] } }, sorted(colA), grid)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("reloads when the stored state shows every column and the grid still hides one", () => {
|
|
79
|
+
expect(isStoredStateApplied(hidden(), hidden(colA), grid)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("treats a state the grid reported as absent or empty alike", () => {
|
|
84
|
+
it("for columnVisibility", () => {
|
|
85
|
+
expect(isStoredStateApplied(hidden(), { columnVisibility: undefined }, grid)).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("for sort", () => {
|
|
89
|
+
expect(isStoredStateApplied({ sort: { sortModel: [] } }, { sort: undefined }, grid)).toBe(
|
|
90
|
+
true,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { isJsonEqual } from "@milaboratories/helpers";
|
|
2
|
+
import type { PlDataTableGridStateCore, PlTableColumnIdJson } from "@platforma-sdk/model";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Whether the grid already shows everything the stored state asks for that it
|
|
6
|
+
* could possibly show.
|
|
7
|
+
*
|
|
8
|
+
* The answer decides whether to destroy and rebuild the grid around the stored
|
|
9
|
+
* state, so it must only ever ask for things a rebuild can deliver. Two kinds of
|
|
10
|
+
* request cannot be delivered, and asking anyway is how this comparison became an
|
|
11
|
+
* engine for endless rebuilds:
|
|
12
|
+
*
|
|
13
|
+
* - a field the stored state does not express. AG Grid always reports its whole
|
|
14
|
+
* state, and a state saved while the grid had no columns carries no
|
|
15
|
+
* `columnOrder` at all; demanding equality there asks the grid to unreport
|
|
16
|
+
* something.
|
|
17
|
+
* - an id the grid does not have. State outlives column sets — a sort on a
|
|
18
|
+
* column a later run dropped, hidden ids saved against different columns — and
|
|
19
|
+
* no rebuild brings those columns back.
|
|
20
|
+
*
|
|
21
|
+
* Both are dropped before comparing rather than guarded against afterwards.
|
|
22
|
+
*/
|
|
23
|
+
export function isStoredStateApplied(
|
|
24
|
+
desired: PlDataTableGridStateCore,
|
|
25
|
+
actual: PlDataTableGridStateCore,
|
|
26
|
+
gridColIds: ReadonlySet<PlTableColumnIdJson>,
|
|
27
|
+
): boolean {
|
|
28
|
+
const {
|
|
29
|
+
orderedColIds: desiredOrder,
|
|
30
|
+
hiddenColIds: desiredHidden,
|
|
31
|
+
sortModel: desiredSort,
|
|
32
|
+
} = dropColumnsTheGridDoesNotHave(desired, gridColIds);
|
|
33
|
+
const {
|
|
34
|
+
orderedColIds: actualOrder,
|
|
35
|
+
hiddenColIds: actualHidden,
|
|
36
|
+
sortModel: actualSort,
|
|
37
|
+
} = normalizeReportedState(actual);
|
|
38
|
+
|
|
39
|
+
if (desiredHidden !== undefined && !isJsonEqual(desiredHidden, actualHidden)) return false;
|
|
40
|
+
|
|
41
|
+
if (desiredSort !== undefined && !isJsonEqual(desiredSort, actualSort)) return false;
|
|
42
|
+
|
|
43
|
+
if (desiredOrder !== undefined) {
|
|
44
|
+
// Only the relative order of the columns it names: the grid may hold others,
|
|
45
|
+
// and where it puts them is not being asked about.
|
|
46
|
+
const desiredColumns = new Set(desiredOrder);
|
|
47
|
+
const actualOrderOfThose = actualOrder.filter((id) => desiredColumns.has(id));
|
|
48
|
+
if (!isJsonEqual(desiredOrder, actualOrderOfThose)) return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read a state the grid reported the way the grid means it.
|
|
56
|
+
*
|
|
57
|
+
* AG Grid leaves out what carries no information — no `sort` while nothing is
|
|
58
|
+
* sorted, no `columnVisibility` while every column is shown — so absent and
|
|
59
|
+
* empty mean the same thing coming from it, and hidden ids arrive in whatever
|
|
60
|
+
* order it happens to hold them.
|
|
61
|
+
*
|
|
62
|
+
* The stored state is deliberately NOT read this way: there an empty list is the
|
|
63
|
+
* user having cleared the sorting or shown every column, and the grid has to be
|
|
64
|
+
* made to match it.
|
|
65
|
+
*/
|
|
66
|
+
function normalizeReportedState(state: PlDataTableGridStateCore) {
|
|
67
|
+
return {
|
|
68
|
+
orderedColIds: state.columnOrder?.orderedColIds ?? [],
|
|
69
|
+
hiddenColIds: sortIds(state.columnVisibility?.hiddenColIds ?? []),
|
|
70
|
+
sortModel: state.sort?.sortModel ?? [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Strike out every column the stored state names that the grid does not have, so
|
|
76
|
+
* what is left is what the grid can still be asked about.
|
|
77
|
+
*/
|
|
78
|
+
function dropColumnsTheGridDoesNotHave(
|
|
79
|
+
state: PlDataTableGridStateCore,
|
|
80
|
+
gridColIds: ReadonlySet<PlTableColumnIdJson>,
|
|
81
|
+
) {
|
|
82
|
+
const inGrid = (id: PlTableColumnIdJson) => gridColIds.has(id);
|
|
83
|
+
const hidden = keepColumnsInGrid(state.columnVisibility?.hiddenColIds, inGrid);
|
|
84
|
+
return {
|
|
85
|
+
orderedColIds: keepColumnsInGrid(state.columnOrder?.orderedColIds, inGrid),
|
|
86
|
+
hiddenColIds: hidden && sortIds(hidden),
|
|
87
|
+
sortModel: keepColumnsInGrid(state.sort?.sortModel, (item) => inGrid(item.colId)),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The entries naming a column the grid has, or `undefined` when there is nothing
|
|
93
|
+
* left to ask about: either the field was never expressed, or every column it
|
|
94
|
+
* named is gone and no rebuild brings those back. An explicitly empty list is an
|
|
95
|
+
* opinion — "sorted by nothing", "every column visible" — and survives as one.
|
|
96
|
+
*/
|
|
97
|
+
function keepColumnsInGrid<T>(
|
|
98
|
+
entries: T[] | undefined,
|
|
99
|
+
inGrid: (entry: T) => boolean,
|
|
100
|
+
): T[] | undefined {
|
|
101
|
+
if (entries === undefined) return undefined;
|
|
102
|
+
const kept = entries.filter(inGrid);
|
|
103
|
+
if (entries.length > 0 && kept.length === 0) return undefined;
|
|
104
|
+
return kept;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Hidden columns are a set; put them in one order so either side compares alike. */
|
|
108
|
+
function sortIds(ids: PlTableColumnIdJson[]): PlTableColumnIdJson[] {
|
|
109
|
+
return [...ids].sort();
|
|
110
|
+
}
|
|
@@ -68,6 +68,13 @@ export function useTableState(
|
|
|
68
68
|
set: (state) => {
|
|
69
69
|
const newState: PlDataTableStateV2Normalized = {
|
|
70
70
|
...tableStateNormalized.value,
|
|
71
|
+
// Copy the cache before editing it below: spreading keeps the same array,
|
|
72
|
+
// and splicing it in place would edit the current state as well — the
|
|
73
|
+
// change check at the end would then compare the new state against
|
|
74
|
+
// itself, find them equal, and skip the assignment, so a change that
|
|
75
|
+
// leaves `pTableParams` untouched (reordering columns, say) would never
|
|
76
|
+
// be written back to the project.
|
|
77
|
+
stateCache: [...tableStateNormalized.value.stateCache],
|
|
71
78
|
pTableParams: createDefaultPTableParams(),
|
|
72
79
|
};
|
|
73
80
|
|