gp-grid-vue 0.1.6 → 0.2.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/README.md +598 -0
- package/dist/index.d.ts +5 -6
- package/dist/index.js +1 -1819
- package/package.json +63 -55
- package/dist/index.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# gp-grid-vue 🏁 🏎️
|
|
2
|
+
|
|
3
|
+
A high-performance, feature lean Vue 3 data grid component built to manage grids with huge amount (millions) of rows. It's based on its core dependency: `gp-grid-core`, featuring virtual scrolling, cell selection, sorting, filtering, editing, and Excel-like fill handle.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [Examples](#examples)
|
|
11
|
+
- [API Reference](#api-reference)
|
|
12
|
+
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
13
|
+
- [Styling](#styling)
|
|
14
|
+
- [Donations](#donations)
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Virtual Scrolling**: Efficiently handles 150,000+ rows through slot-based recycling
|
|
19
|
+
- **Cell Selection**: Single cell, range selection, Shift+click extend, Ctrl+click toggle
|
|
20
|
+
- **Multi-Column Sorting**: Click to sort, Shift+click for multi-column sort
|
|
21
|
+
- **Column Filtering**: Built-in filter row with debounced input
|
|
22
|
+
- **Cell Editing**: Double-click or press Enter to edit, with custom editor support
|
|
23
|
+
- **Fill Handle**: Excel-like drag-to-fill for editable cells
|
|
24
|
+
- **Keyboard Navigation**: Arrow keys, Tab, Enter, Escape, Ctrl+A, Ctrl+C
|
|
25
|
+
- **Custom Renderers**: Registry-based cell, edit, and header renderers
|
|
26
|
+
- **Dark Mode**: Built-in dark theme support
|
|
27
|
+
- **TypeScript**: Full type safety with exported types
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Use `npm`, `yarn` or `pnpm`
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add gp-grid-vue
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { GpGrid, type ColumnDefinition } from "gp-grid-vue";
|
|
42
|
+
|
|
43
|
+
interface Person {
|
|
44
|
+
id: number;
|
|
45
|
+
name: string;
|
|
46
|
+
age: number;
|
|
47
|
+
email: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const columns: ColumnDefinition[] = [
|
|
51
|
+
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
|
|
52
|
+
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
|
|
53
|
+
{ field: "age", cellDataType: "number", width: 80, headerName: "Age" },
|
|
54
|
+
{ field: "email", cellDataType: "text", width: 250, headerName: "Email" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const data: Person[] = [
|
|
58
|
+
{ id: 1, name: "Alice", age: 30, email: "alice@example.com" },
|
|
59
|
+
{ id: 2, name: "Bob", age: 25, email: "bob@example.com" },
|
|
60
|
+
{ id: 3, name: "Charlie", age: 35, email: "charlie@example.com" },
|
|
61
|
+
];
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<div style="width: 800px; height: 400px">
|
|
66
|
+
<GpGrid :columns="columns" :row-data="data" :row-height="36" />
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
### Client-Side Data Source with Sorting and Filtering
|
|
74
|
+
|
|
75
|
+
For larger datasets with client-side sort/filter operations:
|
|
76
|
+
|
|
77
|
+
```vue
|
|
78
|
+
<script setup lang="ts">
|
|
79
|
+
import { computed } from "vue";
|
|
80
|
+
import {
|
|
81
|
+
GpGrid,
|
|
82
|
+
createClientDataSource,
|
|
83
|
+
type ColumnDefinition,
|
|
84
|
+
} from "gp-grid-vue";
|
|
85
|
+
|
|
86
|
+
interface Product {
|
|
87
|
+
id: number;
|
|
88
|
+
name: string;
|
|
89
|
+
price: number;
|
|
90
|
+
category: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const columns: ColumnDefinition[] = [
|
|
94
|
+
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
|
|
95
|
+
{ field: "name", cellDataType: "text", width: 200, headerName: "Product" },
|
|
96
|
+
{ field: "price", cellDataType: "number", width: 100, headerName: "Price" },
|
|
97
|
+
{
|
|
98
|
+
field: "category",
|
|
99
|
+
cellDataType: "text",
|
|
100
|
+
width: 150,
|
|
101
|
+
headerName: "Category",
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const products = computed<Product[]>(() =>
|
|
106
|
+
Array.from({ length: 10000 }, (_, i) => ({
|
|
107
|
+
id: i + 1,
|
|
108
|
+
name: `Product ${i + 1}`,
|
|
109
|
+
price: Math.round(Math.random() * 1000) / 10,
|
|
110
|
+
category: ["Electronics", "Clothing", "Food", "Books"][i % 4],
|
|
111
|
+
})),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const dataSource = computed(() => createClientDataSource(products.value));
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<template>
|
|
118
|
+
<div style="width: 100%; height: 500px">
|
|
119
|
+
<GpGrid
|
|
120
|
+
:columns="columns"
|
|
121
|
+
:data-source="dataSource"
|
|
122
|
+
:row-height="36"
|
|
123
|
+
:header-height="40"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</template>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Server-Side Data Source
|
|
130
|
+
|
|
131
|
+
For datasets too large to load entirely in memory, use a server-side data source:
|
|
132
|
+
|
|
133
|
+
```vue
|
|
134
|
+
<script setup lang="ts">
|
|
135
|
+
import { computed } from "vue";
|
|
136
|
+
import {
|
|
137
|
+
GpGrid,
|
|
138
|
+
createServerDataSource,
|
|
139
|
+
type ColumnDefinition,
|
|
140
|
+
type DataSourceRequest,
|
|
141
|
+
type DataSourceResponse,
|
|
142
|
+
} from "gp-grid-vue";
|
|
143
|
+
|
|
144
|
+
interface User {
|
|
145
|
+
id: number;
|
|
146
|
+
name: string;
|
|
147
|
+
email: string;
|
|
148
|
+
role: string;
|
|
149
|
+
createdAt: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const columns: ColumnDefinition[] = [
|
|
153
|
+
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
|
|
154
|
+
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
|
|
155
|
+
{ field: "email", cellDataType: "text", width: 250, headerName: "Email" },
|
|
156
|
+
{ field: "role", cellDataType: "text", width: 120, headerName: "Role" },
|
|
157
|
+
{
|
|
158
|
+
field: "createdAt",
|
|
159
|
+
cellDataType: "dateString",
|
|
160
|
+
width: 150,
|
|
161
|
+
headerName: "Created",
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
// API fetch function that handles pagination, sorting, and filtering
|
|
166
|
+
async function fetchUsers(
|
|
167
|
+
request: DataSourceRequest,
|
|
168
|
+
): Promise<DataSourceResponse<User>> {
|
|
169
|
+
const { pagination, sort, filter } = request;
|
|
170
|
+
|
|
171
|
+
// Build query parameters
|
|
172
|
+
const params = new URLSearchParams({
|
|
173
|
+
page: String(pagination.pageIndex),
|
|
174
|
+
limit: String(pagination.pageSize),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Add sorting parameters
|
|
178
|
+
if (sort && sort.length > 0) {
|
|
179
|
+
// Format: sortBy=name:asc,email:desc
|
|
180
|
+
const sortString = sort.map((s) => `${s.colId}:${s.direction}`).join(",");
|
|
181
|
+
params.set("sortBy", sortString);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Add filter parameters
|
|
185
|
+
if (filter) {
|
|
186
|
+
Object.entries(filter).forEach(([field, value]) => {
|
|
187
|
+
if (value) {
|
|
188
|
+
params.set(`filter[${field}]`, String(value));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Make API request
|
|
194
|
+
const response = await fetch(`https://api.example.com/users?${params}`);
|
|
195
|
+
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
throw new Error(`API error: ${response.status}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const data = await response.json();
|
|
201
|
+
|
|
202
|
+
// Return in DataSourceResponse format
|
|
203
|
+
return {
|
|
204
|
+
rows: data.users, // Array of User objects
|
|
205
|
+
totalRows: data.total, // Total count for virtual scrolling
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Create server data source - computed to prevent recreation
|
|
210
|
+
const dataSource = computed(() => createServerDataSource<User>(fetchUsers));
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<template>
|
|
214
|
+
<div style="width: 100%; height: 600px">
|
|
215
|
+
<GpGrid
|
|
216
|
+
:columns="columns"
|
|
217
|
+
:data-source="dataSource"
|
|
218
|
+
:row-height="36"
|
|
219
|
+
:header-height="40"
|
|
220
|
+
:dark-mode="true"
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
</template>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Custom Cell Renderers
|
|
227
|
+
|
|
228
|
+
Use the registry pattern to define reusable renderers:
|
|
229
|
+
|
|
230
|
+
```vue
|
|
231
|
+
<script setup lang="ts">
|
|
232
|
+
import { h } from "vue";
|
|
233
|
+
import {
|
|
234
|
+
GpGrid,
|
|
235
|
+
type ColumnDefinition,
|
|
236
|
+
type CellRendererParams,
|
|
237
|
+
type VueCellRenderer,
|
|
238
|
+
} from "gp-grid-vue";
|
|
239
|
+
|
|
240
|
+
interface Order {
|
|
241
|
+
id: number;
|
|
242
|
+
customer: string;
|
|
243
|
+
total: number;
|
|
244
|
+
status: "pending" | "shipped" | "delivered" | "cancelled";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Define reusable renderers
|
|
248
|
+
const cellRenderers: Record<string, VueCellRenderer> = {
|
|
249
|
+
// Currency formatter
|
|
250
|
+
currency: (params: CellRendererParams) => {
|
|
251
|
+
const value = params.value as number;
|
|
252
|
+
return h(
|
|
253
|
+
"span",
|
|
254
|
+
{ style: { color: "#047857", fontWeight: 600 } },
|
|
255
|
+
`$${value.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
|
|
256
|
+
);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
// Status badge
|
|
260
|
+
statusBadge: (params: CellRendererParams) => {
|
|
261
|
+
const status = params.value as Order["status"];
|
|
262
|
+
const colors: Record<string, { bg: string; text: string }> = {
|
|
263
|
+
pending: { bg: "#fef3c7", text: "#92400e" },
|
|
264
|
+
shipped: { bg: "#dbeafe", text: "#1e40af" },
|
|
265
|
+
delivered: { bg: "#dcfce7", text: "#166534" },
|
|
266
|
+
cancelled: { bg: "#fee2e2", text: "#991b1b" },
|
|
267
|
+
};
|
|
268
|
+
const color = colors[status] ?? { bg: "#f3f4f6", text: "#374151" };
|
|
269
|
+
|
|
270
|
+
return h(
|
|
271
|
+
"span",
|
|
272
|
+
{
|
|
273
|
+
style: {
|
|
274
|
+
backgroundColor: color.bg,
|
|
275
|
+
color: color.text,
|
|
276
|
+
padding: "2px 8px",
|
|
277
|
+
borderRadius: "12px",
|
|
278
|
+
fontSize: "12px",
|
|
279
|
+
fontWeight: 600,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
status.toUpperCase(),
|
|
283
|
+
);
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
// Bold text
|
|
287
|
+
bold: (params: CellRendererParams) =>
|
|
288
|
+
h("strong", String(params.value ?? "")),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const columns: ColumnDefinition[] = [
|
|
292
|
+
{
|
|
293
|
+
field: "id",
|
|
294
|
+
cellDataType: "number",
|
|
295
|
+
width: 80,
|
|
296
|
+
headerName: "ID",
|
|
297
|
+
cellRenderer: "bold",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
field: "customer",
|
|
301
|
+
cellDataType: "text",
|
|
302
|
+
width: 200,
|
|
303
|
+
headerName: "Customer",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
field: "total",
|
|
307
|
+
cellDataType: "number",
|
|
308
|
+
width: 120,
|
|
309
|
+
headerName: "Total",
|
|
310
|
+
cellRenderer: "currency",
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
field: "status",
|
|
314
|
+
cellDataType: "text",
|
|
315
|
+
width: 120,
|
|
316
|
+
headerName: "Status",
|
|
317
|
+
cellRenderer: "statusBadge",
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
const orders: Order[] = [
|
|
322
|
+
{ id: 1, customer: "Acme Corp", total: 1250.0, status: "shipped" },
|
|
323
|
+
{ id: 2, customer: "Globex Inc", total: 890.5, status: "pending" },
|
|
324
|
+
{ id: 3, customer: "Initech", total: 2100.75, status: "delivered" },
|
|
325
|
+
];
|
|
326
|
+
</script>
|
|
327
|
+
|
|
328
|
+
<template>
|
|
329
|
+
<div style="width: 100%; height: 400px">
|
|
330
|
+
<GpGrid
|
|
331
|
+
:columns="columns"
|
|
332
|
+
:row-data="orders"
|
|
333
|
+
:row-height="40"
|
|
334
|
+
:cell-renderers="cellRenderers"
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
</template>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Editable Cells with Custom Editors
|
|
341
|
+
|
|
342
|
+
```vue
|
|
343
|
+
<script setup lang="ts">
|
|
344
|
+
import { h, ref as vueRef } from "vue";
|
|
345
|
+
import {
|
|
346
|
+
GpGrid,
|
|
347
|
+
createClientDataSource,
|
|
348
|
+
type ColumnDefinition,
|
|
349
|
+
type EditRendererParams,
|
|
350
|
+
type VueEditRenderer,
|
|
351
|
+
} from "gp-grid-vue";
|
|
352
|
+
|
|
353
|
+
interface Task {
|
|
354
|
+
id: number;
|
|
355
|
+
title: string;
|
|
356
|
+
priority: "low" | "medium" | "high";
|
|
357
|
+
completed: boolean;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Custom select editor for priority field
|
|
361
|
+
const editRenderers: Record<string, VueEditRenderer> = {
|
|
362
|
+
prioritySelect: (params: EditRendererParams) => {
|
|
363
|
+
return h("select", {
|
|
364
|
+
autofocus: true,
|
|
365
|
+
value: params.initialValue as string,
|
|
366
|
+
onChange: (e: Event) => {
|
|
367
|
+
const target = e.target as HTMLSelectElement;
|
|
368
|
+
params.onValueChange(target.value);
|
|
369
|
+
},
|
|
370
|
+
onBlur: () => params.onCommit(),
|
|
371
|
+
onKeydown: (e: KeyboardEvent) => {
|
|
372
|
+
if (e.key === "Enter") params.onCommit();
|
|
373
|
+
if (e.key === "Escape") params.onCancel();
|
|
374
|
+
},
|
|
375
|
+
style: {
|
|
376
|
+
width: "100%",
|
|
377
|
+
height: "100%",
|
|
378
|
+
border: "none",
|
|
379
|
+
outline: "none",
|
|
380
|
+
padding: "0 8px",
|
|
381
|
+
},
|
|
382
|
+
}, [
|
|
383
|
+
h("option", { value: "low" }, "Low"),
|
|
384
|
+
h("option", { value: "medium" }, "Medium"),
|
|
385
|
+
h("option", { value: "high" }, "High"),
|
|
386
|
+
]);
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
checkbox: (params: EditRendererParams) =>
|
|
390
|
+
h("input", {
|
|
391
|
+
type: "checkbox",
|
|
392
|
+
autofocus: true,
|
|
393
|
+
checked: params.initialValue as boolean,
|
|
394
|
+
onChange: (e: Event) => {
|
|
395
|
+
const target = e.target as HTMLInputElement;
|
|
396
|
+
params.onValueChange(target.checked);
|
|
397
|
+
params.onCommit();
|
|
398
|
+
},
|
|
399
|
+
style: { width: "20px", height: "20px" },
|
|
400
|
+
}),
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const columns: ColumnDefinition[] = [
|
|
404
|
+
{ field: "id", cellDataType: "number", width: 60, headerName: "ID" },
|
|
405
|
+
{
|
|
406
|
+
field: "title",
|
|
407
|
+
cellDataType: "text",
|
|
408
|
+
width: 300,
|
|
409
|
+
headerName: "Title",
|
|
410
|
+
editable: true, // Uses default text input
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
field: "priority",
|
|
414
|
+
cellDataType: "text",
|
|
415
|
+
width: 120,
|
|
416
|
+
headerName: "Priority",
|
|
417
|
+
editable: true,
|
|
418
|
+
editRenderer: "prioritySelect", // Custom editor
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
field: "completed",
|
|
422
|
+
cellDataType: "boolean",
|
|
423
|
+
width: 100,
|
|
424
|
+
headerName: "Done",
|
|
425
|
+
editable: true,
|
|
426
|
+
editRenderer: "checkbox", // Custom editor
|
|
427
|
+
},
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const tasks: Task[] = [
|
|
431
|
+
{ id: 1, title: "Write documentation", priority: "high", completed: false },
|
|
432
|
+
{ id: 2, title: "Fix bugs", priority: "medium", completed: true },
|
|
433
|
+
{ id: 3, title: "Add tests", priority: "low", completed: false },
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
const dataSource = createClientDataSource(tasks);
|
|
437
|
+
</script>
|
|
438
|
+
|
|
439
|
+
<template>
|
|
440
|
+
<div style="width: 600px; height: 300px">
|
|
441
|
+
<GpGrid
|
|
442
|
+
:columns="columns"
|
|
443
|
+
:data-source="dataSource"
|
|
444
|
+
:row-height="40"
|
|
445
|
+
:edit-renderers="editRenderers"
|
|
446
|
+
/>
|
|
447
|
+
</div>
|
|
448
|
+
</template>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Dark Mode
|
|
452
|
+
|
|
453
|
+
```vue
|
|
454
|
+
<template>
|
|
455
|
+
<GpGrid :columns="columns" :row-data="data" :row-height="36" :dark-mode="true" />
|
|
456
|
+
</template>
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## API Reference
|
|
460
|
+
|
|
461
|
+
### GpGridProps
|
|
462
|
+
|
|
463
|
+
| Prop | Type | Default | Description |
|
|
464
|
+
| ----------------- | ----------------------------------- | ----------- | ----------------------------------------------------------- |
|
|
465
|
+
| `columns` | `ColumnDefinition[]` | required | Column definitions |
|
|
466
|
+
| `dataSource` | `DataSource<TData>` | - | Data source for fetching data |
|
|
467
|
+
| `rowData` | `TData[]` | - | Alternative: raw data array (wrapped in client data source) |
|
|
468
|
+
| `rowHeight` | `number` | required | Height of each row in pixels |
|
|
469
|
+
| `headerHeight` | `number` | `rowHeight` | Height of header row |
|
|
470
|
+
| `overscan` | `number` | `3` | Number of rows to render outside viewport |
|
|
471
|
+
| `sortingEnabled` | `boolean` | `true` | Enable column sorting |
|
|
472
|
+
| `darkMode` | `boolean` | `false` | Enable dark theme |
|
|
473
|
+
| `wheelDampening` | `number` | `0.1` | Scroll wheel sensitivity (0-1) |
|
|
474
|
+
| `cellRenderers` | `Record<string, VueCellRenderer>` | `{}` | Cell renderer registry |
|
|
475
|
+
| `editRenderers` | `Record<string, VueEditRenderer>` | `{}` | Edit renderer registry |
|
|
476
|
+
| `headerRenderers` | `Record<string, VueHeaderRenderer>` | `{}` | Header renderer registry |
|
|
477
|
+
| `cellRenderer` | `VueCellRenderer` | - | Global fallback cell renderer |
|
|
478
|
+
| `editRenderer` | `VueEditRenderer` | - | Global fallback edit renderer |
|
|
479
|
+
| `headerRenderer` | `VueHeaderRenderer` | - | Global fallback header renderer |
|
|
480
|
+
|
|
481
|
+
### ColumnDefinition
|
|
482
|
+
|
|
483
|
+
| Property | Type | Description |
|
|
484
|
+
| ---------------- | -------------- | ------------------------------------------------------------------- |
|
|
485
|
+
| `field` | `string` | Property path in row data (supports dot notation: `"address.city"`) |
|
|
486
|
+
| `colId` | `string` | Unique column ID (defaults to `field`) |
|
|
487
|
+
| `cellDataType` | `CellDataType` | `"text"` \| `"number"` \| `"boolean"` \| `"date"` \| `"object"` |
|
|
488
|
+
| `width` | `number` | Column width in pixels |
|
|
489
|
+
| `headerName` | `string` | Display name in header (defaults to `field`) |
|
|
490
|
+
| `editable` | `boolean` | Enable cell editing |
|
|
491
|
+
| `cellRenderer` | `string` | Key in `cellRenderers` registry |
|
|
492
|
+
| `editRenderer` | `string` | Key in `editRenderers` registry |
|
|
493
|
+
| `headerRenderer` | `string` | Key in `headerRenderers` registry |
|
|
494
|
+
|
|
495
|
+
### Renderer Types
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import type { VNode } from "vue";
|
|
499
|
+
|
|
500
|
+
// Cell renderer receives these params
|
|
501
|
+
interface CellRendererParams {
|
|
502
|
+
value: CellValue; // Current cell value
|
|
503
|
+
rowData: Row; // Full row data
|
|
504
|
+
column: ColumnDefinition; // Column definition
|
|
505
|
+
rowIndex: number; // Row index
|
|
506
|
+
colIndex: number; // Column index
|
|
507
|
+
isActive: boolean; // Is this the active cell?
|
|
508
|
+
isSelected: boolean; // Is this cell in selection?
|
|
509
|
+
isEditing: boolean; // Is this cell being edited?
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Vue cell renderer - can return VNode or string
|
|
513
|
+
type VueCellRenderer = (params: CellRendererParams) => VNode | string | null;
|
|
514
|
+
|
|
515
|
+
// Edit renderer receives additional callbacks
|
|
516
|
+
interface EditRendererParams extends CellRendererParams {
|
|
517
|
+
initialValue: CellValue;
|
|
518
|
+
onValueChange: (newValue: CellValue) => void;
|
|
519
|
+
onCommit: () => void;
|
|
520
|
+
onCancel: () => void;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Vue edit renderer - returns VNode for edit input
|
|
524
|
+
type VueEditRenderer = (params: EditRendererParams) => VNode | null;
|
|
525
|
+
|
|
526
|
+
// Header renderer params
|
|
527
|
+
interface HeaderRendererParams {
|
|
528
|
+
column: ColumnDefinition;
|
|
529
|
+
colIndex: number;
|
|
530
|
+
sortDirection?: "asc" | "desc";
|
|
531
|
+
sortIndex?: number; // For multi-column sort
|
|
532
|
+
onSort: (direction: "asc" | "desc" | null, addToExisting: boolean) => void;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Vue header renderer
|
|
536
|
+
type VueHeaderRenderer = (params: HeaderRendererParams) => VNode | string | null;
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## Keyboard Shortcuts
|
|
540
|
+
|
|
541
|
+
| Key | Action |
|
|
542
|
+
| ------------------ | --------------------------------- |
|
|
543
|
+
| Arrow keys | Navigate between cells |
|
|
544
|
+
| Shift + Arrow | Extend selection |
|
|
545
|
+
| Enter | Start editing / Commit edit |
|
|
546
|
+
| Escape | Cancel edit / Clear selection |
|
|
547
|
+
| Tab | Commit and move right |
|
|
548
|
+
| Shift + Tab | Commit and move left |
|
|
549
|
+
| F2 | Start editing |
|
|
550
|
+
| Delete / Backspace | Start editing with empty value |
|
|
551
|
+
| Ctrl + A | Select all |
|
|
552
|
+
| Ctrl + C | Copy selection to clipboard |
|
|
553
|
+
| Any character | Start editing with that character |
|
|
554
|
+
|
|
555
|
+
## Styling
|
|
556
|
+
|
|
557
|
+
The grid injects its own styles automatically. The main container uses these CSS classes:
|
|
558
|
+
|
|
559
|
+
- `.gp-grid-container` - Main container
|
|
560
|
+
- `.gp-grid-container--dark` - Dark mode modifier
|
|
561
|
+
- `.gp-grid-header` - Header row container
|
|
562
|
+
- `.gp-grid-header-cell` - Individual header cell
|
|
563
|
+
- `.gp-grid-row` - Row container
|
|
564
|
+
- `.gp-grid-cell` - Cell container
|
|
565
|
+
- `.gp-grid-cell--active` - Active cell
|
|
566
|
+
- `.gp-grid-cell--selected` - Selected cell
|
|
567
|
+
- `.gp-grid-cell--editing` - Cell in edit mode
|
|
568
|
+
- `.gp-grid-filter-row` - Filter row container
|
|
569
|
+
- `.gp-grid-filter-input` - Filter input field
|
|
570
|
+
- `.gp-grid-fill-handle` - Fill handle element
|
|
571
|
+
|
|
572
|
+
## Donations
|
|
573
|
+
|
|
574
|
+
Keeping this library requires effort and passion, I'm a full time engineer employed on other project and I'm trying my best to keep this work free! For all the features.
|
|
575
|
+
|
|
576
|
+
If you think this project helped you achieve your goals, it's hopefully worth a beer! 🍻
|
|
577
|
+
|
|
578
|
+
<div align="center">
|
|
579
|
+
|
|
580
|
+
### Paypal
|
|
581
|
+
|
|
582
|
+
[](https://www.paypal.com/donate/?hosted_button_id=XCNMG6BR4ZMLY)
|
|
583
|
+
|
|
584
|
+
[https://www.paypal.com/donate/?hosted_button_id=XCNMG6BR4ZMLY](https://www.paypal.com/donate/?hosted_button_id=XCNMG6BR4ZMLY)
|
|
585
|
+
|
|
586
|
+
### Bitcoin
|
|
587
|
+
|
|
588
|
+
[](bitcoin:bc1qcukwmzver59eyqq442xyzscmxavqjt568kkc9m)
|
|
589
|
+
|
|
590
|
+
bitcoin:bc1qcukwmzver59eyqq442xyzscmxavqjt568kkc9m
|
|
591
|
+
|
|
592
|
+
### Lightning Network
|
|
593
|
+
|
|
594
|
+
[](lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhhx6rpvanhjetdvfjhyvf4xs0xu5p7)
|
|
595
|
+
|
|
596
|
+
lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhhx6rpvanhjetdvfjhyvf4xs0xu5p7
|
|
597
|
+
|
|
598
|
+
</div>
|
package/dist/index.d.ts
CHANGED
|
@@ -333,11 +333,11 @@ type BatchInstructionListener = (instructions: GridInstruction[]) => void;
|
|
|
333
333
|
//#endregion
|
|
334
334
|
//#region ../core/src/types/renderers.d.ts
|
|
335
335
|
/** Cell renderer params */
|
|
336
|
-
interface CellRendererParams {
|
|
336
|
+
interface CellRendererParams<TData extends Row = Row> {
|
|
337
337
|
/** Cell value */
|
|
338
338
|
value: CellValue;
|
|
339
339
|
/** Row data */
|
|
340
|
-
rowData:
|
|
340
|
+
rowData: TData;
|
|
341
341
|
/** Column definition */
|
|
342
342
|
column: ColumnDefinition;
|
|
343
343
|
/** Row index */
|
|
@@ -352,7 +352,7 @@ interface CellRendererParams {
|
|
|
352
352
|
isEditing: boolean;
|
|
353
353
|
}
|
|
354
354
|
/** Edit renderer params */
|
|
355
|
-
interface EditRendererParams extends CellRendererParams {
|
|
355
|
+
interface EditRendererParams<TData extends Row = Row> extends CellRendererParams<TData> {
|
|
356
356
|
/** Initial value */
|
|
357
357
|
initialValue: CellValue;
|
|
358
358
|
/** On value change */
|
|
@@ -1193,7 +1193,7 @@ interface UseInputHandlerResult {
|
|
|
1193
1193
|
/**
|
|
1194
1194
|
* Vue composable for handling all input interactions
|
|
1195
1195
|
*/
|
|
1196
|
-
declare function useInputHandler(coreRef: ShallowRef<GridCore | null>, containerRef: Ref<HTMLDivElement | null>, columns: ComputedRef<ColumnDefinition[]>, options: UseInputHandlerOptions): UseInputHandlerResult;
|
|
1196
|
+
declare function useInputHandler<TData extends Row = Row>(coreRef: ShallowRef<GridCore<TData> | null>, containerRef: Ref<HTMLDivElement | null>, columns: ComputedRef<ColumnDefinition[]>, options: UseInputHandlerOptions): UseInputHandlerResult;
|
|
1197
1197
|
//#endregion
|
|
1198
1198
|
//#region src/composables/useAutoScroll.d.ts
|
|
1199
1199
|
/**
|
|
@@ -1422,5 +1422,4 @@ interface RenderHeaderOptions {
|
|
|
1422
1422
|
*/
|
|
1423
1423
|
declare function renderHeader(options: RenderHeaderOptions): VNode;
|
|
1424
1424
|
//#endregion
|
|
1425
|
-
export { type CellDataType, type CellPosition, type CellRange, type CellRendererParams, type CellValue, type ColumnDefinition, type ColumnFilterModel, type ContainerBounds, type DataSource, type DataSourceRequest, type DataSourceResponse, type DateFilterCondition, type DragState, type EditRendererParams, type FilterCondition, type FilterModel, type FilterPopupState, _default as GpGrid, _default as default, type GpGridProps, type GridInstruction, type GridState, type HeaderData, type HeaderRendererParams, type KeyEventData, type LocalFilterCondition, type MutableDataSource, type NumberFilterCondition, type PointerEventData, type Row, type RowId, type SelectionState, type SlotData, type SortDirection, type SortModel, type TextFilterCondition, type UseFillHandleOptions, type UseFillHandleResult, type UseFilterConditionsResult, type UseFilterPopupOptions, type UseGpGridOptions, type UseGpGridResult, type UseInputHandlerOptions, type UseInputHandlerResult, type VueCellRenderer, type VueEditRenderer, type VueHeaderRenderer, buildCellClasses, calculateColumnPositions, createClientDataSource, createDataSourceFromArray, createInitialState, createMutableClientDataSource, createServerDataSource, findColumnAtX, getCellValue, getTotalWidth, gridStyles, injectStyles, isCellActive, isCellEditing, isCellInFillPreview, isCellSelected, isRowVisible, renderCell, renderEditCell, renderHeader, useAutoScroll, useFillHandle, useFilterConditions, useFilterPopup, useGpGrid, useGridState, useInputHandler };
|
|
1426
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1425
|
+
export { type CellDataType, type CellPosition, type CellRange, type CellRendererParams, type CellValue, type ColumnDefinition, type ColumnFilterModel, type ContainerBounds, type DataSource, type DataSourceRequest, type DataSourceResponse, type DateFilterCondition, type DragState, type EditRendererParams, type FilterCondition, type FilterModel, type FilterPopupState, _default as GpGrid, _default as default, type GpGridProps, type GridInstruction, type GridState, type HeaderData, type HeaderRendererParams, type KeyEventData, type LocalFilterCondition, type MutableDataSource, type NumberFilterCondition, type PointerEventData, type Row, type RowId, type SelectionState, type SlotData, type SortDirection, type SortModel, type TextFilterCondition, type UseFillHandleOptions, type UseFillHandleResult, type UseFilterConditionsResult, type UseFilterPopupOptions, type UseGpGridOptions, type UseGpGridResult, type UseInputHandlerOptions, type UseInputHandlerResult, type VueCellRenderer, type VueEditRenderer, type VueHeaderRenderer, buildCellClasses, calculateColumnPositions, createClientDataSource, createDataSourceFromArray, createInitialState, createMutableClientDataSource, createServerDataSource, findColumnAtX, getCellValue, getTotalWidth, gridStyles, injectStyles, isCellActive, isCellEditing, isCellInFillPreview, isCellSelected, isRowVisible, renderCell, renderEditCell, renderHeader, useAutoScroll, useFillHandle, useFilterConditions, useFilterPopup, useGpGrid, useGridState, useInputHandler };
|