Skip to content

Commit 3a4e40b

Browse files
BrianHunglukasmasuchCopilot
authored
feat: expose compact selection items and create static method (#1073)
* feat: expose compact selection items * feat: create compact selection from selection ranges * add test cases * Add tests * Update packages/core/src/internal/data-grid/data-grid-types.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Lukas Masuch <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 0a1552c commit 3a4e40b

File tree

4 files changed

+423
-109
lines changed

4 files changed

+423
-109
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import React from "react";
2+
import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3+
import {
4+
BeautifulWrapper,
5+
Description,
6+
PropName,
7+
useMockDataGenerator,
8+
} from "../../data-editor/stories/utils.js";
9+
import type { GridSelection, CompactSelectionRanges } from "../../internal/data-grid/data-grid-types.js";
10+
import { CompactSelection } from "../../internal/data-grid/data-grid-types.js";
11+
import { SimpleThemeWrapper } from "../../stories/story-utils.js";
12+
13+
export default {
14+
title: "Glide-Data-Grid/DataEditor Demos",
15+
16+
decorators: [
17+
(Story: React.ComponentType) => (
18+
<SimpleThemeWrapper>
19+
<Story />
20+
</SimpleThemeWrapper>
21+
),
22+
],
23+
};
24+
25+
export const SelectionSerialization: React.VFC = () => {
26+
const { cols, getCellContent } = useMockDataGenerator(30, true, true);
27+
28+
// Load selection from localStorage on mount
29+
const [selection, setSelection] = React.useState<GridSelection>(() => {
30+
try {
31+
const saved = localStorage.getItem("grid-selection-demo");
32+
if (saved !== null) {
33+
const parsed = JSON.parse(saved) as { columns?: any[]; rows?: any[]; current?: any };
34+
return {
35+
columns: CompactSelection.create(Array.isArray(parsed.columns) ? parsed.columns : []),
36+
rows: CompactSelection.create(Array.isArray(parsed.rows) ? parsed.rows : []),
37+
current: parsed.current,
38+
};
39+
}
40+
} catch (error) {
41+
console.error("Failed to restore selection", error);
42+
}
43+
return {
44+
columns: CompactSelection.empty(),
45+
rows: CompactSelection.empty(),
46+
};
47+
});
48+
49+
// Save selection to localStorage whenever it changes
50+
React.useEffect(() => {
51+
const toSave = {
52+
columns: selection.columns.items,
53+
rows: selection.rows.items,
54+
current: selection.current,
55+
};
56+
localStorage.setItem("grid-selection-demo", JSON.stringify(toSave));
57+
}, [selection]);
58+
59+
const clearSelection = () => {
60+
setSelection({
61+
columns: CompactSelection.empty(),
62+
rows: CompactSelection.empty(),
63+
current: undefined,
64+
});
65+
};
66+
67+
const createExampleSelection = () => {
68+
setSelection({
69+
columns: CompactSelection.create([[2, 5], [8, 10]]),
70+
rows: CompactSelection.create([[1, 4], [10, 15], [20, 23]]),
71+
current: {
72+
cell: [3, 5],
73+
range: { x: 3, y: 5, width: 1, height: 1 },
74+
rangeStack: [],
75+
},
76+
});
77+
};
78+
79+
return (
80+
<BeautifulWrapper
81+
title="Selection Serialization"
82+
description={
83+
<Description>
84+
This example demonstrates how to serialize and persist grid selections using the new{" "}
85+
<PropName>CompactSelection.create()</PropName> and <PropName>.items</PropName> APIs.
86+
The selection is automatically saved to localStorage and restored when the page refreshes.
87+
<br />
88+
<br />
89+
<button onClick={createExampleSelection} style={{ marginRight: 8 }}>
90+
Create Example Selection
91+
</button>
92+
<button onClick={clearSelection}>Clear Selection</button>
93+
<br />
94+
<br />
95+
<strong>Current selection:</strong> {selection.rows.length} rows, {selection.columns.length} columns
96+
<br />
97+
<strong>Persisted data:</strong> <code>{JSON.stringify({ columns: selection.columns.items, rows: selection.rows.items })}</code>
98+
</Description>
99+
}>
100+
<DataEditor
101+
{...useMockDataGenerator(30, false)}
102+
columns={cols}
103+
getCellContent={getCellContent}
104+
rows={10_000}
105+
gridSelection={selection}
106+
onGridSelectionChange={setSelection}
107+
rowMarkers="both"
108+
columnSelect="multi"
109+
/>
110+
</BeautifulWrapper>
111+
);
112+
};
113+
114+
export const SelectionRoundTrip: React.VFC = () => {
115+
const { cols, getCellContent } = useMockDataGenerator(30, true, true);
116+
117+
const [originalSelection, setOriginalSelection] = React.useState<GridSelection>({
118+
columns: CompactSelection.empty(),
119+
rows: CompactSelection.empty(),
120+
});
121+
122+
const [restoredSelection, setRestoredSelection] = React.useState<GridSelection>({
123+
columns: CompactSelection.empty(),
124+
rows: CompactSelection.empty(),
125+
});
126+
127+
const performRoundTrip = () => {
128+
// Serialize the selection
129+
const serialized = {
130+
columns: originalSelection.columns.items,
131+
rows: originalSelection.rows.items,
132+
current: originalSelection.current,
133+
};
134+
135+
// Simulate persistence (e.g., sending to server, storing in database)
136+
const jsonString = JSON.stringify(serialized);
137+
console.log("Serialized selection:", jsonString);
138+
139+
// Deserialize and restore
140+
const parsed = JSON.parse(jsonString) as { columns: CompactSelectionRanges; rows: CompactSelectionRanges; current?: any };
141+
const restored = {
142+
columns: CompactSelection.create(parsed.columns),
143+
rows: CompactSelection.create(parsed.rows),
144+
current: parsed.current,
145+
};
146+
147+
setRestoredSelection(restored);
148+
};
149+
150+
return (
151+
<BeautifulWrapper
152+
title="Selection Round Trip"
153+
description={
154+
<Description>
155+
This example demonstrates a complete round trip: create a selection, serialize it to JSON,
156+
then deserialize it back to a <PropName>CompactSelection</PropName> using the new APIs.
157+
<br />
158+
<br />
159+
<button onClick={performRoundTrip}>Perform Round Trip</button>
160+
<br />
161+
<br />
162+
<strong>Original equals restored:</strong> {originalSelection.columns.equals(restoredSelection.columns) && originalSelection.rows.equals(restoredSelection.rows) ? "✅ Yes" : "❌ No"}
163+
</Description>
164+
}>
165+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, height: 600 }}>
166+
<div>
167+
<h3>Original Selection</h3>
168+
<DataEditor
169+
{...useMockDataGenerator(30, false)}
170+
columns={cols}
171+
getCellContent={getCellContent}
172+
rows={1000}
173+
gridSelection={originalSelection}
174+
onGridSelectionChange={setOriginalSelection}
175+
rowMarkers="both"
176+
columnSelect="multi"
177+
/>
178+
</div>
179+
<div>
180+
<h3>Restored Selection</h3>
181+
<DataEditor
182+
{...useMockDataGenerator(30, false)}
183+
columns={cols}
184+
getCellContent={getCellContent}
185+
rows={1000}
186+
gridSelection={restoredSelection}
187+
onGridSelectionChange={setRestoredSelection}
188+
rowMarkers="both"
189+
columnSelect="multi"
190+
/>
191+
</div>
192+
</div>
193+
</BeautifulWrapper>
194+
);
195+
};

packages/core/src/internal/data-grid/data-grid-types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,11 @@ let emptyCompactSelection: CompactSelection | undefined;
557557

558558
/** @category Selection */
559559
export class CompactSelection {
560-
private constructor(private readonly items: CompactSelectionRanges) {}
560+
private constructor(public readonly items: CompactSelectionRanges) {}
561+
562+
static create = (items: CompactSelectionRanges) => {
563+
return new CompactSelection(mergeRanges(items));
564+
}
561565

562566
static empty = (): CompactSelection => {
563567
return emptyCompactSelection ?? (emptyCompactSelection = new CompactSelection([]));

0 commit comments

Comments
 (0)