gp-grid-react 0.1.1 → 0.1.2
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 +520 -0
- package/dist/index.js +79 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Grid.tsx +119 -5
- package/src/styles.ts +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# gp-grid-react
|
|
2
|
+
|
|
3
|
+
A high-performance React data grid component built on [gp-grid-core](../core/README.md), featuring virtual scrolling, cell selection, sorting, filtering, editing, and Excel-like fill handle.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Virtual Scrolling**: Efficiently handles 150,000+ rows through slot-based recycling
|
|
8
|
+
- **Cell Selection**: Single cell, range selection, Shift+click extend, Ctrl+click toggle
|
|
9
|
+
- **Multi-Column Sorting**: Click to sort, Shift+click for multi-column sort
|
|
10
|
+
- **Column Filtering**: Built-in filter row with debounced input
|
|
11
|
+
- **Cell Editing**: Double-click or press Enter to edit, with custom editor support
|
|
12
|
+
- **Fill Handle**: Excel-like drag-to-fill for editable cells
|
|
13
|
+
- **Keyboard Navigation**: Arrow keys, Tab, Enter, Escape, Ctrl+A, Ctrl+C
|
|
14
|
+
- **Custom Renderers**: Registry-based cell, edit, and header renderers
|
|
15
|
+
- **Dark Mode**: Built-in dark theme support
|
|
16
|
+
- **TypeScript**: Full type safety with exported types
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Use `npm`, `yarn` or `pnpm`
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add gp-grid-react
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { Grid, type ColumnDefinition } from "gp-grid-react";
|
|
29
|
+
|
|
30
|
+
interface Person {
|
|
31
|
+
id: number;
|
|
32
|
+
name: string;
|
|
33
|
+
age: number;
|
|
34
|
+
email: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const columns: ColumnDefinition[] = [
|
|
38
|
+
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
|
|
39
|
+
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
|
|
40
|
+
{ field: "age", cellDataType: "number", width: 80, headerName: "Age" },
|
|
41
|
+
{ field: "email", cellDataType: "text", width: 250, headerName: "Email" },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const data: Person[] = [
|
|
45
|
+
{ id: 1, name: "Alice", age: 30, email: "alice@example.com" },
|
|
46
|
+
{ id: 2, name: "Bob", age: 25, email: "bob@example.com" },
|
|
47
|
+
{ id: 3, name: "Charlie", age: 35, email: "charlie@example.com" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function App() {
|
|
51
|
+
return (
|
|
52
|
+
<div style={{ width: "800px", height: "400px" }}>
|
|
53
|
+
<Grid
|
|
54
|
+
columns={columns}
|
|
55
|
+
rowData={data}
|
|
56
|
+
rowHeight={36}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
### Client-Side Data Source with Sorting and Filtering
|
|
66
|
+
|
|
67
|
+
For larger datasets with client-side sort/filter operations:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { useMemo } from "react";
|
|
71
|
+
import { Grid, createClientDataSource, type ColumnDefinition } from "gp-grid-react";
|
|
72
|
+
|
|
73
|
+
interface Product {
|
|
74
|
+
id: number;
|
|
75
|
+
name: string;
|
|
76
|
+
price: number;
|
|
77
|
+
category: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const columns: ColumnDefinition[] = [
|
|
81
|
+
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
|
|
82
|
+
{ field: "name", cellDataType: "text", width: 200, headerName: "Product" },
|
|
83
|
+
{ field: "price", cellDataType: "number", width: 100, headerName: "Price" },
|
|
84
|
+
{ field: "category", cellDataType: "text", width: 150, headerName: "Category" },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
function ProductGrid() {
|
|
88
|
+
const products: Product[] = useMemo(() =>
|
|
89
|
+
Array.from({ length: 10000 }, (_, i) => ({
|
|
90
|
+
id: i + 1,
|
|
91
|
+
name: `Product ${i + 1}`,
|
|
92
|
+
price: Math.round(Math.random() * 1000) / 10,
|
|
93
|
+
category: ["Electronics", "Clothing", "Food", "Books"][i % 4],
|
|
94
|
+
})),
|
|
95
|
+
[]);
|
|
96
|
+
|
|
97
|
+
const dataSource = useMemo(
|
|
98
|
+
() => createClientDataSource(products),
|
|
99
|
+
[products]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div style={{ width: "100%", height: "500px" }}>
|
|
104
|
+
<Grid
|
|
105
|
+
columns={columns}
|
|
106
|
+
dataSource={dataSource}
|
|
107
|
+
rowHeight={36}
|
|
108
|
+
headerHeight={40}
|
|
109
|
+
showFilters={true}
|
|
110
|
+
filterDebounce={300}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Server-Side Data Source
|
|
118
|
+
|
|
119
|
+
For datasets too large to load entirely in memory, use a server-side data source:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { useMemo } from "react";
|
|
123
|
+
import {
|
|
124
|
+
Grid,
|
|
125
|
+
createServerDataSource,
|
|
126
|
+
type ColumnDefinition,
|
|
127
|
+
type DataSourceRequest,
|
|
128
|
+
type DataSourceResponse,
|
|
129
|
+
} from "gp-grid-react";
|
|
130
|
+
|
|
131
|
+
interface User {
|
|
132
|
+
id: number;
|
|
133
|
+
name: string;
|
|
134
|
+
email: string;
|
|
135
|
+
role: string;
|
|
136
|
+
createdAt: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const columns: ColumnDefinition[] = [
|
|
140
|
+
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
|
|
141
|
+
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
|
|
142
|
+
{ field: "email", cellDataType: "text", width: 250, headerName: "Email" },
|
|
143
|
+
{ field: "role", cellDataType: "text", width: 120, headerName: "Role" },
|
|
144
|
+
{ field: "createdAt", cellDataType: "dateString", width: 150, headerName: "Created" },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
// API fetch function that handles pagination, sorting, and filtering
|
|
148
|
+
async function fetchUsers(request: DataSourceRequest): Promise<DataSourceResponse<User>> {
|
|
149
|
+
const { pagination, sort, filter } = request;
|
|
150
|
+
|
|
151
|
+
// Build query parameters
|
|
152
|
+
const params = new URLSearchParams({
|
|
153
|
+
page: String(pagination.pageIndex),
|
|
154
|
+
limit: String(pagination.pageSize),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Add sorting parameters
|
|
158
|
+
if (sort && sort.length > 0) {
|
|
159
|
+
// Format: sortBy=name:asc,email:desc
|
|
160
|
+
const sortString = sort
|
|
161
|
+
.map((s) => `${s.colId}:${s.direction}`)
|
|
162
|
+
.join(",");
|
|
163
|
+
params.set("sortBy", sortString);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add filter parameters
|
|
167
|
+
if (filter) {
|
|
168
|
+
Object.entries(filter).forEach(([field, value]) => {
|
|
169
|
+
if (value) {
|
|
170
|
+
params.set(`filter[${field}]`, value);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Make API request
|
|
176
|
+
const response = await fetch(`https://api.example.com/users?${params}`);
|
|
177
|
+
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error(`API error: ${response.status}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const data = await response.json();
|
|
183
|
+
|
|
184
|
+
// Return in DataSourceResponse format
|
|
185
|
+
return {
|
|
186
|
+
rows: data.users, // Array of User objects
|
|
187
|
+
totalRows: data.total, // Total count for virtual scrolling
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function UserGrid() {
|
|
192
|
+
// Create server data source - memoize to prevent recreation
|
|
193
|
+
const dataSource = useMemo(
|
|
194
|
+
() => createServerDataSource<User>(fetchUsers),
|
|
195
|
+
[]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<div style={{ width: "100%", height: "600px" }}>
|
|
200
|
+
<Grid
|
|
201
|
+
columns={columns}
|
|
202
|
+
dataSource={dataSource}
|
|
203
|
+
rowHeight={36}
|
|
204
|
+
headerHeight={40}
|
|
205
|
+
showFilters={true}
|
|
206
|
+
filterDebounce={500} // Debounce filter requests
|
|
207
|
+
darkMode={true}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Custom Cell Renderers
|
|
215
|
+
|
|
216
|
+
Use the registry pattern to define reusable renderers:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import { Grid, type ColumnDefinition, type CellRendererParams } from "gp-grid-react";
|
|
220
|
+
|
|
221
|
+
interface Order {
|
|
222
|
+
id: number;
|
|
223
|
+
customer: string;
|
|
224
|
+
total: number;
|
|
225
|
+
status: "pending" | "shipped" | "delivered" | "cancelled";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Define reusable renderers
|
|
229
|
+
const cellRenderers = {
|
|
230
|
+
// Currency formatter
|
|
231
|
+
currency: (params: CellRendererParams) => {
|
|
232
|
+
const value = params.value as number;
|
|
233
|
+
return (
|
|
234
|
+
<span style={{ color: "#047857", fontWeight: 600 }}>
|
|
235
|
+
${value.toLocaleString("en-US", { minimumFractionDigits: 2 })}
|
|
236
|
+
</span>
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// Status badge
|
|
241
|
+
statusBadge: (params: CellRendererParams) => {
|
|
242
|
+
const status = params.value as Order["status"];
|
|
243
|
+
const colors: Record<string, { bg: string; text: string }> = {
|
|
244
|
+
pending: { bg: "#fef3c7", text: "#92400e" },
|
|
245
|
+
shipped: { bg: "#dbeafe", text: "#1e40af" },
|
|
246
|
+
delivered: { bg: "#dcfce7", text: "#166534" },
|
|
247
|
+
cancelled: { bg: "#fee2e2", text: "#991b1b" },
|
|
248
|
+
};
|
|
249
|
+
const color = colors[status] ?? { bg: "#f3f4f6", text: "#374151" };
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<span
|
|
253
|
+
style={{
|
|
254
|
+
backgroundColor: color.bg,
|
|
255
|
+
color: color.text,
|
|
256
|
+
padding: "2px 8px",
|
|
257
|
+
borderRadius: "12px",
|
|
258
|
+
fontSize: "12px",
|
|
259
|
+
fontWeight: 600,
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
{status.toUpperCase()}
|
|
263
|
+
</span>
|
|
264
|
+
);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// Bold text
|
|
268
|
+
bold: (params: CellRendererParams) => (
|
|
269
|
+
<strong>{String(params.value ?? "")}</strong>
|
|
270
|
+
),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const columns: ColumnDefinition[] = [
|
|
274
|
+
{ field: "id", cellDataType: "number", width: 80, headerName: "ID", cellRenderer: "bold" },
|
|
275
|
+
{ field: "customer", cellDataType: "text", width: 200, headerName: "Customer" },
|
|
276
|
+
{ field: "total", cellDataType: "number", width: 120, headerName: "Total", cellRenderer: "currency" },
|
|
277
|
+
{ field: "status", cellDataType: "text", width: 120, headerName: "Status", cellRenderer: "statusBadge" },
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
function OrderGrid({ orders }: { orders: Order[] }) {
|
|
281
|
+
return (
|
|
282
|
+
<div style={{ width: "100%", height: "400px" }}>
|
|
283
|
+
<Grid
|
|
284
|
+
columns={columns}
|
|
285
|
+
rowData={orders}
|
|
286
|
+
rowHeight={40}
|
|
287
|
+
cellRenderers={cellRenderers}
|
|
288
|
+
/>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Editable Cells with Custom Editors
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
import { useState } from "react";
|
|
298
|
+
import {
|
|
299
|
+
Grid,
|
|
300
|
+
createClientDataSource,
|
|
301
|
+
type ColumnDefinition,
|
|
302
|
+
type EditRendererParams
|
|
303
|
+
} from "gp-grid-react";
|
|
304
|
+
|
|
305
|
+
interface Task {
|
|
306
|
+
id: number;
|
|
307
|
+
title: string;
|
|
308
|
+
priority: "low" | "medium" | "high";
|
|
309
|
+
completed: boolean;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Custom select editor for priority field
|
|
313
|
+
const editRenderers = {
|
|
314
|
+
prioritySelect: (params: EditRendererParams) => {
|
|
315
|
+
const [value, setValue] = useState(params.initialValue as string);
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<select
|
|
319
|
+
autoFocus
|
|
320
|
+
value={value}
|
|
321
|
+
onChange={(e) => {
|
|
322
|
+
setValue(e.target.value);
|
|
323
|
+
params.onValueChange(e.target.value);
|
|
324
|
+
}}
|
|
325
|
+
onBlur={() => params.onCommit()}
|
|
326
|
+
onKeyDown={(e) => {
|
|
327
|
+
if (e.key === "Enter") params.onCommit();
|
|
328
|
+
if (e.key === "Escape") params.onCancel();
|
|
329
|
+
}}
|
|
330
|
+
style={{
|
|
331
|
+
width: "100%",
|
|
332
|
+
height: "100%",
|
|
333
|
+
border: "none",
|
|
334
|
+
outline: "none",
|
|
335
|
+
padding: "0 8px",
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
<option value="low">Low</option>
|
|
339
|
+
<option value="medium">Medium</option>
|
|
340
|
+
<option value="high">High</option>
|
|
341
|
+
</select>
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
checkbox: (params: EditRendererParams) => (
|
|
346
|
+
<input
|
|
347
|
+
type="checkbox"
|
|
348
|
+
autoFocus
|
|
349
|
+
defaultChecked={params.initialValue as boolean}
|
|
350
|
+
onChange={(e) => {
|
|
351
|
+
params.onValueChange(e.target.checked);
|
|
352
|
+
params.onCommit();
|
|
353
|
+
}}
|
|
354
|
+
style={{ width: 20, height: 20 }}
|
|
355
|
+
/>
|
|
356
|
+
),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const columns: ColumnDefinition[] = [
|
|
360
|
+
{ field: "id", cellDataType: "number", width: 60, headerName: "ID" },
|
|
361
|
+
{
|
|
362
|
+
field: "title",
|
|
363
|
+
cellDataType: "text",
|
|
364
|
+
width: 300,
|
|
365
|
+
headerName: "Title",
|
|
366
|
+
editable: true, // Uses default text input
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
field: "priority",
|
|
370
|
+
cellDataType: "text",
|
|
371
|
+
width: 120,
|
|
372
|
+
headerName: "Priority",
|
|
373
|
+
editable: true,
|
|
374
|
+
editRenderer: "prioritySelect", // Custom editor
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
field: "completed",
|
|
378
|
+
cellDataType: "boolean",
|
|
379
|
+
width: 100,
|
|
380
|
+
headerName: "Done",
|
|
381
|
+
editable: true,
|
|
382
|
+
editRenderer: "checkbox", // Custom editor
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
function TaskGrid() {
|
|
387
|
+
const tasks: Task[] = [
|
|
388
|
+
{ id: 1, title: "Write documentation", priority: "high", completed: false },
|
|
389
|
+
{ id: 2, title: "Fix bugs", priority: "medium", completed: true },
|
|
390
|
+
{ id: 3, title: "Add tests", priority: "low", completed: false },
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
const dataSource = createClientDataSource(tasks);
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<div style={{ width: "600px", height: "300px" }}>
|
|
397
|
+
<Grid
|
|
398
|
+
columns={columns}
|
|
399
|
+
dataSource={dataSource}
|
|
400
|
+
rowHeight={40}
|
|
401
|
+
editRenderers={editRenderers}
|
|
402
|
+
/>
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Dark Mode
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
<Grid
|
|
412
|
+
columns={columns}
|
|
413
|
+
rowData={data}
|
|
414
|
+
rowHeight={36}
|
|
415
|
+
darkMode={true}
|
|
416
|
+
/>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## API Reference
|
|
420
|
+
|
|
421
|
+
### GridProps
|
|
422
|
+
|
|
423
|
+
| Prop | Type | Default | Description |
|
|
424
|
+
|------|------|---------|-------------|
|
|
425
|
+
| `columns` | `ColumnDefinition[]` | required | Column definitions |
|
|
426
|
+
| `dataSource` | `DataSource<TData>` | - | Data source for fetching data |
|
|
427
|
+
| `rowData` | `TData[]` | - | Alternative: raw data array (wrapped in client data source) |
|
|
428
|
+
| `rowHeight` | `number` | required | Height of each row in pixels |
|
|
429
|
+
| `headerHeight` | `number` | `rowHeight` | Height of header row |
|
|
430
|
+
| `overscan` | `number` | `3` | Number of rows to render outside viewport |
|
|
431
|
+
| `showFilters` | `boolean` | `false` | Show filter row below headers |
|
|
432
|
+
| `filterDebounce` | `number` | `300` | Debounce time for filter input (ms) |
|
|
433
|
+
| `darkMode` | `boolean` | `false` | Enable dark theme |
|
|
434
|
+
| `cellRenderers` | `Record<string, ReactCellRenderer>` | `{}` | Cell renderer registry |
|
|
435
|
+
| `editRenderers` | `Record<string, ReactEditRenderer>` | `{}` | Edit renderer registry |
|
|
436
|
+
| `headerRenderers` | `Record<string, ReactHeaderRenderer>` | `{}` | Header renderer registry |
|
|
437
|
+
| `cellRenderer` | `ReactCellRenderer` | - | Global fallback cell renderer |
|
|
438
|
+
| `editRenderer` | `ReactEditRenderer` | - | Global fallback edit renderer |
|
|
439
|
+
| `headerRenderer` | `ReactHeaderRenderer` | - | Global fallback header renderer |
|
|
440
|
+
|
|
441
|
+
### ColumnDefinition
|
|
442
|
+
|
|
443
|
+
| Property | Type | Description |
|
|
444
|
+
|----------|------|-------------|
|
|
445
|
+
| `field` | `string` | Property path in row data (supports dot notation: `"address.city"`) |
|
|
446
|
+
| `colId` | `string` | Unique column ID (defaults to `field`) |
|
|
447
|
+
| `cellDataType` | `CellDataType` | `"text"` \| `"number"` \| `"boolean"` \| `"date"` \| `"object"` |
|
|
448
|
+
| `width` | `number` | Column width in pixels |
|
|
449
|
+
| `headerName` | `string` | Display name in header (defaults to `field`) |
|
|
450
|
+
| `editable` | `boolean` | Enable cell editing |
|
|
451
|
+
| `cellRenderer` | `string` | Key in `cellRenderers` registry |
|
|
452
|
+
| `editRenderer` | `string` | Key in `editRenderers` registry |
|
|
453
|
+
| `headerRenderer` | `string` | Key in `headerRenderers` registry |
|
|
454
|
+
|
|
455
|
+
### Renderer Types
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// Cell renderer receives these params
|
|
459
|
+
interface CellRendererParams {
|
|
460
|
+
value: CellValue; // Current cell value
|
|
461
|
+
rowData: Row; // Full row data
|
|
462
|
+
column: ColumnDefinition; // Column definition
|
|
463
|
+
rowIndex: number; // Row index
|
|
464
|
+
colIndex: number; // Column index
|
|
465
|
+
isActive: boolean; // Is this the active cell?
|
|
466
|
+
isSelected: boolean; // Is this cell in selection?
|
|
467
|
+
isEditing: boolean; // Is this cell being edited?
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Edit renderer receives additional callbacks
|
|
471
|
+
interface EditRendererParams extends CellRendererParams {
|
|
472
|
+
initialValue: CellValue;
|
|
473
|
+
onValueChange: (newValue: CellValue) => void;
|
|
474
|
+
onCommit: () => void;
|
|
475
|
+
onCancel: () => void;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Header renderer params
|
|
479
|
+
interface HeaderRendererParams {
|
|
480
|
+
column: ColumnDefinition;
|
|
481
|
+
colIndex: number;
|
|
482
|
+
sortDirection?: "asc" | "desc";
|
|
483
|
+
sortIndex?: number; // For multi-column sort
|
|
484
|
+
onSort: (direction: "asc" | "desc" | null, addToExisting: boolean) => void;
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
## Keyboard Shortcuts
|
|
489
|
+
|
|
490
|
+
| Key | Action |
|
|
491
|
+
|-----|--------|
|
|
492
|
+
| Arrow keys | Navigate between cells |
|
|
493
|
+
| Shift + Arrow | Extend selection |
|
|
494
|
+
| Enter | Start editing / Commit edit |
|
|
495
|
+
| Escape | Cancel edit / Clear selection |
|
|
496
|
+
| Tab | Commit and move right |
|
|
497
|
+
| Shift + Tab | Commit and move left |
|
|
498
|
+
| F2 | Start editing |
|
|
499
|
+
| Delete / Backspace | Start editing with empty value |
|
|
500
|
+
| Ctrl + A | Select all |
|
|
501
|
+
| Ctrl + C | Copy selection to clipboard |
|
|
502
|
+
| Any character | Start editing with that character |
|
|
503
|
+
|
|
504
|
+
## Styling
|
|
505
|
+
|
|
506
|
+
The grid injects its own styles automatically. The main container uses these CSS classes:
|
|
507
|
+
|
|
508
|
+
- `.gp-grid-container` - Main container
|
|
509
|
+
- `.gp-grid-container--dark` - Dark mode modifier
|
|
510
|
+
- `.gp-grid-header` - Header row container
|
|
511
|
+
- `.gp-grid-header-cell` - Individual header cell
|
|
512
|
+
- `.gp-grid-row` - Row container
|
|
513
|
+
- `.gp-grid-cell` - Cell container
|
|
514
|
+
- `.gp-grid-cell--active` - Active cell
|
|
515
|
+
- `.gp-grid-cell--selected` - Selected cell
|
|
516
|
+
- `.gp-grid-cell--editing` - Cell in edit mode
|
|
517
|
+
- `.gp-grid-filter-row` - Filter row container
|
|
518
|
+
- `.gp-grid-filter-input` - Filter input field
|
|
519
|
+
- `.gp-grid-fill-handle` - Fill handle element
|
|
520
|
+
|
package/dist/index.js
CHANGED
|
@@ -101,6 +101,8 @@ const gridStyles = `
|
|
|
101
101
|
background-color: var(--gp-grid-bg);
|
|
102
102
|
border: 1px solid var(--gp-grid-border);
|
|
103
103
|
border-radius: 6px;
|
|
104
|
+
user-select: none;
|
|
105
|
+
-webkit-user-select: none;
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
.gp-grid-container:focus {
|
|
@@ -238,6 +240,8 @@ const gridStyles = `
|
|
|
238
240
|
overflow: hidden;
|
|
239
241
|
text-overflow: ellipsis;
|
|
240
242
|
white-space: nowrap;
|
|
243
|
+
user-select: none;
|
|
244
|
+
-webkit-user-select: none;
|
|
241
245
|
}
|
|
242
246
|
|
|
243
247
|
/* Alternating row colors */
|
|
@@ -548,6 +552,7 @@ function Grid(props) {
|
|
|
548
552
|
const [fillTarget, setFillTarget] = useState(null);
|
|
549
553
|
const [fillSourceRange, setFillSourceRange] = useState(null);
|
|
550
554
|
const autoScrollIntervalRef = useRef(null);
|
|
555
|
+
const [isDraggingSelection, setIsDraggingSelection] = useState(false);
|
|
551
556
|
const filterRowHeight = showFilters ? 40 : 0;
|
|
552
557
|
const totalHeaderHeight = headerHeight + filterRowHeight;
|
|
553
558
|
const dataSource = useMemo(() => {
|
|
@@ -711,9 +716,10 @@ function Grid(props) {
|
|
|
711
716
|
columnPositions,
|
|
712
717
|
columns
|
|
713
718
|
]);
|
|
714
|
-
const
|
|
719
|
+
const handleCellMouseDown = useCallback((rowIndex, colIndex, e) => {
|
|
715
720
|
const core = coreRef.current;
|
|
716
721
|
if (!core || core.getEditState() !== null) return;
|
|
722
|
+
if (e.button !== 0) return;
|
|
717
723
|
containerRef.current?.focus();
|
|
718
724
|
core.selection.startSelection({
|
|
719
725
|
row: rowIndex,
|
|
@@ -722,6 +728,7 @@ function Grid(props) {
|
|
|
722
728
|
shift: e.shiftKey,
|
|
723
729
|
ctrl: e.ctrlKey || e.metaKey
|
|
724
730
|
});
|
|
731
|
+
if (!e.shiftKey) setIsDraggingSelection(true);
|
|
725
732
|
}, []);
|
|
726
733
|
const handleCellDoubleClick = useCallback((rowIndex, colIndex) => {
|
|
727
734
|
const core = coreRef.current;
|
|
@@ -838,6 +845,76 @@ function Grid(props) {
|
|
|
838
845
|
rowHeight,
|
|
839
846
|
columnPositions
|
|
840
847
|
]);
|
|
848
|
+
useEffect(() => {
|
|
849
|
+
if (!isDraggingSelection) return;
|
|
850
|
+
const SCROLL_THRESHOLD = 40;
|
|
851
|
+
const SCROLL_SPEED = 10;
|
|
852
|
+
const handleMouseMove = (e) => {
|
|
853
|
+
const core = coreRef.current;
|
|
854
|
+
const container = containerRef.current;
|
|
855
|
+
if (!core || !container) return;
|
|
856
|
+
const rect = container.getBoundingClientRect();
|
|
857
|
+
const scrollLeft = container.scrollLeft;
|
|
858
|
+
const scrollTop = container.scrollTop;
|
|
859
|
+
const mouseX = e.clientX - rect.left + scrollLeft;
|
|
860
|
+
const mouseY = e.clientY - rect.top + scrollTop - totalHeaderHeight;
|
|
861
|
+
const targetRow = Math.max(0, Math.min(Math.floor(mouseY / rowHeight), core.getRowCount() - 1));
|
|
862
|
+
let targetCol = 0;
|
|
863
|
+
for (let i = 0; i < columnPositions.length - 1; i++) {
|
|
864
|
+
if (mouseX >= columnPositions[i] && mouseX < columnPositions[i + 1]) {
|
|
865
|
+
targetCol = i;
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
if (mouseX >= columnPositions[columnPositions.length - 1]) targetCol = columnPositions.length - 2;
|
|
869
|
+
}
|
|
870
|
+
targetCol = Math.max(0, Math.min(targetCol, columns.length - 1));
|
|
871
|
+
core.selection.startSelection({
|
|
872
|
+
row: targetRow,
|
|
873
|
+
col: targetCol
|
|
874
|
+
}, { shift: true });
|
|
875
|
+
const mouseYInContainer = e.clientY - rect.top;
|
|
876
|
+
const mouseXInContainer = e.clientX - rect.left;
|
|
877
|
+
if (autoScrollIntervalRef.current) {
|
|
878
|
+
clearInterval(autoScrollIntervalRef.current);
|
|
879
|
+
autoScrollIntervalRef.current = null;
|
|
880
|
+
}
|
|
881
|
+
let scrollDeltaX = 0;
|
|
882
|
+
let scrollDeltaY = 0;
|
|
883
|
+
if (mouseYInContainer < SCROLL_THRESHOLD + totalHeaderHeight) scrollDeltaY = -SCROLL_SPEED;
|
|
884
|
+
else if (mouseYInContainer > rect.height - SCROLL_THRESHOLD) scrollDeltaY = SCROLL_SPEED;
|
|
885
|
+
if (mouseXInContainer < SCROLL_THRESHOLD) scrollDeltaX = -SCROLL_SPEED;
|
|
886
|
+
else if (mouseXInContainer > rect.width - SCROLL_THRESHOLD) scrollDeltaX = SCROLL_SPEED;
|
|
887
|
+
if (scrollDeltaX !== 0 || scrollDeltaY !== 0) autoScrollIntervalRef.current = setInterval(() => {
|
|
888
|
+
if (containerRef.current) {
|
|
889
|
+
containerRef.current.scrollTop += scrollDeltaY;
|
|
890
|
+
containerRef.current.scrollLeft += scrollDeltaX;
|
|
891
|
+
}
|
|
892
|
+
}, 16);
|
|
893
|
+
};
|
|
894
|
+
const handleMouseUp = () => {
|
|
895
|
+
if (autoScrollIntervalRef.current) {
|
|
896
|
+
clearInterval(autoScrollIntervalRef.current);
|
|
897
|
+
autoScrollIntervalRef.current = null;
|
|
898
|
+
}
|
|
899
|
+
setIsDraggingSelection(false);
|
|
900
|
+
};
|
|
901
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
902
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
903
|
+
return () => {
|
|
904
|
+
if (autoScrollIntervalRef.current) {
|
|
905
|
+
clearInterval(autoScrollIntervalRef.current);
|
|
906
|
+
autoScrollIntervalRef.current = null;
|
|
907
|
+
}
|
|
908
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
909
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
910
|
+
};
|
|
911
|
+
}, [
|
|
912
|
+
isDraggingSelection,
|
|
913
|
+
totalHeaderHeight,
|
|
914
|
+
rowHeight,
|
|
915
|
+
columnPositions,
|
|
916
|
+
columns.length
|
|
917
|
+
]);
|
|
841
918
|
const isSelected = useCallback((row, col) => {
|
|
842
919
|
const { selectionRange } = state;
|
|
843
920
|
if (!selectionRange) return false;
|
|
@@ -1130,7 +1207,7 @@ function Grid(props) {
|
|
|
1130
1207
|
width: `${column.width}px`,
|
|
1131
1208
|
height: `${rowHeight}px`
|
|
1132
1209
|
},
|
|
1133
|
-
|
|
1210
|
+
onMouseDown: (e) => handleCellMouseDown(slot.rowIndex, colIndex, e),
|
|
1134
1211
|
onDoubleClick: () => handleCellDoubleClick(slot.rowIndex, colIndex),
|
|
1135
1212
|
children: isEditing && state.editingCell ? renderEditCell(column, slot.rowData, slot.rowIndex, colIndex, state.editingCell.initialValue) : renderCell(column, slot.rowData, slot.rowIndex, colIndex)
|
|
1136
1213
|
}, `${slot.slotId}-${colIndex}`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["stateChanges: Partial<GridState>","createDataSourceFromArray","createClientDataSource","newDirection: SortDirection | null","value: unknown","rowData","params: CellRendererParams","params: EditRendererParams","params: HeaderRendererParams","row: number","col: number","minCol: number","maxCol: number"],"sources":["../src/styles.ts","../src/Grid.tsx"],"sourcesContent":["// packages/react/src/styles.ts\r\n// Dynamic CSS injection for gp-grid-react\r\n\r\nconst STYLE_ID = \"gp-grid-styles\";\r\n\r\nexport const gridStyles = `\r\n/* =============================================================================\r\n GP Grid - CSS Variables for Theming\r\n ============================================================================= */\r\n\r\n.gp-grid-container {\r\n /* Colors - Light Mode (default) */\r\n --gp-grid-bg: #ffffff;\r\n --gp-grid-bg-alt: #f8f9fa;\r\n --gp-grid-text: #212529;\r\n --gp-grid-text-secondary: #6c757d;\r\n --gp-grid-text-muted: #adb5bd;\r\n --gp-grid-border: #dee2e6;\r\n --gp-grid-border-light: #e9ecef;\r\n \r\n /* Header */\r\n --gp-grid-header-bg: #f1f3f5;\r\n --gp-grid-header-text: #212529;\r\n \r\n /* Selection */\r\n --gp-grid-primary: #228be6;\r\n --gp-grid-primary-light: #e7f5ff;\r\n --gp-grid-primary-border: #74c0fc;\r\n --gp-grid-hover: #f1f3f5;\r\n \r\n /* Filter */\r\n --gp-grid-filter-bg: #f8f9fa;\r\n --gp-grid-input-bg: #ffffff;\r\n --gp-grid-input-border: #ced4da;\r\n \r\n /* Error */\r\n --gp-grid-error-bg: #fff5f5;\r\n --gp-grid-error-text: #c92a2a;\r\n \r\n /* Loading */\r\n --gp-grid-loading-bg: rgba(255, 255, 255, 0.95);\r\n --gp-grid-loading-text: #495057;\r\n \r\n /* Scrollbar */\r\n --gp-grid-scrollbar-track: #f1f3f5;\r\n --gp-grid-scrollbar-thumb: #ced4da;\r\n --gp-grid-scrollbar-thumb-hover: #adb5bd;\r\n}\r\n\r\n/* Dark Mode */\r\n.gp-grid-container--dark {\r\n --gp-grid-bg: #1a1b1e;\r\n --gp-grid-bg-alt: #25262b;\r\n --gp-grid-text: #c1c2c5;\r\n --gp-grid-text-secondary: #909296;\r\n --gp-grid-text-muted: #5c5f66;\r\n --gp-grid-border: #373a40;\r\n --gp-grid-border-light: #2c2e33;\r\n \r\n /* Header */\r\n --gp-grid-header-bg: #25262b;\r\n --gp-grid-header-text: #c1c2c5;\r\n \r\n /* Selection */\r\n --gp-grid-primary: #339af0;\r\n --gp-grid-primary-light: #1c3d5a;\r\n --gp-grid-primary-border: #1c7ed6;\r\n --gp-grid-hover: #2c2e33;\r\n \r\n /* Filter */\r\n --gp-grid-filter-bg: #25262b;\r\n --gp-grid-input-bg: #1a1b1e;\r\n --gp-grid-input-border: #373a40;\r\n \r\n /* Error */\r\n --gp-grid-error-bg: #2c1a1a;\r\n --gp-grid-error-text: #ff6b6b;\r\n \r\n /* Loading */\r\n --gp-grid-loading-bg: rgba(26, 27, 30, 0.95);\r\n --gp-grid-loading-text: #c1c2c5;\r\n \r\n /* Scrollbar */\r\n --gp-grid-scrollbar-track: #25262b;\r\n --gp-grid-scrollbar-thumb: #373a40;\r\n --gp-grid-scrollbar-thumb-hover: #4a4d52;\r\n}\r\n\r\n/* =============================================================================\r\n GP Grid - Clean Flat Design\r\n ============================================================================= */\r\n\r\n/* Grid Container */\r\n.gp-grid-container {\r\n outline: none;\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\r\n font-size: 13px;\r\n line-height: 1.5;\r\n color: var(--gp-grid-text);\r\n background-color: var(--gp-grid-bg);\r\n border: 1px solid var(--gp-grid-border);\r\n border-radius: 6px;\r\n}\r\n\r\n.gp-grid-container:focus {\r\n outline: none;\r\n border-color: var(--gp-grid-primary);\r\n}\r\n\r\n/* =============================================================================\r\n Header\r\n ============================================================================= */\r\n\r\n.gp-grid-header {\r\n position: sticky;\r\n top: 0;\r\n left: 0;\r\n z-index: 100;\r\n background-color: var(--gp-grid-header-bg);\r\n border-bottom: 1px solid var(--gp-grid-border);\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-cell {\r\n position: absolute;\r\n box-sizing: border-box;\r\n border-right: 1px solid var(--gp-grid-border);\r\n font-weight: 600;\r\n font-size: 12px;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n color: var(--gp-grid-header-text);\r\n cursor: pointer;\r\n user-select: none;\r\n display: flex;\r\n align-items: center;\r\n padding: 0 12px;\r\n background-color: transparent;\r\n transition: background-color 0.1s ease;\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-cell:hover {\r\n background-color: var(--gp-grid-hover);\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-cell:active {\r\n background-color: var(--gp-grid-border-light);\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: var(--gp-grid-header-text);\r\n}\r\n\r\n.gp-grid-sort-indicator {\r\n margin-left: 6px;\r\n font-size: 10px;\r\n color: var(--gp-grid-primary);\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.gp-grid-sort-index {\r\n font-size: 9px;\r\n margin-left: 2px;\r\n color: var(--gp-grid-text-secondary);\r\n}\r\n\r\n/* =============================================================================\r\n Filter Row\r\n ============================================================================= */\r\n\r\n.gp-grid-filter-row {\r\n position: sticky;\r\n left: 0;\r\n z-index: 99;\r\n background-color: var(--gp-grid-filter-bg);\r\n border-bottom: 1px solid var(--gp-grid-border);\r\n}\r\n\r\n.gp-grid-filter-cell {\r\n position: absolute;\r\n box-sizing: border-box;\r\n border-right: 1px solid var(--gp-grid-border);\r\n padding: 6px 8px;\r\n display: flex;\r\n align-items: center;\r\n background-color: var(--gp-grid-filter-bg);\r\n}\r\n\r\n.gp-grid-filter-input {\r\n width: 100%;\r\n height: 28px;\r\n padding: 0 10px;\r\n font-family: inherit;\r\n font-size: 12px;\r\n border: 1px solid var(--gp-grid-input-border);\r\n border-radius: 4px;\r\n background-color: var(--gp-grid-input-bg);\r\n color: var(--gp-grid-text);\r\n transition: border-color 0.15s ease;\r\n}\r\n\r\n.gp-grid-filter-input:focus {\r\n outline: none;\r\n border-color: var(--gp-grid-primary);\r\n}\r\n\r\n.gp-grid-filter-input::placeholder {\r\n color: var(--gp-grid-text-muted);\r\n}\r\n\r\n/* =============================================================================\r\n Data Cells\r\n ============================================================================= */\r\n\r\n.gp-grid-row {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n}\r\n\r\n.gp-grid-cell {\r\n position: absolute;\r\n top: 0;\r\n box-sizing: border-box;\r\n padding: 0 12px;\r\n display: flex;\r\n align-items: center;\r\n cursor: cell;\r\n color: var(--gp-grid-text);\r\n border-right: 1px solid var(--gp-grid-border-light);\r\n border-bottom: 1px solid var(--gp-grid-border-light);\r\n background-color: var(--gp-grid-bg);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Alternating row colors */\r\n.gp-grid-row--even .gp-grid-cell {\r\n background-color: var(--gp-grid-bg-alt);\r\n}\r\n\r\n.gp-grid-cell:hover {\r\n background-color: var(--gp-grid-hover) !important;\r\n}\r\n\r\n/* Active cell (focused) */\r\n.gp-grid-cell--active {\r\n background-color: var(--gp-grid-primary-light) !important;\r\n border: 2px solid var(--gp-grid-primary) !important;\r\n outline: none;\r\n z-index: 5;\r\n padding: 0 11px;\r\n}\r\n\r\n/* Selected cells (range selection) */\r\n.gp-grid-cell--selected {\r\n background-color: var(--gp-grid-primary-light) !important;\r\n}\r\n\r\n/* Editing cell */\r\n.gp-grid-cell--editing {\r\n background-color: var(--gp-grid-bg) !important;\r\n border: 2px solid var(--gp-grid-primary) !important;\r\n padding: 0 !important;\r\n z-index: 10;\r\n}\r\n\r\n/* =============================================================================\r\n Fill Handle (drag to fill)\r\n ============================================================================= */\r\n\r\n.gp-grid-fill-handle {\r\n position: absolute;\r\n width: 8px;\r\n height: 8px;\r\n background-color: var(--gp-grid-primary);\r\n border: 2px solid var(--gp-grid-bg);\r\n cursor: crosshair;\r\n z-index: 100;\r\n pointer-events: auto;\r\n box-sizing: border-box;\r\n border-radius: 1px;\r\n}\r\n\r\n.gp-grid-fill-handle:hover {\r\n transform: scale(1.2);\r\n}\r\n\r\n/* Fill preview (cells being filled) */\r\n.gp-grid-cell--fill-preview {\r\n background-color: var(--gp-grid-primary-light) !important;\r\n border: 1px dashed var(--gp-grid-primary) !important;\r\n}\r\n\r\n/* =============================================================================\r\n Edit Input\r\n ============================================================================= */\r\n\r\n.gp-grid-edit-input {\r\n width: 100%;\r\n height: 100%;\r\n padding: 0 11px;\r\n font-family: inherit;\r\n font-size: inherit;\r\n color: var(--gp-grid-text);\r\n border: none;\r\n background-color: transparent;\r\n}\r\n\r\n.gp-grid-edit-input:focus {\r\n outline: none;\r\n}\r\n\r\n/* =============================================================================\r\n Loading & Error States\r\n ============================================================================= */\r\n\r\n.gp-grid-loading {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n padding: 12px 20px;\r\n background-color: var(--gp-grid-loading-bg);\r\n color: var(--gp-grid-loading-text);\r\n border-radius: 6px;\r\n border: 1px solid var(--gp-grid-border);\r\n font-weight: 500;\r\n font-size: 13px;\r\n z-index: 1000;\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n.gp-grid-loading-spinner {\r\n width: 16px;\r\n height: 16px;\r\n border: 2px solid var(--gp-grid-border);\r\n border-top-color: var(--gp-grid-primary);\r\n border-radius: 50%;\r\n animation: gp-grid-spin 0.7s linear infinite;\r\n}\r\n\r\n@keyframes gp-grid-spin {\r\n to {\r\n transform: rotate(360deg);\r\n }\r\n}\r\n\r\n.gp-grid-error {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n padding: 12px 20px;\r\n background-color: var(--gp-grid-error-bg);\r\n color: var(--gp-grid-error-text);\r\n border-radius: 6px;\r\n border: 1px solid var(--gp-grid-error-text);\r\n font-weight: 500;\r\n font-size: 13px;\r\n z-index: 1000;\r\n max-width: 80%;\r\n text-align: center;\r\n}\r\n\r\n/* =============================================================================\r\n Empty State\r\n ============================================================================= */\r\n\r\n.gp-grid-empty {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n color: var(--gp-grid-text-muted);\r\n font-size: 14px;\r\n text-align: center;\r\n}\r\n\r\n/* =============================================================================\r\n Scrollbar Styling\r\n ============================================================================= */\r\n\r\n.gp-grid-container::-webkit-scrollbar {\r\n width: 8px;\r\n height: 8px;\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-track {\r\n background-color: var(--gp-grid-scrollbar-track);\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-thumb {\r\n background-color: var(--gp-grid-scrollbar-thumb);\r\n border-radius: 4px;\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-thumb:hover {\r\n background-color: var(--gp-grid-scrollbar-thumb-hover);\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-corner {\r\n background-color: var(--gp-grid-scrollbar-track);\r\n}\r\n`;\r\n\r\nlet stylesInjected = false;\r\n\r\n/**\r\n * Inject grid styles into the document head.\r\n * This is called automatically when the Grid component mounts.\r\n * Styles are only injected once, even if multiple Grid instances exist.\r\n */\r\nexport function injectStyles(): void {\r\n if (stylesInjected) return;\r\n if (typeof document === \"undefined\") return; // SSR safety\r\n\r\n // Check if styles already exist (e.g., from a previous mount)\r\n if (document.getElementById(STYLE_ID)) {\r\n stylesInjected = true;\r\n return;\r\n }\r\n\r\n const styleElement = document.createElement(\"style\");\r\n styleElement.id = STYLE_ID;\r\n styleElement.textContent = gridStyles;\r\n document.head.appendChild(styleElement);\r\n stylesInjected = true;\r\n}\r\n","// packages/react/src/Grid.tsx\r\n\r\nimport React, {\r\n useEffect,\r\n useRef,\r\n useReducer,\r\n useCallback,\r\n useMemo,\r\n useState,\r\n} from \"react\";\r\nimport {\r\n GridCore,\r\n createClientDataSource,\r\n createDataSourceFromArray,\r\n} from \"gp-grid-core\";\r\nimport type {\r\n GridInstruction,\r\n ColumnDefinition,\r\n DataSource,\r\n Row,\r\n CellRendererParams,\r\n EditRendererParams,\r\n HeaderRendererParams,\r\n CellPosition,\r\n CellRange,\r\n CellValue,\r\n SortDirection,\r\n} from \"gp-grid-core\";\r\nimport { injectStyles } from \"./styles\";\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport type ReactCellRenderer = (params: CellRendererParams) => React.ReactNode;\r\nexport type ReactEditRenderer = (params: EditRendererParams) => React.ReactNode;\r\nexport type ReactHeaderRenderer = (params: HeaderRendererParams) => React.ReactNode;\r\n\r\nexport interface GridProps<TData extends Row = Row> {\r\n columns: ColumnDefinition[];\r\n /** Data source for the grid */\r\n dataSource?: DataSource<TData>;\r\n /** Legacy: Raw row data (will be wrapped in a client data source) */\r\n rowData?: TData[];\r\n rowHeight: number;\r\n headerHeight?: number;\r\n overscan?: number;\r\n /** Show filter row below headers */\r\n showFilters?: boolean;\r\n /** Debounce time for filter input (ms) */\r\n filterDebounce?: number;\r\n /** Enable dark mode styling */\r\n darkMode?: boolean;\r\n\r\n // Renderer registries\r\n cellRenderers?: Record<string, ReactCellRenderer>;\r\n editRenderers?: Record<string, ReactEditRenderer>;\r\n headerRenderers?: Record<string, ReactHeaderRenderer>;\r\n\r\n // Global fallback renderers\r\n cellRenderer?: ReactCellRenderer;\r\n editRenderer?: ReactEditRenderer;\r\n headerRenderer?: ReactHeaderRenderer;\r\n}\r\n\r\n// =============================================================================\r\n// State Types\r\n// =============================================================================\r\n\r\ninterface SlotData {\r\n slotId: string;\r\n rowIndex: number;\r\n rowData: Row;\r\n translateY: number;\r\n}\r\n\r\ninterface GridState {\r\n slots: Map<string, SlotData>;\r\n activeCell: CellPosition | null;\r\n selectionRange: CellRange | null;\r\n editingCell: { row: number; col: number; initialValue: CellValue } | null;\r\n contentWidth: number;\r\n contentHeight: number;\r\n headers: Map<number, { column: ColumnDefinition; sortDirection?: SortDirection; sortIndex?: number }>;\r\n isLoading: boolean;\r\n error: string | null;\r\n totalRows: number;\r\n}\r\n\r\ntype GridAction =\r\n | { type: \"BATCH_INSTRUCTIONS\"; instructions: GridInstruction[] }\r\n | { type: \"RESET\" };\r\n\r\n// =============================================================================\r\n// Reducer\r\n// =============================================================================\r\n\r\n/**\r\n * Apply a single instruction to mutable slot maps and return other state changes.\r\n * This allows batching multiple slot operations efficiently.\r\n */\r\nfunction applyInstruction(\r\n instruction: GridInstruction,\r\n slots: Map<string, SlotData>,\r\n headers: Map<number, { column: ColumnDefinition; sortDirection?: SortDirection; sortIndex?: number }>\r\n): Partial<GridState> | null {\r\n switch (instruction.type) {\r\n case \"CREATE_SLOT\":\r\n slots.set(instruction.slotId, {\r\n slotId: instruction.slotId,\r\n rowIndex: -1,\r\n rowData: {},\r\n translateY: 0,\r\n });\r\n return null; // Slots map is mutated\r\n\r\n case \"DESTROY_SLOT\":\r\n slots.delete(instruction.slotId);\r\n return null;\r\n\r\n case \"ASSIGN_SLOT\": {\r\n const existing = slots.get(instruction.slotId);\r\n if (existing) {\r\n slots.set(instruction.slotId, {\r\n ...existing,\r\n rowIndex: instruction.rowIndex,\r\n rowData: instruction.rowData,\r\n });\r\n }\r\n return null;\r\n }\r\n\r\n case \"MOVE_SLOT\": {\r\n const existing = slots.get(instruction.slotId);\r\n if (existing) {\r\n slots.set(instruction.slotId, {\r\n ...existing,\r\n translateY: instruction.translateY,\r\n });\r\n }\r\n return null;\r\n }\r\n\r\n case \"SET_ACTIVE_CELL\":\r\n return { activeCell: instruction.position };\r\n\r\n case \"SET_SELECTION_RANGE\":\r\n return { selectionRange: instruction.range };\r\n\r\n case \"START_EDIT\":\r\n return {\r\n editingCell: {\r\n row: instruction.row,\r\n col: instruction.col,\r\n initialValue: instruction.initialValue,\r\n },\r\n };\r\n\r\n case \"STOP_EDIT\":\r\n return { editingCell: null };\r\n\r\n case \"SET_CONTENT_SIZE\":\r\n return {\r\n contentWidth: instruction.width,\r\n contentHeight: instruction.height,\r\n };\r\n\r\n case \"UPDATE_HEADER\":\r\n headers.set(instruction.colIndex, {\r\n column: instruction.column,\r\n sortDirection: instruction.sortDirection,\r\n sortIndex: instruction.sortIndex,\r\n });\r\n return null;\r\n\r\n case \"DATA_LOADING\":\r\n return { isLoading: true, error: null };\r\n\r\n case \"DATA_LOADED\":\r\n return { isLoading: false, totalRows: instruction.totalRows };\r\n\r\n case \"DATA_ERROR\":\r\n return { isLoading: false, error: instruction.error };\r\n\r\n default:\r\n return null;\r\n }\r\n}\r\n\r\nfunction gridReducer(state: GridState, action: GridAction): GridState {\r\n if (action.type === \"RESET\") {\r\n return createInitialState();\r\n }\r\n\r\n // Process batch of instructions in one state update\r\n const { instructions } = action;\r\n // console.log(\"[GP-Grid Reducer] Processing batch:\", instructions.map(i => i.type));\r\n if (instructions.length === 0) {\r\n return state;\r\n }\r\n\r\n // Create mutable copies of Maps to batch updates\r\n const newSlots = new Map(state.slots);\r\n const newHeaders = new Map(state.headers);\r\n let stateChanges: Partial<GridState> = {};\r\n\r\n // Apply all instructions\r\n for (const instruction of instructions) {\r\n const changes = applyInstruction(instruction, newSlots, newHeaders);\r\n if (changes) {\r\n stateChanges = { ...stateChanges, ...changes };\r\n }\r\n }\r\n\r\n // Return new state with all changes applied\r\n return {\r\n ...state,\r\n ...stateChanges,\r\n slots: newSlots,\r\n headers: newHeaders,\r\n };\r\n}\r\n\r\nfunction createInitialState(): GridState {\r\n return {\r\n slots: new Map(),\r\n activeCell: null,\r\n selectionRange: null,\r\n editingCell: null,\r\n contentWidth: 0,\r\n contentHeight: 0,\r\n headers: new Map(),\r\n isLoading: false,\r\n error: null,\r\n totalRows: 0,\r\n };\r\n}\r\n\r\n// =============================================================================\r\n// Grid Component\r\n// =============================================================================\r\n\r\nexport function Grid<TData extends Row = Row>(props: GridProps<TData>) {\r\n // Inject styles on first render (safe to call multiple times)\r\n injectStyles();\r\n\r\n const {\r\n columns,\r\n dataSource: providedDataSource,\r\n rowData,\r\n rowHeight,\r\n headerHeight = rowHeight,\r\n overscan = 3,\r\n showFilters = false,\r\n filterDebounce = 300,\r\n darkMode = false,\r\n cellRenderers = {},\r\n editRenderers = {},\r\n headerRenderers = {},\r\n cellRenderer,\r\n editRenderer,\r\n headerRenderer,\r\n } = props;\r\n\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const coreRef = useRef<GridCore<TData> | null>(null);\r\n const [state, dispatch] = useReducer(gridReducer, null, createInitialState);\r\n const [filterValues, setFilterValues] = useState<Record<string, string>>({});\r\n const filterTimeoutRef = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\r\n const [isDraggingFill, setIsDraggingFill] = useState(false);\r\n const [fillTarget, setFillTarget] = useState<{ row: number; col: number } | null>(null);\r\n const [fillSourceRange, setFillSourceRange] = useState<{ startRow: number; startCol: number; endRow: number; endCol: number } | null>(null);\r\n const autoScrollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\r\n\r\n // Computed heights\r\n const filterRowHeight = showFilters ? 40 : 0;\r\n const totalHeaderHeight = headerHeight + filterRowHeight;\r\n\r\n // Create data source from rowData if not provided\r\n const dataSource = useMemo(() => {\r\n if (providedDataSource) {\r\n return providedDataSource;\r\n }\r\n if (rowData) {\r\n return createDataSourceFromArray(rowData);\r\n }\r\n // Empty data source\r\n return createClientDataSource<TData>([]);\r\n }, [providedDataSource, rowData]);\r\n\r\n // Compute column positions\r\n const columnPositions = useMemo(() => {\r\n const positions = [0];\r\n let pos = 0;\r\n for (const col of columns) {\r\n pos += col.width;\r\n positions.push(pos);\r\n }\r\n return positions;\r\n }, [columns]);\r\n\r\n const totalWidth = columnPositions[columnPositions.length - 1] ?? 0;\r\n\r\n // Initialize GridCore\r\n useEffect(() => {\r\n const core = new GridCore<TData>({\r\n columns,\r\n dataSource,\r\n rowHeight,\r\n headerHeight: totalHeaderHeight,\r\n overscan,\r\n });\r\n\r\n coreRef.current = core;\r\n\r\n // Subscribe to batched instructions for efficient state updates\r\n const unsubscribe = core.onBatchInstruction((instructions) => {\r\n dispatch({ type: \"BATCH_INSTRUCTIONS\", instructions });\r\n });\r\n\r\n // Initialize\r\n core.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n coreRef.current = null;\r\n };\r\n }, [columns, dataSource, rowHeight, totalHeaderHeight, overscan]);\r\n\r\n // Handle scroll\r\n const handleScroll = useCallback(() => {\r\n const container = containerRef.current;\r\n const core = coreRef.current;\r\n if (!container || !core) return;\r\n\r\n core.setViewport(\r\n container.scrollTop,\r\n container.scrollLeft,\r\n container.clientWidth,\r\n container.clientHeight\r\n );\r\n }, []);\r\n\r\n // Initial measurement\r\n useEffect(() => {\r\n const container = containerRef.current;\r\n const core = coreRef.current;\r\n if (!container || !core) return;\r\n\r\n const resizeObserver = new ResizeObserver(() => {\r\n core.setViewport(\r\n container.scrollTop,\r\n container.scrollLeft,\r\n container.clientWidth,\r\n container.clientHeight\r\n );\r\n });\r\n\r\n resizeObserver.observe(container);\r\n handleScroll();\r\n\r\n return () => resizeObserver.disconnect();\r\n }, [handleScroll]);\r\n\r\n // Handle filter change with debounce\r\n const handleFilterChange = useCallback(\r\n (colId: string, value: string) => {\r\n setFilterValues((prev) => ({ ...prev, [colId]: value }));\r\n\r\n // Clear existing timeout\r\n if (filterTimeoutRef.current[colId]) {\r\n clearTimeout(filterTimeoutRef.current[colId]);\r\n }\r\n\r\n // Debounce the actual filter application\r\n filterTimeoutRef.current[colId] = setTimeout(() => {\r\n const core = coreRef.current;\r\n if (core) {\r\n core.setFilter(colId, value);\r\n }\r\n }, filterDebounce);\r\n },\r\n [filterDebounce]\r\n );\r\n\r\n // Keyboard navigation\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent) => {\r\n const core = coreRef.current;\r\n if (!core) return;\r\n\r\n // Don't handle keyboard events when editing\r\n if (state.editingCell && e.key !== \"Enter\" && e.key !== \"Escape\" && e.key !== \"Tab\") {\r\n return;\r\n }\r\n\r\n const { selection } = core;\r\n const isShift = e.shiftKey;\r\n const isCtrl = e.ctrlKey || e.metaKey;\r\n\r\n switch (e.key) {\r\n case \"ArrowUp\":\r\n e.preventDefault();\r\n selection.moveFocus(\"up\", isShift);\r\n break;\r\n case \"ArrowDown\":\r\n e.preventDefault();\r\n selection.moveFocus(\"down\", isShift);\r\n break;\r\n case \"ArrowLeft\":\r\n e.preventDefault();\r\n selection.moveFocus(\"left\", isShift);\r\n break;\r\n case \"ArrowRight\":\r\n e.preventDefault();\r\n selection.moveFocus(\"right\", isShift);\r\n break;\r\n case \"Enter\":\r\n e.preventDefault();\r\n if (state.editingCell) {\r\n core.commitEdit();\r\n } else if (state.activeCell) {\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n case \"Escape\":\r\n e.preventDefault();\r\n if (state.editingCell) {\r\n core.cancelEdit();\r\n } else {\r\n selection.clearSelection();\r\n }\r\n break;\r\n case \"Tab\":\r\n e.preventDefault();\r\n if (state.editingCell) {\r\n core.commitEdit();\r\n }\r\n selection.moveFocus(isShift ? \"left\" : \"right\", false);\r\n break;\r\n case \"a\":\r\n if (isCtrl) {\r\n e.preventDefault();\r\n selection.selectAll();\r\n }\r\n break;\r\n case \"c\":\r\n if (isCtrl) {\r\n e.preventDefault();\r\n selection.copySelectionToClipboard();\r\n }\r\n break;\r\n case \"F2\":\r\n e.preventDefault();\r\n if (state.activeCell && !state.editingCell) {\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n case \"Delete\":\r\n case \"Backspace\":\r\n // Start editing with empty value on delete/backspace\r\n if (state.activeCell && !state.editingCell) {\r\n e.preventDefault();\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n default:\r\n // Start editing on any printable character\r\n if (\r\n state.activeCell &&\r\n !state.editingCell &&\r\n !isCtrl &&\r\n e.key.length === 1\r\n ) {\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n }\r\n },\r\n [state.activeCell, state.editingCell]\r\n );\r\n\r\n // Scroll active cell into view when navigating with keyboard\r\n useEffect(() => {\r\n // Skip scrolling when editing - the user just clicked on the cell so it's already visible\r\n if (!state.activeCell || !containerRef.current || state.editingCell) return;\r\n\r\n const { row, col } = state.activeCell;\r\n const container = containerRef.current;\r\n\r\n // Calculate cell position\r\n const cellTop = row * rowHeight + totalHeaderHeight;\r\n const cellBottom = cellTop + rowHeight;\r\n const cellLeft = columnPositions[col] ?? 0;\r\n const cellRight = cellLeft + (columns[col]?.width ?? 0);\r\n\r\n // Get visible area\r\n const visibleTop = container.scrollTop + totalHeaderHeight;\r\n const visibleBottom = container.scrollTop + container.clientHeight;\r\n const visibleLeft = container.scrollLeft;\r\n const visibleRight = container.scrollLeft + container.clientWidth;\r\n\r\n // Scroll vertically if needed\r\n if (cellTop < visibleTop) {\r\n container.scrollTop = cellTop - totalHeaderHeight;\r\n } else if (cellBottom > visibleBottom) {\r\n container.scrollTop = cellBottom - container.clientHeight;\r\n }\r\n\r\n // Scroll horizontally if needed\r\n if (cellLeft < visibleLeft) {\r\n container.scrollLeft = cellLeft;\r\n } else if (cellRight > visibleRight) {\r\n container.scrollLeft = cellRight - container.clientWidth;\r\n }\r\n }, [state.activeCell, state.editingCell, rowHeight, totalHeaderHeight, columnPositions, columns]);\r\n\r\n // Cell click handler\r\n const handleCellClick = useCallback(\r\n (rowIndex: number, colIndex: number, e: React.MouseEvent) => {\r\n // console.log(\"[GP-Grid] Cell click:\", { rowIndex, colIndex, coreExists: !!coreRef.current });\r\n const core = coreRef.current;\r\n if (!core || core.getEditState() !== null) {\r\n // console.warn(\"[GP-Grid] Core not initialized on cell click\");\r\n return;\r\n }\r\n\r\n // Focus the container to enable keyboard navigation\r\n containerRef.current?.focus();\r\n\r\n core.selection.startSelection(\r\n { row: rowIndex, col: colIndex },\r\n { shift: e.shiftKey, ctrl: e.ctrlKey || e.metaKey }\r\n );\r\n },\r\n []\r\n );\r\n\r\n // Cell double-click handler\r\n const handleCellDoubleClick = useCallback(\r\n (rowIndex: number, colIndex: number) => {\r\n const core = coreRef.current;\r\n if (!core) return;\r\n\r\n core.startEdit(rowIndex, colIndex);\r\n },\r\n []\r\n );\r\n\r\n // Header click handler (sort)\r\n const handleHeaderClick = useCallback(\r\n (colIndex: number, e: React.MouseEvent) => {\r\n // console.log(\"[GP-Grid] Header click:\", { colIndex, coreExists: !!coreRef.current });\r\n const core = coreRef.current;\r\n if (!core) {\r\n // console.warn(\"[GP-Grid] Core not initialized on header click\");\r\n return;\r\n }\r\n\r\n const column = columns[colIndex];\r\n if (!column) {\r\n // console.warn(\"[GP-Grid] Column not found for index:\", colIndex);\r\n return;\r\n }\r\n\r\n const colId = column.colId ?? column.field;\r\n const headerInfo = state.headers.get(colIndex);\r\n const currentDirection = headerInfo?.sortDirection;\r\n\r\n // Cycle: none -> asc -> desc -> none\r\n let newDirection: SortDirection | null;\r\n if (!currentDirection) {\r\n newDirection = \"asc\";\r\n } else if (currentDirection === \"asc\") {\r\n newDirection = \"desc\";\r\n } else {\r\n newDirection = null;\r\n }\r\n\r\n // console.log(\"[GP-Grid] Setting sort:\", { colId, newDirection });\r\n core.setSort(colId, newDirection, e.shiftKey);\r\n },\r\n [columns, state.headers]\r\n );\r\n\r\n // Fill handle drag handlers\r\n const handleFillHandleMouseDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n // console.log(\"[GP-Grid] Fill handle mousedown triggered\");\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const core = coreRef.current;\r\n if (!core) return;\r\n\r\n const { activeCell, selectionRange } = state;\r\n if (!activeCell && !selectionRange) return;\r\n\r\n // Create source range from selection or active cell\r\n const sourceRange = selectionRange ?? {\r\n startRow: activeCell!.row,\r\n startCol: activeCell!.col,\r\n endRow: activeCell!.row,\r\n endCol: activeCell!.col,\r\n };\r\n\r\n // console.log(\"[GP-Grid] Starting fill drag with source range:\", sourceRange);\r\n core.fill.startFillDrag(sourceRange);\r\n setFillSourceRange(sourceRange);\r\n setFillTarget({ \r\n row: Math.max(sourceRange.startRow, sourceRange.endRow),\r\n col: Math.max(sourceRange.startCol, sourceRange.endCol)\r\n });\r\n setIsDraggingFill(true);\r\n },\r\n [state.activeCell, state.selectionRange]\r\n );\r\n\r\n // Handle mouse move during fill drag\r\n useEffect(() => {\r\n if (!isDraggingFill) return;\r\n\r\n // Auto-scroll configuration\r\n const SCROLL_THRESHOLD = 40; // pixels from edge to trigger scroll\r\n const SCROLL_SPEED = 10; // pixels per frame\r\n\r\n const handleMouseMove = (e: MouseEvent) => {\r\n const core = coreRef.current;\r\n const container = containerRef.current;\r\n if (!core || !container) return;\r\n\r\n // Get container bounds\r\n const rect = container.getBoundingClientRect();\r\n const scrollLeft = container.scrollLeft;\r\n const scrollTop = container.scrollTop;\r\n\r\n // Calculate mouse position relative to grid content\r\n const mouseX = e.clientX - rect.left + scrollLeft;\r\n const mouseY = e.clientY - rect.top + scrollTop - totalHeaderHeight;\r\n\r\n // Find the row and column under the mouse\r\n const targetRow = Math.max(0, Math.floor(mouseY / rowHeight));\r\n \r\n // Find column by checking column positions\r\n let targetCol = 0;\r\n for (let i = 0; i < columnPositions.length - 1; i++) {\r\n if (mouseX >= columnPositions[i]! && mouseX < columnPositions[i + 1]!) {\r\n targetCol = i;\r\n break;\r\n }\r\n if (mouseX >= columnPositions[columnPositions.length - 1]!) {\r\n targetCol = columnPositions.length - 2;\r\n }\r\n }\r\n\r\n core.fill.updateFillDrag(targetRow, targetCol);\r\n setFillTarget({ row: targetRow, col: targetCol });\r\n\r\n // Auto-scroll logic\r\n const mouseYInContainer = e.clientY - rect.top;\r\n const mouseXInContainer = e.clientX - rect.left;\r\n\r\n // Clear any existing auto-scroll\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n\r\n // Check if we need to auto-scroll\r\n let scrollDeltaX = 0;\r\n let scrollDeltaY = 0;\r\n\r\n // Vertical scrolling\r\n if (mouseYInContainer < SCROLL_THRESHOLD + totalHeaderHeight) {\r\n scrollDeltaY = -SCROLL_SPEED;\r\n } else if (mouseYInContainer > rect.height - SCROLL_THRESHOLD) {\r\n scrollDeltaY = SCROLL_SPEED;\r\n }\r\n\r\n // Horizontal scrolling\r\n if (mouseXInContainer < SCROLL_THRESHOLD) {\r\n scrollDeltaX = -SCROLL_SPEED;\r\n } else if (mouseXInContainer > rect.width - SCROLL_THRESHOLD) {\r\n scrollDeltaX = SCROLL_SPEED;\r\n }\r\n\r\n // Start auto-scroll if needed\r\n if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {\r\n autoScrollIntervalRef.current = setInterval(() => {\r\n if (containerRef.current) {\r\n containerRef.current.scrollTop += scrollDeltaY;\r\n containerRef.current.scrollLeft += scrollDeltaX;\r\n }\r\n }, 16); // ~60fps\r\n }\r\n };\r\n\r\n const handleMouseUp = () => {\r\n // Clear auto-scroll\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n\r\n const core = coreRef.current;\r\n if (core) {\r\n core.fill.commitFillDrag();\r\n // Refresh slots to show updated values\r\n core.refreshSlotData();\r\n }\r\n setIsDraggingFill(false);\r\n setFillTarget(null);\r\n setFillSourceRange(null);\r\n };\r\n\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleMouseUp);\r\n\r\n return () => {\r\n // Clear auto-scroll on cleanup\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleMouseUp);\r\n };\r\n }, [isDraggingFill, totalHeaderHeight, rowHeight, columnPositions]);\r\n\r\n // Render helpers\r\n const isSelected = useCallback(\r\n (row: number, col: number): boolean => {\r\n const { selectionRange } = state;\r\n if (!selectionRange) return false;\r\n\r\n const minRow = Math.min(selectionRange.startRow, selectionRange.endRow);\r\n const maxRow = Math.max(selectionRange.startRow, selectionRange.endRow);\r\n const minCol = Math.min(selectionRange.startCol, selectionRange.endCol);\r\n const maxCol = Math.max(selectionRange.startCol, selectionRange.endCol);\r\n\r\n return row >= minRow && row <= maxRow && col >= minCol && col <= maxCol;\r\n },\r\n [state.selectionRange]\r\n );\r\n\r\n const isActiveCell = useCallback(\r\n (row: number, col: number): boolean => {\r\n return state.activeCell?.row === row && state.activeCell?.col === col;\r\n },\r\n [state.activeCell]\r\n );\r\n\r\n const isEditingCell = useCallback(\r\n (row: number, col: number): boolean => {\r\n return state.editingCell?.row === row && state.editingCell?.col === col;\r\n },\r\n [state.editingCell]\r\n );\r\n\r\n // Check if cell is in fill preview range\r\n const isInFillPreview = useCallback(\r\n (row: number, col: number): boolean => {\r\n if (!isDraggingFill || !fillSourceRange || !fillTarget) return false;\r\n\r\n const srcMinRow = Math.min(fillSourceRange.startRow, fillSourceRange.endRow);\r\n const srcMaxRow = Math.max(fillSourceRange.startRow, fillSourceRange.endRow);\r\n const srcMinCol = Math.min(fillSourceRange.startCol, fillSourceRange.endCol);\r\n const srcMaxCol = Math.max(fillSourceRange.startCol, fillSourceRange.endCol);\r\n\r\n // Determine fill direction and range\r\n const fillDown = fillTarget.row > srcMaxRow;\r\n const fillUp = fillTarget.row < srcMinRow;\r\n const fillRight = fillTarget.col > srcMaxCol;\r\n const fillLeft = fillTarget.col < srcMinCol;\r\n\r\n // Check if cell is in the fill preview area (not the source area)\r\n if (fillDown) {\r\n return row > srcMaxRow && row <= fillTarget.row && col >= srcMinCol && col <= srcMaxCol;\r\n }\r\n if (fillUp) {\r\n return row < srcMinRow && row >= fillTarget.row && col >= srcMinCol && col <= srcMaxCol;\r\n }\r\n if (fillRight) {\r\n return col > srcMaxCol && col <= fillTarget.col && row >= srcMinRow && row <= srcMaxRow;\r\n }\r\n if (fillLeft) {\r\n return col < srcMinCol && col >= fillTarget.col && row >= srcMinRow && row <= srcMaxRow;\r\n }\r\n\r\n return false;\r\n },\r\n [isDraggingFill, fillSourceRange, fillTarget]\r\n );\r\n\r\n // Get cell value from row data\r\n const getCellValue = useCallback((rowData: Row, field: string): CellValue => {\r\n const parts = field.split(\".\");\r\n let value: unknown = rowData;\r\n\r\n for (const part of parts) {\r\n if (value == null || typeof value !== \"object\") {\r\n return null;\r\n }\r\n value = (value as Record<string, unknown>)[part];\r\n }\r\n\r\n return (value ?? null) as CellValue;\r\n }, []);\r\n\r\n // Render cell content\r\n const renderCell = useCallback(\r\n (\r\n column: ColumnDefinition,\r\n rowData: Row,\r\n rowIndex: number,\r\n colIndex: number\r\n ): React.ReactNode => {\r\n const value = getCellValue(rowData, column.field);\r\n const params: CellRendererParams = {\r\n value,\r\n rowData,\r\n column,\r\n rowIndex,\r\n colIndex,\r\n isActive: isActiveCell(rowIndex, colIndex),\r\n isSelected: isSelected(rowIndex, colIndex),\r\n isEditing: isEditingCell(rowIndex, colIndex),\r\n };\r\n\r\n // Check for column-specific renderer\r\n if (column.cellRenderer && typeof column.cellRenderer === \"string\") {\r\n const renderer = cellRenderers[column.cellRenderer];\r\n if (renderer) {\r\n return renderer(params);\r\n }\r\n }\r\n\r\n // Fall back to global renderer\r\n if (cellRenderer) {\r\n return cellRenderer(params);\r\n }\r\n\r\n // Default text rendering\r\n return value == null ? \"\" : String(value);\r\n },\r\n [getCellValue, isActiveCell, isSelected, isEditingCell, cellRenderers, cellRenderer]\r\n );\r\n\r\n // Render edit cell\r\n const renderEditCell = useCallback(\r\n (\r\n column: ColumnDefinition,\r\n rowData: Row,\r\n rowIndex: number,\r\n colIndex: number,\r\n initialValue: CellValue\r\n ): React.ReactNode => {\r\n const core = coreRef.current;\r\n if (!core) return null;\r\n\r\n const value = getCellValue(rowData, column.field);\r\n const params: EditRendererParams = {\r\n value,\r\n rowData,\r\n column,\r\n rowIndex,\r\n colIndex,\r\n isActive: true,\r\n isSelected: true,\r\n isEditing: true,\r\n initialValue,\r\n onValueChange: (newValue) => core.updateEditValue(newValue),\r\n onCommit: () => core.commitEdit(),\r\n onCancel: () => core.cancelEdit(),\r\n };\r\n\r\n // Check for column-specific renderer\r\n if (column.editRenderer && typeof column.editRenderer === \"string\") {\r\n const renderer = editRenderers[column.editRenderer];\r\n if (renderer) {\r\n return renderer(params);\r\n }\r\n }\r\n\r\n // Fall back to global renderer\r\n if (editRenderer) {\r\n return editRenderer(params);\r\n }\r\n\r\n // Default input\r\n return (\r\n <input\r\n className=\"gp-grid-edit-input\"\r\n type=\"text\"\r\n defaultValue={initialValue == null ? \"\" : String(initialValue)}\r\n autoFocus\r\n onFocus={(e) => e.target.select()}\r\n onChange={(e) => core.updateEditValue(e.target.value)}\r\n onKeyDown={(e) => {\r\n e.stopPropagation();\r\n if (e.key === \"Enter\") {\r\n core.commitEdit();\r\n } else if (e.key === \"Escape\") {\r\n core.cancelEdit();\r\n } else if (e.key === \"Tab\") {\r\n e.preventDefault();\r\n core.commitEdit();\r\n core.selection.moveFocus(e.shiftKey ? \"left\" : \"right\", false);\r\n }\r\n }}\r\n onBlur={() => core.commitEdit()}\r\n />\r\n );\r\n },\r\n [getCellValue, editRenderers, editRenderer]\r\n );\r\n\r\n // Render header\r\n const renderHeader = useCallback(\r\n (\r\n column: ColumnDefinition,\r\n colIndex: number,\r\n sortDirection?: SortDirection,\r\n sortIndex?: number\r\n ): React.ReactNode => {\r\n const core = coreRef.current;\r\n const params: HeaderRendererParams = {\r\n column,\r\n colIndex,\r\n sortDirection,\r\n sortIndex,\r\n onSort: (direction, addToExisting) => {\r\n if (core) {\r\n core.setSort(column.colId ?? column.field, direction, addToExisting);\r\n }\r\n },\r\n };\r\n\r\n // Check for column-specific renderer\r\n if (column.headerRenderer && typeof column.headerRenderer === \"string\") {\r\n const renderer = headerRenderers[column.headerRenderer];\r\n if (renderer) {\r\n return renderer(params);\r\n }\r\n }\r\n\r\n // Fall back to global renderer\r\n if (headerRenderer) {\r\n return headerRenderer(params);\r\n }\r\n\r\n // Default header\r\n return (\r\n <>\r\n <span className=\"gp-grid-header-text\">\r\n {column.headerName ?? column.field}\r\n </span>\r\n {sortDirection && (\r\n <span className=\"gp-grid-sort-indicator\">\r\n {sortDirection === \"asc\" ? \"▲\" : \"▼\"}\r\n {sortIndex !== undefined && sortIndex > 0 && (\r\n <span className=\"gp-grid-sort-index\">{sortIndex}</span>\r\n )}\r\n </span>\r\n )}\r\n </>\r\n );\r\n },\r\n [headerRenderers, headerRenderer]\r\n );\r\n\r\n // Convert slots map to array for rendering\r\n const slotsArray = useMemo(() => Array.from(state.slots.values()), [state.slots]);\r\n\r\n // Calculate fill handle position (only show for editable columns)\r\n const fillHandlePosition = useMemo(() => {\r\n const { activeCell, selectionRange } = state;\r\n if (!activeCell && !selectionRange) return null;\r\n\r\n // Get the bottom-right corner and column range of selection or active cell\r\n let row: number, col: number;\r\n let minCol: number, maxCol: number;\r\n \r\n if (selectionRange) {\r\n row = Math.max(selectionRange.startRow, selectionRange.endRow);\r\n col = Math.max(selectionRange.startCol, selectionRange.endCol);\r\n minCol = Math.min(selectionRange.startCol, selectionRange.endCol);\r\n maxCol = Math.max(selectionRange.startCol, selectionRange.endCol);\r\n } else if (activeCell) {\r\n row = activeCell.row;\r\n col = activeCell.col;\r\n minCol = col;\r\n maxCol = col;\r\n } else {\r\n return null;\r\n }\r\n\r\n // Check if ALL columns in the selection are editable\r\n for (let c = minCol; c <= maxCol; c++) {\r\n const column = columns[c];\r\n if (!column || column.editable !== true) {\r\n return null; // Don't show fill handle if any column is not editable\r\n }\r\n }\r\n\r\n const cellTop = row * rowHeight + totalHeaderHeight;\r\n const cellLeft = columnPositions[col] ?? 0;\r\n const cellWidth = columns[col]?.width ?? 0;\r\n\r\n return {\r\n top: cellTop + rowHeight - 5,\r\n left: cellLeft + cellWidth - 20, // Move significantly left to avoid scrollbar overlap\r\n };\r\n }, [state.activeCell, state.selectionRange, rowHeight, totalHeaderHeight, columnPositions, columns]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={`gp-grid-container${darkMode ? \" gp-grid-container--dark\" : \"\"}`}\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n overflow: \"auto\",\r\n position: \"relative\",\r\n }}\r\n onScroll={handleScroll}\r\n onKeyDown={handleKeyDown}\r\n tabIndex={0}\r\n >\r\n {/* Content sizer */}\r\n <div\r\n style={{\r\n width: Math.max(state.contentWidth, totalWidth),\r\n height: Math.max(state.contentHeight, totalHeaderHeight),\r\n position: \"relative\",\r\n minWidth: \"100%\",\r\n }}\r\n >\r\n {/* Headers */}\r\n <div\r\n className=\"gp-grid-header\"\r\n style={{\r\n position: \"sticky\",\r\n top: 0,\r\n left: 0,\r\n height: headerHeight,\r\n width: Math.max(state.contentWidth, totalWidth),\r\n minWidth: \"100%\",\r\n zIndex: 100,\r\n }}\r\n >\r\n {columns.map((column, colIndex) => {\r\n const headerInfo = state.headers.get(colIndex);\r\n return (\r\n <div\r\n key={column.colId ?? column.field}\r\n className=\"gp-grid-header-cell\"\r\n style={{\r\n position: \"absolute\",\r\n left: `${columnPositions[colIndex]}px`,\r\n top: 0,\r\n width: `${column.width}px`,\r\n height: `${headerHeight}px`,\r\n background: \"transparent\",\r\n }}\r\n onClick={(e) => handleHeaderClick(colIndex, e)}\r\n >\r\n {renderHeader(\r\n column,\r\n colIndex,\r\n headerInfo?.sortDirection,\r\n headerInfo?.sortIndex\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n\r\n {/* Filter Row */}\r\n {showFilters && (\r\n <div\r\n className=\"gp-grid-filter-row\"\r\n style={{\r\n position: \"sticky\",\r\n top: headerHeight,\r\n left: 0,\r\n height: filterRowHeight,\r\n width: Math.max(state.contentWidth, totalWidth),\r\n minWidth: \"100%\",\r\n zIndex: 99,\r\n }}\r\n >\r\n {columns.map((column, colIndex) => {\r\n const colId = column.colId ?? column.field;\r\n return (\r\n <div\r\n key={`filter-${colId}`}\r\n className=\"gp-grid-filter-cell\"\r\n style={{\r\n position: \"absolute\",\r\n left: `${columnPositions[colIndex]}px`,\r\n top: 0,\r\n width: `${column.width}px`,\r\n height: `${filterRowHeight}px`,\r\n }}\r\n >\r\n <input\r\n className=\"gp-grid-filter-input\"\r\n type=\"text\"\r\n placeholder={`Filter ${column.headerName ?? column.field}...`}\r\n value={filterValues[colId] ?? \"\"}\r\n onChange={(e) => handleFilterChange(colId, e.target.value)}\r\n onKeyDown={(e) => e.stopPropagation()}\r\n />\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n\r\n {/* Row slots */}\r\n {slotsArray.map((slot) => {\r\n if (slot.rowIndex < 0) return null;\r\n\r\n const isEvenRow = slot.rowIndex % 2 === 0;\r\n\r\n return (\r\n <div\r\n key={slot.slotId}\r\n className={`gp-grid-row ${isEvenRow ? \"gp-grid-row--even\" : \"\"}`}\r\n style={{\r\n position: \"absolute\",\r\n top: 0,\r\n left: 0,\r\n transform: `translateY(${slot.translateY}px)`,\r\n width: `${Math.max(state.contentWidth, totalWidth)}px`,\r\n height: `${rowHeight}px`,\r\n }}\r\n >\r\n {columns.map((column, colIndex) => {\r\n const isEditing = isEditingCell(slot.rowIndex, colIndex);\r\n const active = isActiveCell(slot.rowIndex, colIndex);\r\n const selected = isSelected(slot.rowIndex, colIndex);\r\n const inFillPreview = isInFillPreview(slot.rowIndex, colIndex);\r\n\r\n const cellClasses = [\r\n \"gp-grid-cell\",\r\n active && \"gp-grid-cell--active\",\r\n selected && !active && \"gp-grid-cell--selected\",\r\n isEditing && \"gp-grid-cell--editing\",\r\n inFillPreview && \"gp-grid-cell--fill-preview\",\r\n ]\r\n .filter(Boolean)\r\n .join(\" \");\r\n\r\n return (\r\n <div\r\n key={`${slot.slotId}-${colIndex}`}\r\n className={cellClasses}\r\n style={{\r\n position: \"absolute\",\r\n left: `${columnPositions[colIndex]}px`,\r\n top: 0,\r\n width: `${column.width}px`,\r\n height: `${rowHeight}px`,\r\n }}\r\n onClick={(e) => handleCellClick(slot.rowIndex, colIndex, e)}\r\n onDoubleClick={() => handleCellDoubleClick(slot.rowIndex, colIndex)}\r\n >\r\n {isEditing && state.editingCell\r\n ? renderEditCell(\r\n column,\r\n slot.rowData,\r\n slot.rowIndex,\r\n colIndex,\r\n state.editingCell.initialValue\r\n )\r\n : renderCell(column, slot.rowData, slot.rowIndex, colIndex)}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n );\r\n })}\r\n\r\n {/* Fill handle (drag to fill) */}\r\n {fillHandlePosition && !state.editingCell && (\r\n <div\r\n className=\"gp-grid-fill-handle\"\r\n style={{\r\n position: \"absolute\",\r\n top: fillHandlePosition.top,\r\n left: fillHandlePosition.left,\r\n zIndex: 200,\r\n }}\r\n onMouseDown={handleFillHandleMouseDown}\r\n />\r\n )}\r\n\r\n {/* Loading indicator */}\r\n {state.isLoading && (\r\n <div className=\"gp-grid-loading\">\r\n <div className=\"gp-grid-loading-spinner\" />\r\n Loading...\r\n </div>\r\n )}\r\n\r\n {/* Error message */}\r\n {state.error && (\r\n <div className=\"gp-grid-error\">Error: {state.error}</div>\r\n )}\r\n\r\n {/* Empty state */}\r\n {!state.isLoading && !state.error && state.totalRows === 0 && (\r\n <div className=\"gp-grid-empty\">No data to display</div>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["stateChanges: Partial<GridState>","createDataSourceFromArray","createClientDataSource","newDirection: SortDirection | null","value: unknown","rowData","params: CellRendererParams","params: EditRendererParams","params: HeaderRendererParams","row: number","col: number","minCol: number","maxCol: number"],"sources":["../src/styles.ts","../src/Grid.tsx"],"sourcesContent":["// packages/react/src/styles.ts\r\n// Dynamic CSS injection for gp-grid-react\r\n\r\nconst STYLE_ID = \"gp-grid-styles\";\r\n\r\nexport const gridStyles = `\r\n/* =============================================================================\r\n GP Grid - CSS Variables for Theming\r\n ============================================================================= */\r\n\r\n.gp-grid-container {\r\n /* Colors - Light Mode (default) */\r\n --gp-grid-bg: #ffffff;\r\n --gp-grid-bg-alt: #f8f9fa;\r\n --gp-grid-text: #212529;\r\n --gp-grid-text-secondary: #6c757d;\r\n --gp-grid-text-muted: #adb5bd;\r\n --gp-grid-border: #dee2e6;\r\n --gp-grid-border-light: #e9ecef;\r\n \r\n /* Header */\r\n --gp-grid-header-bg: #f1f3f5;\r\n --gp-grid-header-text: #212529;\r\n \r\n /* Selection */\r\n --gp-grid-primary: #228be6;\r\n --gp-grid-primary-light: #e7f5ff;\r\n --gp-grid-primary-border: #74c0fc;\r\n --gp-grid-hover: #f1f3f5;\r\n \r\n /* Filter */\r\n --gp-grid-filter-bg: #f8f9fa;\r\n --gp-grid-input-bg: #ffffff;\r\n --gp-grid-input-border: #ced4da;\r\n \r\n /* Error */\r\n --gp-grid-error-bg: #fff5f5;\r\n --gp-grid-error-text: #c92a2a;\r\n \r\n /* Loading */\r\n --gp-grid-loading-bg: rgba(255, 255, 255, 0.95);\r\n --gp-grid-loading-text: #495057;\r\n \r\n /* Scrollbar */\r\n --gp-grid-scrollbar-track: #f1f3f5;\r\n --gp-grid-scrollbar-thumb: #ced4da;\r\n --gp-grid-scrollbar-thumb-hover: #adb5bd;\r\n}\r\n\r\n/* Dark Mode */\r\n.gp-grid-container--dark {\r\n --gp-grid-bg: #1a1b1e;\r\n --gp-grid-bg-alt: #25262b;\r\n --gp-grid-text: #c1c2c5;\r\n --gp-grid-text-secondary: #909296;\r\n --gp-grid-text-muted: #5c5f66;\r\n --gp-grid-border: #373a40;\r\n --gp-grid-border-light: #2c2e33;\r\n \r\n /* Header */\r\n --gp-grid-header-bg: #25262b;\r\n --gp-grid-header-text: #c1c2c5;\r\n \r\n /* Selection */\r\n --gp-grid-primary: #339af0;\r\n --gp-grid-primary-light: #1c3d5a;\r\n --gp-grid-primary-border: #1c7ed6;\r\n --gp-grid-hover: #2c2e33;\r\n \r\n /* Filter */\r\n --gp-grid-filter-bg: #25262b;\r\n --gp-grid-input-bg: #1a1b1e;\r\n --gp-grid-input-border: #373a40;\r\n \r\n /* Error */\r\n --gp-grid-error-bg: #2c1a1a;\r\n --gp-grid-error-text: #ff6b6b;\r\n \r\n /* Loading */\r\n --gp-grid-loading-bg: rgba(26, 27, 30, 0.95);\r\n --gp-grid-loading-text: #c1c2c5;\r\n \r\n /* Scrollbar */\r\n --gp-grid-scrollbar-track: #25262b;\r\n --gp-grid-scrollbar-thumb: #373a40;\r\n --gp-grid-scrollbar-thumb-hover: #4a4d52;\r\n}\r\n\r\n/* =============================================================================\r\n GP Grid - Clean Flat Design\r\n ============================================================================= */\r\n\r\n/* Grid Container */\r\n.gp-grid-container {\r\n outline: none;\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\r\n font-size: 13px;\r\n line-height: 1.5;\r\n color: var(--gp-grid-text);\r\n background-color: var(--gp-grid-bg);\r\n border: 1px solid var(--gp-grid-border);\r\n border-radius: 6px;\r\n user-select: none;\r\n -webkit-user-select: none;\r\n}\r\n\r\n.gp-grid-container:focus {\r\n outline: none;\r\n border-color: var(--gp-grid-primary);\r\n}\r\n\r\n/* =============================================================================\r\n Header\r\n ============================================================================= */\r\n\r\n.gp-grid-header {\r\n position: sticky;\r\n top: 0;\r\n left: 0;\r\n z-index: 100;\r\n background-color: var(--gp-grid-header-bg);\r\n border-bottom: 1px solid var(--gp-grid-border);\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-cell {\r\n position: absolute;\r\n box-sizing: border-box;\r\n border-right: 1px solid var(--gp-grid-border);\r\n font-weight: 600;\r\n font-size: 12px;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n color: var(--gp-grid-header-text);\r\n cursor: pointer;\r\n user-select: none;\r\n display: flex;\r\n align-items: center;\r\n padding: 0 12px;\r\n background-color: transparent;\r\n transition: background-color 0.1s ease;\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-cell:hover {\r\n background-color: var(--gp-grid-hover);\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-cell:active {\r\n background-color: var(--gp-grid-border-light);\r\n}\r\n\r\n.gp-grid-container .gp-grid-header-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: var(--gp-grid-header-text);\r\n}\r\n\r\n.gp-grid-sort-indicator {\r\n margin-left: 6px;\r\n font-size: 10px;\r\n color: var(--gp-grid-primary);\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.gp-grid-sort-index {\r\n font-size: 9px;\r\n margin-left: 2px;\r\n color: var(--gp-grid-text-secondary);\r\n}\r\n\r\n/* =============================================================================\r\n Filter Row\r\n ============================================================================= */\r\n\r\n.gp-grid-filter-row {\r\n position: sticky;\r\n left: 0;\r\n z-index: 99;\r\n background-color: var(--gp-grid-filter-bg);\r\n border-bottom: 1px solid var(--gp-grid-border);\r\n}\r\n\r\n.gp-grid-filter-cell {\r\n position: absolute;\r\n box-sizing: border-box;\r\n border-right: 1px solid var(--gp-grid-border);\r\n padding: 6px 8px;\r\n display: flex;\r\n align-items: center;\r\n background-color: var(--gp-grid-filter-bg);\r\n}\r\n\r\n.gp-grid-filter-input {\r\n width: 100%;\r\n height: 28px;\r\n padding: 0 10px;\r\n font-family: inherit;\r\n font-size: 12px;\r\n border: 1px solid var(--gp-grid-input-border);\r\n border-radius: 4px;\r\n background-color: var(--gp-grid-input-bg);\r\n color: var(--gp-grid-text);\r\n transition: border-color 0.15s ease;\r\n}\r\n\r\n.gp-grid-filter-input:focus {\r\n outline: none;\r\n border-color: var(--gp-grid-primary);\r\n}\r\n\r\n.gp-grid-filter-input::placeholder {\r\n color: var(--gp-grid-text-muted);\r\n}\r\n\r\n/* =============================================================================\r\n Data Cells\r\n ============================================================================= */\r\n\r\n.gp-grid-row {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n}\r\n\r\n.gp-grid-cell {\r\n position: absolute;\r\n top: 0;\r\n box-sizing: border-box;\r\n padding: 0 12px;\r\n display: flex;\r\n align-items: center;\r\n cursor: cell;\r\n color: var(--gp-grid-text);\r\n border-right: 1px solid var(--gp-grid-border-light);\r\n border-bottom: 1px solid var(--gp-grid-border-light);\r\n background-color: var(--gp-grid-bg);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n user-select: none;\r\n -webkit-user-select: none;\r\n}\r\n\r\n/* Alternating row colors */\r\n.gp-grid-row--even .gp-grid-cell {\r\n background-color: var(--gp-grid-bg-alt);\r\n}\r\n\r\n.gp-grid-cell:hover {\r\n background-color: var(--gp-grid-hover) !important;\r\n}\r\n\r\n/* Active cell (focused) */\r\n.gp-grid-cell--active {\r\n background-color: var(--gp-grid-primary-light) !important;\r\n border: 2px solid var(--gp-grid-primary) !important;\r\n outline: none;\r\n z-index: 5;\r\n padding: 0 11px;\r\n}\r\n\r\n/* Selected cells (range selection) */\r\n.gp-grid-cell--selected {\r\n background-color: var(--gp-grid-primary-light) !important;\r\n}\r\n\r\n/* Editing cell */\r\n.gp-grid-cell--editing {\r\n background-color: var(--gp-grid-bg) !important;\r\n border: 2px solid var(--gp-grid-primary) !important;\r\n padding: 0 !important;\r\n z-index: 10;\r\n}\r\n\r\n/* =============================================================================\r\n Fill Handle (drag to fill)\r\n ============================================================================= */\r\n\r\n.gp-grid-fill-handle {\r\n position: absolute;\r\n width: 8px;\r\n height: 8px;\r\n background-color: var(--gp-grid-primary);\r\n border: 2px solid var(--gp-grid-bg);\r\n cursor: crosshair;\r\n z-index: 100;\r\n pointer-events: auto;\r\n box-sizing: border-box;\r\n border-radius: 1px;\r\n}\r\n\r\n.gp-grid-fill-handle:hover {\r\n transform: scale(1.2);\r\n}\r\n\r\n/* Fill preview (cells being filled) */\r\n.gp-grid-cell--fill-preview {\r\n background-color: var(--gp-grid-primary-light) !important;\r\n border: 1px dashed var(--gp-grid-primary) !important;\r\n}\r\n\r\n/* =============================================================================\r\n Edit Input\r\n ============================================================================= */\r\n\r\n.gp-grid-edit-input {\r\n width: 100%;\r\n height: 100%;\r\n padding: 0 11px;\r\n font-family: inherit;\r\n font-size: inherit;\r\n color: var(--gp-grid-text);\r\n border: none;\r\n background-color: transparent;\r\n}\r\n\r\n.gp-grid-edit-input:focus {\r\n outline: none;\r\n}\r\n\r\n/* =============================================================================\r\n Loading & Error States\r\n ============================================================================= */\r\n\r\n.gp-grid-loading {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n padding: 12px 20px;\r\n background-color: var(--gp-grid-loading-bg);\r\n color: var(--gp-grid-loading-text);\r\n border-radius: 6px;\r\n border: 1px solid var(--gp-grid-border);\r\n font-weight: 500;\r\n font-size: 13px;\r\n z-index: 1000;\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n.gp-grid-loading-spinner {\r\n width: 16px;\r\n height: 16px;\r\n border: 2px solid var(--gp-grid-border);\r\n border-top-color: var(--gp-grid-primary);\r\n border-radius: 50%;\r\n animation: gp-grid-spin 0.7s linear infinite;\r\n}\r\n\r\n@keyframes gp-grid-spin {\r\n to {\r\n transform: rotate(360deg);\r\n }\r\n}\r\n\r\n.gp-grid-error {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n padding: 12px 20px;\r\n background-color: var(--gp-grid-error-bg);\r\n color: var(--gp-grid-error-text);\r\n border-radius: 6px;\r\n border: 1px solid var(--gp-grid-error-text);\r\n font-weight: 500;\r\n font-size: 13px;\r\n z-index: 1000;\r\n max-width: 80%;\r\n text-align: center;\r\n}\r\n\r\n/* =============================================================================\r\n Empty State\r\n ============================================================================= */\r\n\r\n.gp-grid-empty {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n color: var(--gp-grid-text-muted);\r\n font-size: 14px;\r\n text-align: center;\r\n}\r\n\r\n/* =============================================================================\r\n Scrollbar Styling\r\n ============================================================================= */\r\n\r\n.gp-grid-container::-webkit-scrollbar {\r\n width: 8px;\r\n height: 8px;\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-track {\r\n background-color: var(--gp-grid-scrollbar-track);\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-thumb {\r\n background-color: var(--gp-grid-scrollbar-thumb);\r\n border-radius: 4px;\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-thumb:hover {\r\n background-color: var(--gp-grid-scrollbar-thumb-hover);\r\n}\r\n\r\n.gp-grid-container::-webkit-scrollbar-corner {\r\n background-color: var(--gp-grid-scrollbar-track);\r\n}\r\n`;\r\n\r\nlet stylesInjected = false;\r\n\r\n/**\r\n * Inject grid styles into the document head.\r\n * This is called automatically when the Grid component mounts.\r\n * Styles are only injected once, even if multiple Grid instances exist.\r\n */\r\nexport function injectStyles(): void {\r\n if (stylesInjected) return;\r\n if (typeof document === \"undefined\") return; // SSR safety\r\n\r\n // Check if styles already exist (e.g., from a previous mount)\r\n if (document.getElementById(STYLE_ID)) {\r\n stylesInjected = true;\r\n return;\r\n }\r\n\r\n const styleElement = document.createElement(\"style\");\r\n styleElement.id = STYLE_ID;\r\n styleElement.textContent = gridStyles;\r\n document.head.appendChild(styleElement);\r\n stylesInjected = true;\r\n}\r\n","// packages/react/src/Grid.tsx\r\n\r\nimport React, {\r\n useEffect,\r\n useRef,\r\n useReducer,\r\n useCallback,\r\n useMemo,\r\n useState,\r\n} from \"react\";\r\nimport {\r\n GridCore,\r\n createClientDataSource,\r\n createDataSourceFromArray,\r\n} from \"gp-grid-core\";\r\nimport type {\r\n GridInstruction,\r\n ColumnDefinition,\r\n DataSource,\r\n Row,\r\n CellRendererParams,\r\n EditRendererParams,\r\n HeaderRendererParams,\r\n CellPosition,\r\n CellRange,\r\n CellValue,\r\n SortDirection,\r\n} from \"gp-grid-core\";\r\nimport { injectStyles } from \"./styles\";\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport type ReactCellRenderer = (params: CellRendererParams) => React.ReactNode;\r\nexport type ReactEditRenderer = (params: EditRendererParams) => React.ReactNode;\r\nexport type ReactHeaderRenderer = (params: HeaderRendererParams) => React.ReactNode;\r\n\r\nexport interface GridProps<TData extends Row = Row> {\r\n columns: ColumnDefinition[];\r\n /** Data source for the grid */\r\n dataSource?: DataSource<TData>;\r\n /** Legacy: Raw row data (will be wrapped in a client data source) */\r\n rowData?: TData[];\r\n rowHeight: number;\r\n headerHeight?: number;\r\n overscan?: number;\r\n /** Show filter row below headers */\r\n showFilters?: boolean;\r\n /** Debounce time for filter input (ms) */\r\n filterDebounce?: number;\r\n /** Enable dark mode styling */\r\n darkMode?: boolean;\r\n\r\n // Renderer registries\r\n cellRenderers?: Record<string, ReactCellRenderer>;\r\n editRenderers?: Record<string, ReactEditRenderer>;\r\n headerRenderers?: Record<string, ReactHeaderRenderer>;\r\n\r\n // Global fallback renderers\r\n cellRenderer?: ReactCellRenderer;\r\n editRenderer?: ReactEditRenderer;\r\n headerRenderer?: ReactHeaderRenderer;\r\n}\r\n\r\n// =============================================================================\r\n// State Types\r\n// =============================================================================\r\n\r\ninterface SlotData {\r\n slotId: string;\r\n rowIndex: number;\r\n rowData: Row;\r\n translateY: number;\r\n}\r\n\r\ninterface GridState {\r\n slots: Map<string, SlotData>;\r\n activeCell: CellPosition | null;\r\n selectionRange: CellRange | null;\r\n editingCell: { row: number; col: number; initialValue: CellValue } | null;\r\n contentWidth: number;\r\n contentHeight: number;\r\n headers: Map<number, { column: ColumnDefinition; sortDirection?: SortDirection; sortIndex?: number }>;\r\n isLoading: boolean;\r\n error: string | null;\r\n totalRows: number;\r\n}\r\n\r\ntype GridAction =\r\n | { type: \"BATCH_INSTRUCTIONS\"; instructions: GridInstruction[] }\r\n | { type: \"RESET\" };\r\n\r\n// =============================================================================\r\n// Reducer\r\n// =============================================================================\r\n\r\n/**\r\n * Apply a single instruction to mutable slot maps and return other state changes.\r\n * This allows batching multiple slot operations efficiently.\r\n */\r\nfunction applyInstruction(\r\n instruction: GridInstruction,\r\n slots: Map<string, SlotData>,\r\n headers: Map<number, { column: ColumnDefinition; sortDirection?: SortDirection; sortIndex?: number }>\r\n): Partial<GridState> | null {\r\n switch (instruction.type) {\r\n case \"CREATE_SLOT\":\r\n slots.set(instruction.slotId, {\r\n slotId: instruction.slotId,\r\n rowIndex: -1,\r\n rowData: {},\r\n translateY: 0,\r\n });\r\n return null; // Slots map is mutated\r\n\r\n case \"DESTROY_SLOT\":\r\n slots.delete(instruction.slotId);\r\n return null;\r\n\r\n case \"ASSIGN_SLOT\": {\r\n const existing = slots.get(instruction.slotId);\r\n if (existing) {\r\n slots.set(instruction.slotId, {\r\n ...existing,\r\n rowIndex: instruction.rowIndex,\r\n rowData: instruction.rowData,\r\n });\r\n }\r\n return null;\r\n }\r\n\r\n case \"MOVE_SLOT\": {\r\n const existing = slots.get(instruction.slotId);\r\n if (existing) {\r\n slots.set(instruction.slotId, {\r\n ...existing,\r\n translateY: instruction.translateY,\r\n });\r\n }\r\n return null;\r\n }\r\n\r\n case \"SET_ACTIVE_CELL\":\r\n return { activeCell: instruction.position };\r\n\r\n case \"SET_SELECTION_RANGE\":\r\n return { selectionRange: instruction.range };\r\n\r\n case \"START_EDIT\":\r\n return {\r\n editingCell: {\r\n row: instruction.row,\r\n col: instruction.col,\r\n initialValue: instruction.initialValue,\r\n },\r\n };\r\n\r\n case \"STOP_EDIT\":\r\n return { editingCell: null };\r\n\r\n case \"SET_CONTENT_SIZE\":\r\n return {\r\n contentWidth: instruction.width,\r\n contentHeight: instruction.height,\r\n };\r\n\r\n case \"UPDATE_HEADER\":\r\n headers.set(instruction.colIndex, {\r\n column: instruction.column,\r\n sortDirection: instruction.sortDirection,\r\n sortIndex: instruction.sortIndex,\r\n });\r\n return null;\r\n\r\n case \"DATA_LOADING\":\r\n return { isLoading: true, error: null };\r\n\r\n case \"DATA_LOADED\":\r\n return { isLoading: false, totalRows: instruction.totalRows };\r\n\r\n case \"DATA_ERROR\":\r\n return { isLoading: false, error: instruction.error };\r\n\r\n default:\r\n return null;\r\n }\r\n}\r\n\r\nfunction gridReducer(state: GridState, action: GridAction): GridState {\r\n if (action.type === \"RESET\") {\r\n return createInitialState();\r\n }\r\n\r\n // Process batch of instructions in one state update\r\n const { instructions } = action;\r\n // console.log(\"[GP-Grid Reducer] Processing batch:\", instructions.map(i => i.type));\r\n if (instructions.length === 0) {\r\n return state;\r\n }\r\n\r\n // Create mutable copies of Maps to batch updates\r\n const newSlots = new Map(state.slots);\r\n const newHeaders = new Map(state.headers);\r\n let stateChanges: Partial<GridState> = {};\r\n\r\n // Apply all instructions\r\n for (const instruction of instructions) {\r\n const changes = applyInstruction(instruction, newSlots, newHeaders);\r\n if (changes) {\r\n stateChanges = { ...stateChanges, ...changes };\r\n }\r\n }\r\n\r\n // Return new state with all changes applied\r\n return {\r\n ...state,\r\n ...stateChanges,\r\n slots: newSlots,\r\n headers: newHeaders,\r\n };\r\n}\r\n\r\nfunction createInitialState(): GridState {\r\n return {\r\n slots: new Map(),\r\n activeCell: null,\r\n selectionRange: null,\r\n editingCell: null,\r\n contentWidth: 0,\r\n contentHeight: 0,\r\n headers: new Map(),\r\n isLoading: false,\r\n error: null,\r\n totalRows: 0,\r\n };\r\n}\r\n\r\n// =============================================================================\r\n// Grid Component\r\n// =============================================================================\r\n\r\nexport function Grid<TData extends Row = Row>(props: GridProps<TData>) {\r\n // Inject styles on first render (safe to call multiple times)\r\n injectStyles();\r\n\r\n const {\r\n columns,\r\n dataSource: providedDataSource,\r\n rowData,\r\n rowHeight,\r\n headerHeight = rowHeight,\r\n overscan = 3,\r\n showFilters = false,\r\n filterDebounce = 300,\r\n darkMode = false,\r\n cellRenderers = {},\r\n editRenderers = {},\r\n headerRenderers = {},\r\n cellRenderer,\r\n editRenderer,\r\n headerRenderer,\r\n } = props;\r\n\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const coreRef = useRef<GridCore<TData> | null>(null);\r\n const [state, dispatch] = useReducer(gridReducer, null, createInitialState);\r\n const [filterValues, setFilterValues] = useState<Record<string, string>>({});\r\n const filterTimeoutRef = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\r\n const [isDraggingFill, setIsDraggingFill] = useState(false);\r\n const [fillTarget, setFillTarget] = useState<{ row: number; col: number } | null>(null);\r\n const [fillSourceRange, setFillSourceRange] = useState<{ startRow: number; startCol: number; endRow: number; endCol: number } | null>(null);\r\n const autoScrollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\r\n const [isDraggingSelection, setIsDraggingSelection] = useState(false);\r\n\r\n // Computed heights\r\n const filterRowHeight = showFilters ? 40 : 0;\r\n const totalHeaderHeight = headerHeight + filterRowHeight;\r\n\r\n // Create data source from rowData if not provided\r\n const dataSource = useMemo(() => {\r\n if (providedDataSource) {\r\n return providedDataSource;\r\n }\r\n if (rowData) {\r\n return createDataSourceFromArray(rowData);\r\n }\r\n // Empty data source\r\n return createClientDataSource<TData>([]);\r\n }, [providedDataSource, rowData]);\r\n\r\n // Compute column positions\r\n const columnPositions = useMemo(() => {\r\n const positions = [0];\r\n let pos = 0;\r\n for (const col of columns) {\r\n pos += col.width;\r\n positions.push(pos);\r\n }\r\n return positions;\r\n }, [columns]);\r\n\r\n const totalWidth = columnPositions[columnPositions.length - 1] ?? 0;\r\n\r\n // Initialize GridCore\r\n useEffect(() => {\r\n const core = new GridCore<TData>({\r\n columns,\r\n dataSource,\r\n rowHeight,\r\n headerHeight: totalHeaderHeight,\r\n overscan,\r\n });\r\n\r\n coreRef.current = core;\r\n\r\n // Subscribe to batched instructions for efficient state updates\r\n const unsubscribe = core.onBatchInstruction((instructions) => {\r\n dispatch({ type: \"BATCH_INSTRUCTIONS\", instructions });\r\n });\r\n\r\n // Initialize\r\n core.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n coreRef.current = null;\r\n };\r\n }, [columns, dataSource, rowHeight, totalHeaderHeight, overscan]);\r\n\r\n // Handle scroll\r\n const handleScroll = useCallback(() => {\r\n const container = containerRef.current;\r\n const core = coreRef.current;\r\n if (!container || !core) return;\r\n\r\n core.setViewport(\r\n container.scrollTop,\r\n container.scrollLeft,\r\n container.clientWidth,\r\n container.clientHeight\r\n );\r\n }, []);\r\n\r\n // Initial measurement\r\n useEffect(() => {\r\n const container = containerRef.current;\r\n const core = coreRef.current;\r\n if (!container || !core) return;\r\n\r\n const resizeObserver = new ResizeObserver(() => {\r\n core.setViewport(\r\n container.scrollTop,\r\n container.scrollLeft,\r\n container.clientWidth,\r\n container.clientHeight\r\n );\r\n });\r\n\r\n resizeObserver.observe(container);\r\n handleScroll();\r\n\r\n return () => resizeObserver.disconnect();\r\n }, [handleScroll]);\r\n\r\n // Handle filter change with debounce\r\n const handleFilterChange = useCallback(\r\n (colId: string, value: string) => {\r\n setFilterValues((prev) => ({ ...prev, [colId]: value }));\r\n\r\n // Clear existing timeout\r\n if (filterTimeoutRef.current[colId]) {\r\n clearTimeout(filterTimeoutRef.current[colId]);\r\n }\r\n\r\n // Debounce the actual filter application\r\n filterTimeoutRef.current[colId] = setTimeout(() => {\r\n const core = coreRef.current;\r\n if (core) {\r\n core.setFilter(colId, value);\r\n }\r\n }, filterDebounce);\r\n },\r\n [filterDebounce]\r\n );\r\n\r\n // Keyboard navigation\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent) => {\r\n const core = coreRef.current;\r\n if (!core) return;\r\n\r\n // Don't handle keyboard events when editing\r\n if (state.editingCell && e.key !== \"Enter\" && e.key !== \"Escape\" && e.key !== \"Tab\") {\r\n return;\r\n }\r\n\r\n const { selection } = core;\r\n const isShift = e.shiftKey;\r\n const isCtrl = e.ctrlKey || e.metaKey;\r\n\r\n switch (e.key) {\r\n case \"ArrowUp\":\r\n e.preventDefault();\r\n selection.moveFocus(\"up\", isShift);\r\n break;\r\n case \"ArrowDown\":\r\n e.preventDefault();\r\n selection.moveFocus(\"down\", isShift);\r\n break;\r\n case \"ArrowLeft\":\r\n e.preventDefault();\r\n selection.moveFocus(\"left\", isShift);\r\n break;\r\n case \"ArrowRight\":\r\n e.preventDefault();\r\n selection.moveFocus(\"right\", isShift);\r\n break;\r\n case \"Enter\":\r\n e.preventDefault();\r\n if (state.editingCell) {\r\n core.commitEdit();\r\n } else if (state.activeCell) {\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n case \"Escape\":\r\n e.preventDefault();\r\n if (state.editingCell) {\r\n core.cancelEdit();\r\n } else {\r\n selection.clearSelection();\r\n }\r\n break;\r\n case \"Tab\":\r\n e.preventDefault();\r\n if (state.editingCell) {\r\n core.commitEdit();\r\n }\r\n selection.moveFocus(isShift ? \"left\" : \"right\", false);\r\n break;\r\n case \"a\":\r\n if (isCtrl) {\r\n e.preventDefault();\r\n selection.selectAll();\r\n }\r\n break;\r\n case \"c\":\r\n if (isCtrl) {\r\n e.preventDefault();\r\n selection.copySelectionToClipboard();\r\n }\r\n break;\r\n case \"F2\":\r\n e.preventDefault();\r\n if (state.activeCell && !state.editingCell) {\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n case \"Delete\":\r\n case \"Backspace\":\r\n // Start editing with empty value on delete/backspace\r\n if (state.activeCell && !state.editingCell) {\r\n e.preventDefault();\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n default:\r\n // Start editing on any printable character\r\n if (\r\n state.activeCell &&\r\n !state.editingCell &&\r\n !isCtrl &&\r\n e.key.length === 1\r\n ) {\r\n core.startEdit(state.activeCell.row, state.activeCell.col);\r\n }\r\n break;\r\n }\r\n },\r\n [state.activeCell, state.editingCell]\r\n );\r\n\r\n // Scroll active cell into view when navigating with keyboard\r\n useEffect(() => {\r\n // Skip scrolling when editing - the user just clicked on the cell so it's already visible\r\n if (!state.activeCell || !containerRef.current || state.editingCell) return;\r\n\r\n const { row, col } = state.activeCell;\r\n const container = containerRef.current;\r\n\r\n // Calculate cell position\r\n const cellTop = row * rowHeight + totalHeaderHeight;\r\n const cellBottom = cellTop + rowHeight;\r\n const cellLeft = columnPositions[col] ?? 0;\r\n const cellRight = cellLeft + (columns[col]?.width ?? 0);\r\n\r\n // Get visible area\r\n const visibleTop = container.scrollTop + totalHeaderHeight;\r\n const visibleBottom = container.scrollTop + container.clientHeight;\r\n const visibleLeft = container.scrollLeft;\r\n const visibleRight = container.scrollLeft + container.clientWidth;\r\n\r\n // Scroll vertically if needed\r\n if (cellTop < visibleTop) {\r\n container.scrollTop = cellTop - totalHeaderHeight;\r\n } else if (cellBottom > visibleBottom) {\r\n container.scrollTop = cellBottom - container.clientHeight;\r\n }\r\n\r\n // Scroll horizontally if needed\r\n if (cellLeft < visibleLeft) {\r\n container.scrollLeft = cellLeft;\r\n } else if (cellRight > visibleRight) {\r\n container.scrollLeft = cellRight - container.clientWidth;\r\n }\r\n }, [state.activeCell, state.editingCell, rowHeight, totalHeaderHeight, columnPositions, columns]);\r\n\r\n // Cell mouse down handler (starts selection and drag)\r\n const handleCellMouseDown = useCallback(\r\n (rowIndex: number, colIndex: number, e: React.MouseEvent) => {\r\n // console.log(\"[GP-Grid] Cell mousedown:\", { rowIndex, colIndex, coreExists: !!coreRef.current });\r\n const core = coreRef.current;\r\n if (!core || core.getEditState() !== null) {\r\n // console.warn(\"[GP-Grid] Core not initialized on cell mousedown\");\r\n return;\r\n }\r\n\r\n // Only handle left mouse button\r\n if (e.button !== 0) return;\r\n\r\n // Focus the container to enable keyboard navigation\r\n containerRef.current?.focus();\r\n\r\n core.selection.startSelection(\r\n { row: rowIndex, col: colIndex },\r\n { shift: e.shiftKey, ctrl: e.ctrlKey || e.metaKey }\r\n );\r\n\r\n // Start drag selection (unless shift is held - that's a one-time extend)\r\n if (!e.shiftKey) {\r\n setIsDraggingSelection(true);\r\n }\r\n },\r\n []\r\n );\r\n\r\n // Cell double-click handler\r\n const handleCellDoubleClick = useCallback(\r\n (rowIndex: number, colIndex: number) => {\r\n const core = coreRef.current;\r\n if (!core) return;\r\n\r\n core.startEdit(rowIndex, colIndex);\r\n },\r\n []\r\n );\r\n\r\n // Header click handler (sort)\r\n const handleHeaderClick = useCallback(\r\n (colIndex: number, e: React.MouseEvent) => {\r\n // console.log(\"[GP-Grid] Header click:\", { colIndex, coreExists: !!coreRef.current });\r\n const core = coreRef.current;\r\n if (!core) {\r\n // console.warn(\"[GP-Grid] Core not initialized on header click\");\r\n return;\r\n }\r\n\r\n const column = columns[colIndex];\r\n if (!column) {\r\n // console.warn(\"[GP-Grid] Column not found for index:\", colIndex);\r\n return;\r\n }\r\n\r\n const colId = column.colId ?? column.field;\r\n const headerInfo = state.headers.get(colIndex);\r\n const currentDirection = headerInfo?.sortDirection;\r\n\r\n // Cycle: none -> asc -> desc -> none\r\n let newDirection: SortDirection | null;\r\n if (!currentDirection) {\r\n newDirection = \"asc\";\r\n } else if (currentDirection === \"asc\") {\r\n newDirection = \"desc\";\r\n } else {\r\n newDirection = null;\r\n }\r\n\r\n // console.log(\"[GP-Grid] Setting sort:\", { colId, newDirection });\r\n core.setSort(colId, newDirection, e.shiftKey);\r\n },\r\n [columns, state.headers]\r\n );\r\n\r\n // Fill handle drag handlers\r\n const handleFillHandleMouseDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n // console.log(\"[GP-Grid] Fill handle mousedown triggered\");\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const core = coreRef.current;\r\n if (!core) return;\r\n\r\n const { activeCell, selectionRange } = state;\r\n if (!activeCell && !selectionRange) return;\r\n\r\n // Create source range from selection or active cell\r\n const sourceRange = selectionRange ?? {\r\n startRow: activeCell!.row,\r\n startCol: activeCell!.col,\r\n endRow: activeCell!.row,\r\n endCol: activeCell!.col,\r\n };\r\n\r\n // console.log(\"[GP-Grid] Starting fill drag with source range:\", sourceRange);\r\n core.fill.startFillDrag(sourceRange);\r\n setFillSourceRange(sourceRange);\r\n setFillTarget({ \r\n row: Math.max(sourceRange.startRow, sourceRange.endRow),\r\n col: Math.max(sourceRange.startCol, sourceRange.endCol)\r\n });\r\n setIsDraggingFill(true);\r\n },\r\n [state.activeCell, state.selectionRange]\r\n );\r\n\r\n // Handle mouse move during fill drag\r\n useEffect(() => {\r\n if (!isDraggingFill) return;\r\n\r\n // Auto-scroll configuration\r\n const SCROLL_THRESHOLD = 40; // pixels from edge to trigger scroll\r\n const SCROLL_SPEED = 10; // pixels per frame\r\n\r\n const handleMouseMove = (e: MouseEvent) => {\r\n const core = coreRef.current;\r\n const container = containerRef.current;\r\n if (!core || !container) return;\r\n\r\n // Get container bounds\r\n const rect = container.getBoundingClientRect();\r\n const scrollLeft = container.scrollLeft;\r\n const scrollTop = container.scrollTop;\r\n\r\n // Calculate mouse position relative to grid content\r\n const mouseX = e.clientX - rect.left + scrollLeft;\r\n const mouseY = e.clientY - rect.top + scrollTop - totalHeaderHeight;\r\n\r\n // Find the row and column under the mouse\r\n const targetRow = Math.max(0, Math.floor(mouseY / rowHeight));\r\n \r\n // Find column by checking column positions\r\n let targetCol = 0;\r\n for (let i = 0; i < columnPositions.length - 1; i++) {\r\n if (mouseX >= columnPositions[i]! && mouseX < columnPositions[i + 1]!) {\r\n targetCol = i;\r\n break;\r\n }\r\n if (mouseX >= columnPositions[columnPositions.length - 1]!) {\r\n targetCol = columnPositions.length - 2;\r\n }\r\n }\r\n\r\n core.fill.updateFillDrag(targetRow, targetCol);\r\n setFillTarget({ row: targetRow, col: targetCol });\r\n\r\n // Auto-scroll logic\r\n const mouseYInContainer = e.clientY - rect.top;\r\n const mouseXInContainer = e.clientX - rect.left;\r\n\r\n // Clear any existing auto-scroll\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n\r\n // Check if we need to auto-scroll\r\n let scrollDeltaX = 0;\r\n let scrollDeltaY = 0;\r\n\r\n // Vertical scrolling\r\n if (mouseYInContainer < SCROLL_THRESHOLD + totalHeaderHeight) {\r\n scrollDeltaY = -SCROLL_SPEED;\r\n } else if (mouseYInContainer > rect.height - SCROLL_THRESHOLD) {\r\n scrollDeltaY = SCROLL_SPEED;\r\n }\r\n\r\n // Horizontal scrolling\r\n if (mouseXInContainer < SCROLL_THRESHOLD) {\r\n scrollDeltaX = -SCROLL_SPEED;\r\n } else if (mouseXInContainer > rect.width - SCROLL_THRESHOLD) {\r\n scrollDeltaX = SCROLL_SPEED;\r\n }\r\n\r\n // Start auto-scroll if needed\r\n if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {\r\n autoScrollIntervalRef.current = setInterval(() => {\r\n if (containerRef.current) {\r\n containerRef.current.scrollTop += scrollDeltaY;\r\n containerRef.current.scrollLeft += scrollDeltaX;\r\n }\r\n }, 16); // ~60fps\r\n }\r\n };\r\n\r\n const handleMouseUp = () => {\r\n // Clear auto-scroll\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n\r\n const core = coreRef.current;\r\n if (core) {\r\n core.fill.commitFillDrag();\r\n // Refresh slots to show updated values\r\n core.refreshSlotData();\r\n }\r\n setIsDraggingFill(false);\r\n setFillTarget(null);\r\n setFillSourceRange(null);\r\n };\r\n\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleMouseUp);\r\n\r\n return () => {\r\n // Clear auto-scroll on cleanup\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleMouseUp);\r\n };\r\n }, [isDraggingFill, totalHeaderHeight, rowHeight, columnPositions]);\r\n\r\n // Handle mouse move/up during selection drag\r\n useEffect(() => {\r\n if (!isDraggingSelection) return;\r\n\r\n // Auto-scroll configuration\r\n const SCROLL_THRESHOLD = 40;\r\n const SCROLL_SPEED = 10;\r\n\r\n const handleMouseMove = (e: MouseEvent) => {\r\n const core = coreRef.current;\r\n const container = containerRef.current;\r\n if (!core || !container) return;\r\n\r\n // Get container bounds\r\n const rect = container.getBoundingClientRect();\r\n const scrollLeft = container.scrollLeft;\r\n const scrollTop = container.scrollTop;\r\n\r\n // Calculate mouse position relative to grid content\r\n const mouseX = e.clientX - rect.left + scrollLeft;\r\n const mouseY = e.clientY - rect.top + scrollTop - totalHeaderHeight;\r\n\r\n // Find the row and column under the mouse\r\n const targetRow = Math.max(0, Math.min(Math.floor(mouseY / rowHeight), core.getRowCount() - 1));\r\n \r\n // Find column by checking column positions\r\n let targetCol = 0;\r\n for (let i = 0; i < columnPositions.length - 1; i++) {\r\n if (mouseX >= columnPositions[i]! && mouseX < columnPositions[i + 1]!) {\r\n targetCol = i;\r\n break;\r\n }\r\n if (mouseX >= columnPositions[columnPositions.length - 1]!) {\r\n targetCol = columnPositions.length - 2;\r\n }\r\n }\r\n targetCol = Math.max(0, Math.min(targetCol, columns.length - 1));\r\n\r\n // Extend selection to target cell (like shift+click)\r\n core.selection.startSelection(\r\n { row: targetRow, col: targetCol },\r\n { shift: true }\r\n );\r\n\r\n // Auto-scroll logic\r\n const mouseYInContainer = e.clientY - rect.top;\r\n const mouseXInContainer = e.clientX - rect.left;\r\n\r\n // Clear any existing auto-scroll\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n\r\n // Check if we need to auto-scroll\r\n let scrollDeltaX = 0;\r\n let scrollDeltaY = 0;\r\n\r\n // Vertical scrolling\r\n if (mouseYInContainer < SCROLL_THRESHOLD + totalHeaderHeight) {\r\n scrollDeltaY = -SCROLL_SPEED;\r\n } else if (mouseYInContainer > rect.height - SCROLL_THRESHOLD) {\r\n scrollDeltaY = SCROLL_SPEED;\r\n }\r\n\r\n // Horizontal scrolling\r\n if (mouseXInContainer < SCROLL_THRESHOLD) {\r\n scrollDeltaX = -SCROLL_SPEED;\r\n } else if (mouseXInContainer > rect.width - SCROLL_THRESHOLD) {\r\n scrollDeltaX = SCROLL_SPEED;\r\n }\r\n\r\n // Start auto-scroll if needed\r\n if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {\r\n autoScrollIntervalRef.current = setInterval(() => {\r\n if (containerRef.current) {\r\n containerRef.current.scrollTop += scrollDeltaY;\r\n containerRef.current.scrollLeft += scrollDeltaX;\r\n }\r\n }, 16); // ~60fps\r\n }\r\n };\r\n\r\n const handleMouseUp = () => {\r\n // Clear auto-scroll\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n setIsDraggingSelection(false);\r\n };\r\n\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleMouseUp);\r\n\r\n return () => {\r\n if (autoScrollIntervalRef.current) {\r\n clearInterval(autoScrollIntervalRef.current);\r\n autoScrollIntervalRef.current = null;\r\n }\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleMouseUp);\r\n };\r\n }, [isDraggingSelection, totalHeaderHeight, rowHeight, columnPositions, columns.length]);\r\n\r\n // Render helpers\r\n const isSelected = useCallback(\r\n (row: number, col: number): boolean => {\r\n const { selectionRange } = state;\r\n if (!selectionRange) return false;\r\n\r\n const minRow = Math.min(selectionRange.startRow, selectionRange.endRow);\r\n const maxRow = Math.max(selectionRange.startRow, selectionRange.endRow);\r\n const minCol = Math.min(selectionRange.startCol, selectionRange.endCol);\r\n const maxCol = Math.max(selectionRange.startCol, selectionRange.endCol);\r\n\r\n return row >= minRow && row <= maxRow && col >= minCol && col <= maxCol;\r\n },\r\n [state.selectionRange]\r\n );\r\n\r\n const isActiveCell = useCallback(\r\n (row: number, col: number): boolean => {\r\n return state.activeCell?.row === row && state.activeCell?.col === col;\r\n },\r\n [state.activeCell]\r\n );\r\n\r\n const isEditingCell = useCallback(\r\n (row: number, col: number): boolean => {\r\n return state.editingCell?.row === row && state.editingCell?.col === col;\r\n },\r\n [state.editingCell]\r\n );\r\n\r\n // Check if cell is in fill preview range\r\n const isInFillPreview = useCallback(\r\n (row: number, col: number): boolean => {\r\n if (!isDraggingFill || !fillSourceRange || !fillTarget) return false;\r\n\r\n const srcMinRow = Math.min(fillSourceRange.startRow, fillSourceRange.endRow);\r\n const srcMaxRow = Math.max(fillSourceRange.startRow, fillSourceRange.endRow);\r\n const srcMinCol = Math.min(fillSourceRange.startCol, fillSourceRange.endCol);\r\n const srcMaxCol = Math.max(fillSourceRange.startCol, fillSourceRange.endCol);\r\n\r\n // Determine fill direction and range\r\n const fillDown = fillTarget.row > srcMaxRow;\r\n const fillUp = fillTarget.row < srcMinRow;\r\n const fillRight = fillTarget.col > srcMaxCol;\r\n const fillLeft = fillTarget.col < srcMinCol;\r\n\r\n // Check if cell is in the fill preview area (not the source area)\r\n if (fillDown) {\r\n return row > srcMaxRow && row <= fillTarget.row && col >= srcMinCol && col <= srcMaxCol;\r\n }\r\n if (fillUp) {\r\n return row < srcMinRow && row >= fillTarget.row && col >= srcMinCol && col <= srcMaxCol;\r\n }\r\n if (fillRight) {\r\n return col > srcMaxCol && col <= fillTarget.col && row >= srcMinRow && row <= srcMaxRow;\r\n }\r\n if (fillLeft) {\r\n return col < srcMinCol && col >= fillTarget.col && row >= srcMinRow && row <= srcMaxRow;\r\n }\r\n\r\n return false;\r\n },\r\n [isDraggingFill, fillSourceRange, fillTarget]\r\n );\r\n\r\n // Get cell value from row data\r\n const getCellValue = useCallback((rowData: Row, field: string): CellValue => {\r\n const parts = field.split(\".\");\r\n let value: unknown = rowData;\r\n\r\n for (const part of parts) {\r\n if (value == null || typeof value !== \"object\") {\r\n return null;\r\n }\r\n value = (value as Record<string, unknown>)[part];\r\n }\r\n\r\n return (value ?? null) as CellValue;\r\n }, []);\r\n\r\n // Render cell content\r\n const renderCell = useCallback(\r\n (\r\n column: ColumnDefinition,\r\n rowData: Row,\r\n rowIndex: number,\r\n colIndex: number\r\n ): React.ReactNode => {\r\n const value = getCellValue(rowData, column.field);\r\n const params: CellRendererParams = {\r\n value,\r\n rowData,\r\n column,\r\n rowIndex,\r\n colIndex,\r\n isActive: isActiveCell(rowIndex, colIndex),\r\n isSelected: isSelected(rowIndex, colIndex),\r\n isEditing: isEditingCell(rowIndex, colIndex),\r\n };\r\n\r\n // Check for column-specific renderer\r\n if (column.cellRenderer && typeof column.cellRenderer === \"string\") {\r\n const renderer = cellRenderers[column.cellRenderer];\r\n if (renderer) {\r\n return renderer(params);\r\n }\r\n }\r\n\r\n // Fall back to global renderer\r\n if (cellRenderer) {\r\n return cellRenderer(params);\r\n }\r\n\r\n // Default text rendering\r\n return value == null ? \"\" : String(value);\r\n },\r\n [getCellValue, isActiveCell, isSelected, isEditingCell, cellRenderers, cellRenderer]\r\n );\r\n\r\n // Render edit cell\r\n const renderEditCell = useCallback(\r\n (\r\n column: ColumnDefinition,\r\n rowData: Row,\r\n rowIndex: number,\r\n colIndex: number,\r\n initialValue: CellValue\r\n ): React.ReactNode => {\r\n const core = coreRef.current;\r\n if (!core) return null;\r\n\r\n const value = getCellValue(rowData, column.field);\r\n const params: EditRendererParams = {\r\n value,\r\n rowData,\r\n column,\r\n rowIndex,\r\n colIndex,\r\n isActive: true,\r\n isSelected: true,\r\n isEditing: true,\r\n initialValue,\r\n onValueChange: (newValue) => core.updateEditValue(newValue),\r\n onCommit: () => core.commitEdit(),\r\n onCancel: () => core.cancelEdit(),\r\n };\r\n\r\n // Check for column-specific renderer\r\n if (column.editRenderer && typeof column.editRenderer === \"string\") {\r\n const renderer = editRenderers[column.editRenderer];\r\n if (renderer) {\r\n return renderer(params);\r\n }\r\n }\r\n\r\n // Fall back to global renderer\r\n if (editRenderer) {\r\n return editRenderer(params);\r\n }\r\n\r\n // Default input\r\n return (\r\n <input\r\n className=\"gp-grid-edit-input\"\r\n type=\"text\"\r\n defaultValue={initialValue == null ? \"\" : String(initialValue)}\r\n autoFocus\r\n onFocus={(e) => e.target.select()}\r\n onChange={(e) => core.updateEditValue(e.target.value)}\r\n onKeyDown={(e) => {\r\n e.stopPropagation();\r\n if (e.key === \"Enter\") {\r\n core.commitEdit();\r\n } else if (e.key === \"Escape\") {\r\n core.cancelEdit();\r\n } else if (e.key === \"Tab\") {\r\n e.preventDefault();\r\n core.commitEdit();\r\n core.selection.moveFocus(e.shiftKey ? \"left\" : \"right\", false);\r\n }\r\n }}\r\n onBlur={() => core.commitEdit()}\r\n />\r\n );\r\n },\r\n [getCellValue, editRenderers, editRenderer]\r\n );\r\n\r\n // Render header\r\n const renderHeader = useCallback(\r\n (\r\n column: ColumnDefinition,\r\n colIndex: number,\r\n sortDirection?: SortDirection,\r\n sortIndex?: number\r\n ): React.ReactNode => {\r\n const core = coreRef.current;\r\n const params: HeaderRendererParams = {\r\n column,\r\n colIndex,\r\n sortDirection,\r\n sortIndex,\r\n onSort: (direction, addToExisting) => {\r\n if (core) {\r\n core.setSort(column.colId ?? column.field, direction, addToExisting);\r\n }\r\n },\r\n };\r\n\r\n // Check for column-specific renderer\r\n if (column.headerRenderer && typeof column.headerRenderer === \"string\") {\r\n const renderer = headerRenderers[column.headerRenderer];\r\n if (renderer) {\r\n return renderer(params);\r\n }\r\n }\r\n\r\n // Fall back to global renderer\r\n if (headerRenderer) {\r\n return headerRenderer(params);\r\n }\r\n\r\n // Default header\r\n return (\r\n <>\r\n <span className=\"gp-grid-header-text\">\r\n {column.headerName ?? column.field}\r\n </span>\r\n {sortDirection && (\r\n <span className=\"gp-grid-sort-indicator\">\r\n {sortDirection === \"asc\" ? \"▲\" : \"▼\"}\r\n {sortIndex !== undefined && sortIndex > 0 && (\r\n <span className=\"gp-grid-sort-index\">{sortIndex}</span>\r\n )}\r\n </span>\r\n )}\r\n </>\r\n );\r\n },\r\n [headerRenderers, headerRenderer]\r\n );\r\n\r\n // Convert slots map to array for rendering\r\n const slotsArray = useMemo(() => Array.from(state.slots.values()), [state.slots]);\r\n\r\n // Calculate fill handle position (only show for editable columns)\r\n const fillHandlePosition = useMemo(() => {\r\n const { activeCell, selectionRange } = state;\r\n if (!activeCell && !selectionRange) return null;\r\n\r\n // Get the bottom-right corner and column range of selection or active cell\r\n let row: number, col: number;\r\n let minCol: number, maxCol: number;\r\n \r\n if (selectionRange) {\r\n row = Math.max(selectionRange.startRow, selectionRange.endRow);\r\n col = Math.max(selectionRange.startCol, selectionRange.endCol);\r\n minCol = Math.min(selectionRange.startCol, selectionRange.endCol);\r\n maxCol = Math.max(selectionRange.startCol, selectionRange.endCol);\r\n } else if (activeCell) {\r\n row = activeCell.row;\r\n col = activeCell.col;\r\n minCol = col;\r\n maxCol = col;\r\n } else {\r\n return null;\r\n }\r\n\r\n // Check if ALL columns in the selection are editable\r\n for (let c = minCol; c <= maxCol; c++) {\r\n const column = columns[c];\r\n if (!column || column.editable !== true) {\r\n return null; // Don't show fill handle if any column is not editable\r\n }\r\n }\r\n\r\n const cellTop = row * rowHeight + totalHeaderHeight;\r\n const cellLeft = columnPositions[col] ?? 0;\r\n const cellWidth = columns[col]?.width ?? 0;\r\n\r\n return {\r\n top: cellTop + rowHeight - 5,\r\n left: cellLeft + cellWidth - 20, // Move significantly left to avoid scrollbar overlap\r\n };\r\n }, [state.activeCell, state.selectionRange, rowHeight, totalHeaderHeight, columnPositions, columns]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={`gp-grid-container${darkMode ? \" gp-grid-container--dark\" : \"\"}`}\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n overflow: \"auto\",\r\n position: \"relative\",\r\n }}\r\n onScroll={handleScroll}\r\n onKeyDown={handleKeyDown}\r\n tabIndex={0}\r\n >\r\n {/* Content sizer */}\r\n <div\r\n style={{\r\n width: Math.max(state.contentWidth, totalWidth),\r\n height: Math.max(state.contentHeight, totalHeaderHeight),\r\n position: \"relative\",\r\n minWidth: \"100%\",\r\n }}\r\n >\r\n {/* Headers */}\r\n <div\r\n className=\"gp-grid-header\"\r\n style={{\r\n position: \"sticky\",\r\n top: 0,\r\n left: 0,\r\n height: headerHeight,\r\n width: Math.max(state.contentWidth, totalWidth),\r\n minWidth: \"100%\",\r\n zIndex: 100,\r\n }}\r\n >\r\n {columns.map((column, colIndex) => {\r\n const headerInfo = state.headers.get(colIndex);\r\n return (\r\n <div\r\n key={column.colId ?? column.field}\r\n className=\"gp-grid-header-cell\"\r\n style={{\r\n position: \"absolute\",\r\n left: `${columnPositions[colIndex]}px`,\r\n top: 0,\r\n width: `${column.width}px`,\r\n height: `${headerHeight}px`,\r\n background: \"transparent\",\r\n }}\r\n onClick={(e) => handleHeaderClick(colIndex, e)}\r\n >\r\n {renderHeader(\r\n column,\r\n colIndex,\r\n headerInfo?.sortDirection,\r\n headerInfo?.sortIndex\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n\r\n {/* Filter Row */}\r\n {showFilters && (\r\n <div\r\n className=\"gp-grid-filter-row\"\r\n style={{\r\n position: \"sticky\",\r\n top: headerHeight,\r\n left: 0,\r\n height: filterRowHeight,\r\n width: Math.max(state.contentWidth, totalWidth),\r\n minWidth: \"100%\",\r\n zIndex: 99,\r\n }}\r\n >\r\n {columns.map((column, colIndex) => {\r\n const colId = column.colId ?? column.field;\r\n return (\r\n <div\r\n key={`filter-${colId}`}\r\n className=\"gp-grid-filter-cell\"\r\n style={{\r\n position: \"absolute\",\r\n left: `${columnPositions[colIndex]}px`,\r\n top: 0,\r\n width: `${column.width}px`,\r\n height: `${filterRowHeight}px`,\r\n }}\r\n >\r\n <input\r\n className=\"gp-grid-filter-input\"\r\n type=\"text\"\r\n placeholder={`Filter ${column.headerName ?? column.field}...`}\r\n value={filterValues[colId] ?? \"\"}\r\n onChange={(e) => handleFilterChange(colId, e.target.value)}\r\n onKeyDown={(e) => e.stopPropagation()}\r\n />\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n\r\n {/* Row slots */}\r\n {slotsArray.map((slot) => {\r\n if (slot.rowIndex < 0) return null;\r\n\r\n const isEvenRow = slot.rowIndex % 2 === 0;\r\n\r\n return (\r\n <div\r\n key={slot.slotId}\r\n className={`gp-grid-row ${isEvenRow ? \"gp-grid-row--even\" : \"\"}`}\r\n style={{\r\n position: \"absolute\",\r\n top: 0,\r\n left: 0,\r\n transform: `translateY(${slot.translateY}px)`,\r\n width: `${Math.max(state.contentWidth, totalWidth)}px`,\r\n height: `${rowHeight}px`,\r\n }}\r\n >\r\n {columns.map((column, colIndex) => {\r\n const isEditing = isEditingCell(slot.rowIndex, colIndex);\r\n const active = isActiveCell(slot.rowIndex, colIndex);\r\n const selected = isSelected(slot.rowIndex, colIndex);\r\n const inFillPreview = isInFillPreview(slot.rowIndex, colIndex);\r\n\r\n const cellClasses = [\r\n \"gp-grid-cell\",\r\n active && \"gp-grid-cell--active\",\r\n selected && !active && \"gp-grid-cell--selected\",\r\n isEditing && \"gp-grid-cell--editing\",\r\n inFillPreview && \"gp-grid-cell--fill-preview\",\r\n ]\r\n .filter(Boolean)\r\n .join(\" \");\r\n\r\n return (\r\n <div\r\n key={`${slot.slotId}-${colIndex}`}\r\n className={cellClasses}\r\n style={{\r\n position: \"absolute\",\r\n left: `${columnPositions[colIndex]}px`,\r\n top: 0,\r\n width: `${column.width}px`,\r\n height: `${rowHeight}px`,\r\n }}\r\n onMouseDown={(e) => handleCellMouseDown(slot.rowIndex, colIndex, e)}\r\n onDoubleClick={() => handleCellDoubleClick(slot.rowIndex, colIndex)}\r\n >\r\n {isEditing && state.editingCell\r\n ? renderEditCell(\r\n column,\r\n slot.rowData,\r\n slot.rowIndex,\r\n colIndex,\r\n state.editingCell.initialValue\r\n )\r\n : renderCell(column, slot.rowData, slot.rowIndex, colIndex)}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n );\r\n })}\r\n\r\n {/* Fill handle (drag to fill) */}\r\n {fillHandlePosition && !state.editingCell && (\r\n <div\r\n className=\"gp-grid-fill-handle\"\r\n style={{\r\n position: \"absolute\",\r\n top: fillHandlePosition.top,\r\n left: fillHandlePosition.left,\r\n zIndex: 200,\r\n }}\r\n onMouseDown={handleFillHandleMouseDown}\r\n />\r\n )}\r\n\r\n {/* Loading indicator */}\r\n {state.isLoading && (\r\n <div className=\"gp-grid-loading\">\r\n <div className=\"gp-grid-loading-spinner\" />\r\n Loading...\r\n </div>\r\n )}\r\n\r\n {/* Error message */}\r\n {state.error && (\r\n <div className=\"gp-grid-error\">Error: {state.error}</div>\r\n )}\r\n\r\n {/* Empty state */}\r\n {!state.isLoading && !state.error && state.totalRows === 0 && (\r\n <div className=\"gp-grid-empty\">No data to display</div>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gp-grid-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"link-workspace-packages": false
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"gp-grid-core": "0.1.
|
|
11
|
+
"gp-grid-core": "0.1.1"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
14
|
"react": "^19.0.0",
|
package/src/Grid.tsx
CHANGED
|
@@ -271,6 +271,7 @@ export function Grid<TData extends Row = Row>(props: GridProps<TData>) {
|
|
|
271
271
|
const [fillTarget, setFillTarget] = useState<{ row: number; col: number } | null>(null);
|
|
272
272
|
const [fillSourceRange, setFillSourceRange] = useState<{ startRow: number; startCol: number; endRow: number; endCol: number } | null>(null);
|
|
273
273
|
const autoScrollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
274
|
+
const [isDraggingSelection, setIsDraggingSelection] = useState(false);
|
|
274
275
|
|
|
275
276
|
// Computed heights
|
|
276
277
|
const filterRowHeight = showFilters ? 40 : 0;
|
|
@@ -515,16 +516,19 @@ export function Grid<TData extends Row = Row>(props: GridProps<TData>) {
|
|
|
515
516
|
}
|
|
516
517
|
}, [state.activeCell, state.editingCell, rowHeight, totalHeaderHeight, columnPositions, columns]);
|
|
517
518
|
|
|
518
|
-
// Cell
|
|
519
|
-
const
|
|
519
|
+
// Cell mouse down handler (starts selection and drag)
|
|
520
|
+
const handleCellMouseDown = useCallback(
|
|
520
521
|
(rowIndex: number, colIndex: number, e: React.MouseEvent) => {
|
|
521
|
-
// console.log("[GP-Grid] Cell
|
|
522
|
+
// console.log("[GP-Grid] Cell mousedown:", { rowIndex, colIndex, coreExists: !!coreRef.current });
|
|
522
523
|
const core = coreRef.current;
|
|
523
524
|
if (!core || core.getEditState() !== null) {
|
|
524
|
-
// console.warn("[GP-Grid] Core not initialized on cell
|
|
525
|
+
// console.warn("[GP-Grid] Core not initialized on cell mousedown");
|
|
525
526
|
return;
|
|
526
527
|
}
|
|
527
528
|
|
|
529
|
+
// Only handle left mouse button
|
|
530
|
+
if (e.button !== 0) return;
|
|
531
|
+
|
|
528
532
|
// Focus the container to enable keyboard navigation
|
|
529
533
|
containerRef.current?.focus();
|
|
530
534
|
|
|
@@ -532,6 +536,11 @@ export function Grid<TData extends Row = Row>(props: GridProps<TData>) {
|
|
|
532
536
|
{ row: rowIndex, col: colIndex },
|
|
533
537
|
{ shift: e.shiftKey, ctrl: e.ctrlKey || e.metaKey }
|
|
534
538
|
);
|
|
539
|
+
|
|
540
|
+
// Start drag selection (unless shift is held - that's a one-time extend)
|
|
541
|
+
if (!e.shiftKey) {
|
|
542
|
+
setIsDraggingSelection(true);
|
|
543
|
+
}
|
|
535
544
|
},
|
|
536
545
|
[]
|
|
537
546
|
);
|
|
@@ -727,6 +736,111 @@ export function Grid<TData extends Row = Row>(props: GridProps<TData>) {
|
|
|
727
736
|
};
|
|
728
737
|
}, [isDraggingFill, totalHeaderHeight, rowHeight, columnPositions]);
|
|
729
738
|
|
|
739
|
+
// Handle mouse move/up during selection drag
|
|
740
|
+
useEffect(() => {
|
|
741
|
+
if (!isDraggingSelection) return;
|
|
742
|
+
|
|
743
|
+
// Auto-scroll configuration
|
|
744
|
+
const SCROLL_THRESHOLD = 40;
|
|
745
|
+
const SCROLL_SPEED = 10;
|
|
746
|
+
|
|
747
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
748
|
+
const core = coreRef.current;
|
|
749
|
+
const container = containerRef.current;
|
|
750
|
+
if (!core || !container) return;
|
|
751
|
+
|
|
752
|
+
// Get container bounds
|
|
753
|
+
const rect = container.getBoundingClientRect();
|
|
754
|
+
const scrollLeft = container.scrollLeft;
|
|
755
|
+
const scrollTop = container.scrollTop;
|
|
756
|
+
|
|
757
|
+
// Calculate mouse position relative to grid content
|
|
758
|
+
const mouseX = e.clientX - rect.left + scrollLeft;
|
|
759
|
+
const mouseY = e.clientY - rect.top + scrollTop - totalHeaderHeight;
|
|
760
|
+
|
|
761
|
+
// Find the row and column under the mouse
|
|
762
|
+
const targetRow = Math.max(0, Math.min(Math.floor(mouseY / rowHeight), core.getRowCount() - 1));
|
|
763
|
+
|
|
764
|
+
// Find column by checking column positions
|
|
765
|
+
let targetCol = 0;
|
|
766
|
+
for (let i = 0; i < columnPositions.length - 1; i++) {
|
|
767
|
+
if (mouseX >= columnPositions[i]! && mouseX < columnPositions[i + 1]!) {
|
|
768
|
+
targetCol = i;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
if (mouseX >= columnPositions[columnPositions.length - 1]!) {
|
|
772
|
+
targetCol = columnPositions.length - 2;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
targetCol = Math.max(0, Math.min(targetCol, columns.length - 1));
|
|
776
|
+
|
|
777
|
+
// Extend selection to target cell (like shift+click)
|
|
778
|
+
core.selection.startSelection(
|
|
779
|
+
{ row: targetRow, col: targetCol },
|
|
780
|
+
{ shift: true }
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
// Auto-scroll logic
|
|
784
|
+
const mouseYInContainer = e.clientY - rect.top;
|
|
785
|
+
const mouseXInContainer = e.clientX - rect.left;
|
|
786
|
+
|
|
787
|
+
// Clear any existing auto-scroll
|
|
788
|
+
if (autoScrollIntervalRef.current) {
|
|
789
|
+
clearInterval(autoScrollIntervalRef.current);
|
|
790
|
+
autoScrollIntervalRef.current = null;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Check if we need to auto-scroll
|
|
794
|
+
let scrollDeltaX = 0;
|
|
795
|
+
let scrollDeltaY = 0;
|
|
796
|
+
|
|
797
|
+
// Vertical scrolling
|
|
798
|
+
if (mouseYInContainer < SCROLL_THRESHOLD + totalHeaderHeight) {
|
|
799
|
+
scrollDeltaY = -SCROLL_SPEED;
|
|
800
|
+
} else if (mouseYInContainer > rect.height - SCROLL_THRESHOLD) {
|
|
801
|
+
scrollDeltaY = SCROLL_SPEED;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Horizontal scrolling
|
|
805
|
+
if (mouseXInContainer < SCROLL_THRESHOLD) {
|
|
806
|
+
scrollDeltaX = -SCROLL_SPEED;
|
|
807
|
+
} else if (mouseXInContainer > rect.width - SCROLL_THRESHOLD) {
|
|
808
|
+
scrollDeltaX = SCROLL_SPEED;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Start auto-scroll if needed
|
|
812
|
+
if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {
|
|
813
|
+
autoScrollIntervalRef.current = setInterval(() => {
|
|
814
|
+
if (containerRef.current) {
|
|
815
|
+
containerRef.current.scrollTop += scrollDeltaY;
|
|
816
|
+
containerRef.current.scrollLeft += scrollDeltaX;
|
|
817
|
+
}
|
|
818
|
+
}, 16); // ~60fps
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
const handleMouseUp = () => {
|
|
823
|
+
// Clear auto-scroll
|
|
824
|
+
if (autoScrollIntervalRef.current) {
|
|
825
|
+
clearInterval(autoScrollIntervalRef.current);
|
|
826
|
+
autoScrollIntervalRef.current = null;
|
|
827
|
+
}
|
|
828
|
+
setIsDraggingSelection(false);
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
832
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
833
|
+
|
|
834
|
+
return () => {
|
|
835
|
+
if (autoScrollIntervalRef.current) {
|
|
836
|
+
clearInterval(autoScrollIntervalRef.current);
|
|
837
|
+
autoScrollIntervalRef.current = null;
|
|
838
|
+
}
|
|
839
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
840
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
841
|
+
};
|
|
842
|
+
}, [isDraggingSelection, totalHeaderHeight, rowHeight, columnPositions, columns.length]);
|
|
843
|
+
|
|
730
844
|
// Render helpers
|
|
731
845
|
const isSelected = useCallback(
|
|
732
846
|
(row: number, col: number): boolean => {
|
|
@@ -1164,7 +1278,7 @@ export function Grid<TData extends Row = Row>(props: GridProps<TData>) {
|
|
|
1164
1278
|
width: `${column.width}px`,
|
|
1165
1279
|
height: `${rowHeight}px`,
|
|
1166
1280
|
}}
|
|
1167
|
-
|
|
1281
|
+
onMouseDown={(e) => handleCellMouseDown(slot.rowIndex, colIndex, e)}
|
|
1168
1282
|
onDoubleClick={() => handleCellDoubleClick(slot.rowIndex, colIndex)}
|
|
1169
1283
|
>
|
|
1170
1284
|
{isEditing && state.editingCell
|
package/src/styles.ts
CHANGED
|
@@ -100,6 +100,8 @@ export const gridStyles = `
|
|
|
100
100
|
background-color: var(--gp-grid-bg);
|
|
101
101
|
border: 1px solid var(--gp-grid-border);
|
|
102
102
|
border-radius: 6px;
|
|
103
|
+
user-select: none;
|
|
104
|
+
-webkit-user-select: none;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
.gp-grid-container:focus {
|
|
@@ -237,6 +239,8 @@ export const gridStyles = `
|
|
|
237
239
|
overflow: hidden;
|
|
238
240
|
text-overflow: ellipsis;
|
|
239
241
|
white-space: nowrap;
|
|
242
|
+
user-select: none;
|
|
243
|
+
-webkit-user-select: none;
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
/* Alternating row colors */
|