gp-grid-core 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/README.md +362 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,362 @@
1
+ # gp-grid-core
2
+
3
+ A framework-agnostic TypeScript library for building high-performance data grids with virtual scrolling, supporting 150,000+ rows with ease.
4
+
5
+ ## Philosophy
6
+
7
+ **gp-grid-core** is built on three core principles:
8
+
9
+ ### 1. Slot-Based Virtual Scrolling
10
+
11
+ Instead of rendering all rows, the grid maintains a pool of reusable "slots" (DOM containers) that are recycled as users scroll. This approach:
12
+
13
+ - Renders only visible rows plus a small overscan buffer
14
+ - Recycles DOM elements instead of creating/destroying them
15
+ - Maintains consistent performance regardless of dataset size
16
+
17
+ ### 2. Instruction-Based Architecture
18
+
19
+ The core emits declarative **instructions** (commands) that describe what the UI should do, rather than manipulating the DOM directly. This pattern:
20
+
21
+ - Keeps the core framework-agnostic (works with React, Vue, Svelte, vanilla JS)
22
+ - Enables batched updates for optimal rendering performance
23
+ - Provides a clean separation between logic and presentation
24
+
25
+ ### 3. DataSource Abstraction
26
+
27
+ Data fetching is abstracted through a `DataSource` interface, supporting both:
28
+
29
+ - **Client-side**: All data loaded in memory, with local sorting/filtering
30
+ - **Server-side**: Data fetched on-demand from an API with server-side operations
31
+
32
+ ## Installation
33
+
34
+ npm/pnpm/yarn
35
+
36
+ ```bash
37
+ pnpm add gp-grid-core
38
+ ```
39
+
40
+ ## Architecture Overview
41
+
42
+ ### GridCore
43
+
44
+ The main orchestrator class that manages:
45
+
46
+ - Viewport tracking and scroll synchronization
47
+ - Slot pool lifecycle (create, assign, move, destroy)
48
+ - Data fetching and caching
49
+ - Sort and filter state
50
+
51
+ ```typescript
52
+ import { GridCore, createClientDataSource } from "gp-grid-core";
53
+
54
+ const dataSource = createClientDataSource(myData);
55
+
56
+ const grid = new GridCore({
57
+ columns: [
58
+ { field: "name", cellDataType: "text", width: 150 },
59
+ { field: "age", cellDataType: "number", width: 80 },
60
+ ],
61
+ dataSource,
62
+ rowHeight: 36,
63
+ headerHeight: 40,
64
+ overscan: 3,
65
+ });
66
+
67
+ // Subscribe to instructions
68
+ grid.onBatchInstruction((instructions) => {
69
+ // Handle UI updates based on instructions
70
+ instructions.forEach((instruction) => {
71
+ switch (instruction.type) {
72
+ case "CREATE_SLOT":
73
+ // Create a new row container
74
+ break;
75
+ case "ASSIGN_SLOT":
76
+ // Assign row data to a slot
77
+ break;
78
+ case "MOVE_SLOT":
79
+ // Position slot via translateY
80
+ break;
81
+ // ... handle other instructions
82
+ }
83
+ });
84
+ });
85
+
86
+ // Initialize and start
87
+ await grid.initialize();
88
+ ```
89
+
90
+ ### Managers
91
+
92
+ GridCore includes specialized managers for complex behaviors:
93
+
94
+ - **SelectionManager**: Handles cell selection, range selection, keyboard navigation
95
+ - **FillManager**: Implements Excel-like fill handle drag operations
96
+
97
+ ### Instruction Types
98
+
99
+ The core emits these instruction types:
100
+
101
+ | Instruction | Description |
102
+ |-------------|-------------|
103
+ | `CREATE_SLOT` | Create a new slot in the DOM pool |
104
+ | `DESTROY_SLOT` | Remove a slot from the pool |
105
+ | `ASSIGN_SLOT` | Assign row data to a slot |
106
+ | `MOVE_SLOT` | Update slot position (translateY) |
107
+ | `SET_ACTIVE_CELL` | Update active cell highlight |
108
+ | `SET_SELECTION_RANGE` | Update selection range |
109
+ | `START_EDIT` / `STOP_EDIT` | Toggle edit mode |
110
+ | `COMMIT_EDIT` | Commit edited value |
111
+ | `UPDATE_HEADER` | Update header with sort state |
112
+ | `DATA_LOADING` / `DATA_LOADED` / `DATA_ERROR` | Data fetch lifecycle |
113
+
114
+ ## Data Sources
115
+
116
+ ### Client-Side Data Source
117
+
118
+ For datasets that can be loaded entirely in memory. Sorting and filtering are performed client-side.
119
+
120
+ ```typescript
121
+ import { createClientDataSource } from "gp-grid-core";
122
+
123
+ interface Person {
124
+ id: number;
125
+ name: string;
126
+ age: number;
127
+ email: string;
128
+ }
129
+
130
+ const data: Person[] = [
131
+ { id: 1, name: "Alice", age: 30, email: "alice@example.com" },
132
+ { id: 2, name: "Bob", age: 25, email: "bob@example.com" },
133
+ // ... more rows
134
+ ];
135
+
136
+ const dataSource = createClientDataSource(data);
137
+ ```
138
+
139
+ **With custom field accessor** (for nested properties):
140
+
141
+ ```typescript
142
+ const dataSource = createClientDataSource(data, {
143
+ getFieldValue: (row, field) => {
144
+ // Custom logic for accessing nested fields
145
+ if (field === "address.city") {
146
+ return row.address?.city;
147
+ }
148
+ return row[field];
149
+ },
150
+ });
151
+ ```
152
+
153
+ ### Server-Side Data Source
154
+
155
+ For large datasets that require server-side pagination, sorting, and filtering.
156
+
157
+ ```typescript
158
+ import { createServerDataSource, DataSourceRequest, DataSourceResponse } from "gp-grid-core";
159
+
160
+ interface Person {
161
+ id: number;
162
+ name: string;
163
+ age: number;
164
+ }
165
+
166
+ const dataSource = createServerDataSource<Person>(async (request: DataSourceRequest) => {
167
+ // Build query parameters from request
168
+ const params = new URLSearchParams({
169
+ page: String(request.pagination.pageIndex),
170
+ pageSize: String(request.pagination.pageSize),
171
+ });
172
+
173
+ // Add sort parameters
174
+ if (request.sort && request.sort.length > 0) {
175
+ params.set("sortBy", request.sort.map(s => `${s.colId}:${s.direction}`).join(","));
176
+ }
177
+
178
+ // Add filter parameters
179
+ if (request.filter) {
180
+ Object.entries(request.filter).forEach(([field, value]) => {
181
+ params.set(`filter_${field}`, value);
182
+ });
183
+ }
184
+
185
+ // Fetch from your API
186
+ const response = await fetch(`/api/people?${params}`);
187
+ const data = await response.json();
188
+
189
+ return {
190
+ rows: data.items,
191
+ totalRows: data.totalCount,
192
+ };
193
+ });
194
+ ```
195
+
196
+ ### DataSource Interface
197
+
198
+ Both data source types implement this interface:
199
+
200
+ ```typescript
201
+ interface DataSource<TData = Row> {
202
+ fetch(request: DataSourceRequest): Promise<DataSourceResponse<TData>>;
203
+ }
204
+
205
+ interface DataSourceRequest {
206
+ pagination: {
207
+ pageIndex: number;
208
+ pageSize: number;
209
+ };
210
+ sort?: SortModel[];
211
+ filter?: FilterModel;
212
+ }
213
+
214
+ interface DataSourceResponse<TData> {
215
+ rows: TData[];
216
+ totalRows: number;
217
+ }
218
+ ```
219
+
220
+ ## Types Reference
221
+
222
+ ### ColumnDefinition
223
+
224
+ ```typescript
225
+ interface ColumnDefinition {
226
+ field: string; // Property path in row data
227
+ colId?: string; // Unique column ID (defaults to field)
228
+ cellDataType: CellDataType; // "text" | "number" | "boolean" | "date" | "object"
229
+ width: number; // Column width in pixels
230
+ headerName?: string; // Display name (defaults to field)
231
+ editable?: boolean; // Enable cell editing
232
+ cellRenderer?: string; // Custom renderer key
233
+ editRenderer?: string; // Custom edit renderer key
234
+ headerRenderer?: string; // Custom header renderer key
235
+ }
236
+ ```
237
+
238
+ ### Renderer Params
239
+
240
+ When building framework adapters, these params are passed to custom renderers:
241
+
242
+ ```typescript
243
+ interface CellRendererParams {
244
+ value: CellValue;
245
+ rowData: Row;
246
+ column: ColumnDefinition;
247
+ rowIndex: number;
248
+ colIndex: number;
249
+ isActive: boolean;
250
+ isSelected: boolean;
251
+ isEditing: boolean;
252
+ }
253
+
254
+ interface EditRendererParams extends CellRendererParams {
255
+ initialValue: CellValue;
256
+ onValueChange: (newValue: CellValue) => void;
257
+ onCommit: () => void;
258
+ onCancel: () => void;
259
+ }
260
+
261
+ interface HeaderRendererParams {
262
+ column: ColumnDefinition;
263
+ colIndex: number;
264
+ sortDirection?: SortDirection;
265
+ sortIndex?: number;
266
+ onSort: (direction: SortDirection | null, addToExisting: boolean) => void;
267
+ }
268
+ ```
269
+
270
+ ## Creating a Framework Adapter
271
+
272
+ To integrate gp-grid-core with any UI framework:
273
+
274
+ 1. **Subscribe to instructions** using `onBatchInstruction()`
275
+ 2. **Maintain UI state** by processing instructions
276
+ 3. **Render slots** based on the slot pool state
277
+ 4. **Forward user interactions** back to GridCore
278
+
279
+ ### Example: Minimal Adapter Pattern
280
+
281
+ ```typescript
282
+ import { GridCore, GridInstruction } from "gp-grid-core";
283
+
284
+ class MyGridAdapter {
285
+ private core: GridCore;
286
+ private slots: Map<string, SlotUIElement> = new Map();
287
+
288
+ constructor(options: GridCoreOptions) {
289
+ this.core = new GridCore(options);
290
+
291
+ // Process instructions to update UI
292
+ this.core.onBatchInstruction((instructions) => {
293
+ this.processInstructions(instructions);
294
+ this.render();
295
+ });
296
+ }
297
+
298
+ private processInstructions(instructions: GridInstruction[]) {
299
+ for (const instr of instructions) {
300
+ switch (instr.type) {
301
+ case "CREATE_SLOT":
302
+ this.slots.set(instr.slotId, this.createSlotElement());
303
+ break;
304
+ case "DESTROY_SLOT":
305
+ this.slots.delete(instr.slotId);
306
+ break;
307
+ case "ASSIGN_SLOT":
308
+ const slot = this.slots.get(instr.slotId);
309
+ if (slot) {
310
+ slot.rowIndex = instr.rowIndex;
311
+ slot.rowData = instr.rowData;
312
+ }
313
+ break;
314
+ case "MOVE_SLOT":
315
+ const moveSlot = this.slots.get(instr.slotId);
316
+ if (moveSlot) {
317
+ moveSlot.translateY = instr.translateY;
318
+ }
319
+ break;
320
+ }
321
+ }
322
+ }
323
+
324
+ // Handle scroll events
325
+ onScroll(scrollTop: number, scrollLeft: number, width: number, height: number) {
326
+ this.core.setViewport(scrollTop, scrollLeft, width, height);
327
+ }
328
+
329
+ // Handle cell click
330
+ onCellClick(row: number, col: number, modifiers: { shift: boolean; ctrl: boolean }) {
331
+ this.core.selection.startSelection({ row, col }, modifiers);
332
+ }
333
+
334
+ async initialize() {
335
+ await this.core.initialize();
336
+ }
337
+ }
338
+ ```
339
+
340
+ ## API Reference
341
+
342
+ ### GridCore Methods
343
+
344
+ | Method | Description |
345
+ |--------|-------------|
346
+ | `initialize()` | Initialize grid and load initial data |
347
+ | `setViewport(scrollTop, scrollLeft, width, height)` | Update viewport on scroll/resize |
348
+ | `setSort(colId, direction, addToExisting)` | Set column sort |
349
+ | `setFilter(colId, value)` | Set column filter |
350
+ | `startEdit(row, col)` | Start editing a cell |
351
+ | `commitEdit()` | Commit current edit |
352
+ | `cancelEdit()` | Cancel current edit |
353
+ | `refresh()` | Refetch data from source |
354
+ | `getRowCount()` | Get total row count |
355
+ | `getRowData(rowIndex)` | Get data for a specific row |
356
+
357
+ ### GridCore Properties
358
+
359
+ | Property | Description |
360
+ |----------|-------------|
361
+ | `selection` | SelectionManager instance |
362
+ | `fill` | FillManager instance |
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "gp-grid-core",
3
3
  "description": "A typescript library that enables grid creation",
4
4
  "private": false,
5
- "version": "0.1.0",
5
+ "version": "0.1.1",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "author": "Giovanni Patruno (giovanni.patruno@outlook.com)",