@agent-native/toolkit 0.15.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +26 -0
  2. package/agent-native.eject.json +16 -0
  3. package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
  4. package/dist/composer/ComposerPlusMenu.js +29 -19
  5. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  6. package/dist/composer/PromptComposer.d.ts +11 -1
  7. package/dist/composer/PromptComposer.d.ts.map +1 -1
  8. package/dist/composer/PromptComposer.js +2 -2
  9. package/dist/composer/PromptComposer.js.map +1 -1
  10. package/dist/composer/TiptapComposer.d.ts +30 -1
  11. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  12. package/dist/composer/TiptapComposer.js +255 -145
  13. package/dist/composer/TiptapComposer.js.map +1 -1
  14. package/dist/composer/TiptapComposer.spec.js +33 -1
  15. package/dist/composer/TiptapComposer.spec.js.map +1 -1
  16. package/dist/composer/index.d.ts +1 -0
  17. package/dist/composer/index.d.ts.map +1 -1
  18. package/dist/composer/index.js +1 -0
  19. package/dist/composer/index.js.map +1 -1
  20. package/dist/composer/model-selection.d.ts +12 -0
  21. package/dist/composer/model-selection.d.ts.map +1 -0
  22. package/dist/composer/model-selection.js +31 -0
  23. package/dist/composer/model-selection.js.map +1 -0
  24. package/dist/composer/model-selection.spec.d.ts +2 -0
  25. package/dist/composer/model-selection.spec.d.ts.map +1 -0
  26. package/dist/composer/model-selection.spec.js +38 -0
  27. package/dist/composer/model-selection.spec.js.map +1 -0
  28. package/dist/data-grid/DataGrid.d.ts +92 -0
  29. package/dist/data-grid/DataGrid.d.ts.map +1 -0
  30. package/dist/data-grid/DataGrid.js +315 -0
  31. package/dist/data-grid/DataGrid.js.map +1 -0
  32. package/dist/data-grid/DataGrid.spec.d.ts +2 -0
  33. package/dist/data-grid/DataGrid.spec.d.ts.map +1 -0
  34. package/dist/data-grid/DataGrid.spec.js +134 -0
  35. package/dist/data-grid/DataGrid.spec.js.map +1 -0
  36. package/dist/data-grid/index.d.ts +2 -0
  37. package/dist/data-grid/index.d.ts.map +1 -0
  38. package/dist/data-grid/index.js +2 -0
  39. package/dist/data-grid/index.js.map +1 -0
  40. package/dist/sharing/SharePrimitives.d.ts +14 -3
  41. package/dist/sharing/SharePrimitives.d.ts.map +1 -1
  42. package/dist/sharing/SharePrimitives.js +11 -3
  43. package/dist/sharing/SharePrimitives.js.map +1 -1
  44. package/dist/sharing/index.d.ts +1 -1
  45. package/dist/sharing/index.d.ts.map +1 -1
  46. package/dist/sharing/index.js +1 -1
  47. package/dist/sharing/index.js.map +1 -1
  48. package/package.json +15 -1
  49. package/src/composer/ComposerPlusMenu.tsx +122 -65
  50. package/src/composer/PromptComposer.tsx +21 -0
  51. package/src/composer/TiptapComposer.spec.ts +46 -0
  52. package/src/composer/TiptapComposer.tsx +721 -349
  53. package/src/composer/index.ts +6 -0
  54. package/src/composer/model-selection.spec.ts +45 -0
  55. package/src/composer/model-selection.ts +51 -0
  56. package/src/data-grid/DataGrid.spec.tsx +243 -0
  57. package/src/data-grid/DataGrid.tsx +754 -0
  58. package/src/data-grid/index.ts +15 -0
  59. package/src/sharing/SharePrimitives.tsx +47 -8
  60. package/src/sharing/index.ts +4 -0
@@ -0,0 +1,754 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../utils.js";
4
+
5
+ export type DataGridSelectionMode = "none" | "single" | "multiple";
6
+
7
+ export type DataGridDirection = "up" | "down" | "left" | "right";
8
+
9
+ export interface DataGridActiveCell {
10
+ rowId: string;
11
+ columnId: string;
12
+ editing: boolean;
13
+ }
14
+
15
+ export interface DataGridColumn<Row, Value = unknown> {
16
+ id: string;
17
+ label?: React.ReactNode;
18
+ width?: number;
19
+ minWidth?: number;
20
+ maxWidth?: number;
21
+ resizable?: boolean;
22
+ editable?: boolean | ((row: Row) => boolean);
23
+ getValue?: (row: Row) => Value;
24
+ renderCell?: (context: DataGridCellContext<Row, Value>) => React.ReactNode;
25
+ renderEditor?: (
26
+ context: DataGridEditorContext<Row, Value>,
27
+ ) => React.ReactNode;
28
+ }
29
+
30
+ export interface DataGridCellContext<Row, Value = unknown> {
31
+ row: Row;
32
+ rowIndex: number;
33
+ column: DataGridColumn<Row, Value>;
34
+ value: Value | undefined;
35
+ active: boolean;
36
+ editing: boolean;
37
+ editable: boolean;
38
+ startEditing: () => void;
39
+ commit: (value: Value) => void;
40
+ cancel: () => void;
41
+ move: (direction: DataGridDirection) => void;
42
+ }
43
+
44
+ export interface DataGridEditorContext<
45
+ Row,
46
+ Value = unknown,
47
+ > extends DataGridCellContext<Row, Value> {
48
+ onCommit: (value: Value) => void;
49
+ onCancel: () => void;
50
+ onMove: (direction: DataGridDirection) => void;
51
+ }
52
+
53
+ export interface DataGridCommitContext<Row> {
54
+ row: Row;
55
+ rowIndex: number;
56
+ column: DataGridColumn<Row>;
57
+ previousValue: unknown;
58
+ value: unknown;
59
+ }
60
+
61
+ export interface DataGridSlotContext<Row> {
62
+ rows: readonly Row[];
63
+ columns: readonly DataGridColumn<Row>[];
64
+ columnWidths: Readonly<Record<string, number>>;
65
+ gridTemplateColumns: string;
66
+ selection: DataGridSelectionMode;
67
+ selectedRowIds: ReadonlySet<string>;
68
+ getRowId: (row: Row, rowIndex: number) => string;
69
+ setActiveCell: (cell: DataGridActiveCell | null) => void;
70
+ toggleRowSelection: (rowId: string) => void;
71
+ resizeColumn: (columnId: string, width: number) => void;
72
+ }
73
+
74
+ export interface DataGridRowContext<Row> extends DataGridSlotContext<Row> {
75
+ row: Row;
76
+ rowIndex: number;
77
+ rowId: string;
78
+ selected: boolean;
79
+ }
80
+
81
+ type DataGridScrollContainerProps = Omit<
82
+ React.HTMLAttributes<HTMLDivElement>,
83
+ "children"
84
+ > &
85
+ Record<`data-${string}`, string | undefined>;
86
+
87
+ export interface DataGridProps<Row> extends Omit<
88
+ React.HTMLAttributes<HTMLDivElement>,
89
+ "children"
90
+ > {
91
+ rows: readonly Row[];
92
+ columns: readonly DataGridColumn<Row>[];
93
+ getRowId: (row: Row, rowIndex: number) => string;
94
+ columnWidths?: Readonly<Record<string, number>>;
95
+ onColumnWidthsChange?: (columnWidths: Record<string, number>) => void;
96
+ selection?: DataGridSelectionMode;
97
+ selectedRowIds?: ReadonlySet<string>;
98
+ onSelectedRowIdsChange?: (rowIds: ReadonlySet<string>) => void;
99
+ activeCell?: DataGridActiveCell | null;
100
+ onActiveCellChange?: (cell: DataGridActiveCell | null) => void;
101
+ onCellCommit?: (context: DataGridCommitContext<Row>) => void;
102
+ renderHeader?: (context: DataGridSlotContext<Row>) => React.ReactNode;
103
+ renderBody?: (context: DataGridSlotContext<Row>) => React.ReactNode;
104
+ renderRow?: (context: DataGridRowContext<Row>) => React.ReactNode;
105
+ renderFooter?: (context: DataGridSlotContext<Row>) => React.ReactNode;
106
+ emptyState?: React.ReactNode;
107
+ loading?: boolean;
108
+ loadingRowCount?: number;
109
+ contentClassName?: string;
110
+ rowClassName?: string | ((row: Row, rowIndex: number) => string);
111
+ scrollContainerProps?: DataGridScrollContainerProps;
112
+ }
113
+
114
+ const DEFAULT_COLUMN_WIDTH = 160;
115
+ const DEFAULT_MIN_COLUMN_WIDTH = 96;
116
+ const DEFAULT_MAX_COLUMN_WIDTH = 640;
117
+ const SELECTION_COLUMN_WIDTH = 40;
118
+
119
+ function columnWidth<Row>(
120
+ column: DataGridColumn<Row>,
121
+ requestedWidth: number | undefined,
122
+ ) {
123
+ const fallback = column.width ?? DEFAULT_COLUMN_WIDTH;
124
+ const width =
125
+ requestedWidth !== undefined && Number.isFinite(requestedWidth)
126
+ ? requestedWidth
127
+ : fallback;
128
+ const minWidth = column.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
129
+ const maxWidth = Math.max(
130
+ minWidth,
131
+ column.maxWidth ?? DEFAULT_MAX_COLUMN_WIDTH,
132
+ );
133
+ return Math.min(maxWidth, Math.max(minWidth, Math.round(width)));
134
+ }
135
+
136
+ export function clampDataGridColumnWidth<Row>(
137
+ width: number,
138
+ column: DataGridColumn<Row>,
139
+ ) {
140
+ return columnWidth(column, width);
141
+ }
142
+
143
+ export function dataGridColumnTemplate<Row>(
144
+ columns: readonly DataGridColumn<Row>[],
145
+ columnWidths: Readonly<Record<string, number>> = {},
146
+ selection: DataGridSelectionMode = "none",
147
+ ) {
148
+ const selectionTemplate =
149
+ selection === "none" ? [] : [`${SELECTION_COLUMN_WIDTH}px`];
150
+ return [
151
+ ...selectionTemplate,
152
+ ...columns.map(
153
+ (column) => `${columnWidth(column, columnWidths[column.id])}px`,
154
+ ),
155
+ ].join(" ");
156
+ }
157
+
158
+ function isColumnEditable<Row>(column: DataGridColumn<Row>, row: Row) {
159
+ if (!column.renderEditor) return false;
160
+ return typeof column.editable === "function"
161
+ ? column.editable(row)
162
+ : column.editable !== false;
163
+ }
164
+
165
+ function defaultCellText(value: unknown) {
166
+ return value == null ? "" : String(value);
167
+ }
168
+
169
+ function loadingRows<Row>({
170
+ count,
171
+ columns,
172
+ gridTemplateColumns,
173
+ selection,
174
+ }: {
175
+ count: number;
176
+ columns: readonly DataGridColumn<Row>[];
177
+ gridTemplateColumns: string;
178
+ selection: DataGridSelectionMode;
179
+ }) {
180
+ return Array.from({ length: count }, (_, rowIndex) => (
181
+ <div
182
+ key={`loading-${rowIndex}`}
183
+ role="row"
184
+ aria-hidden="true"
185
+ className="grid min-h-10 border-b border-border/30"
186
+ style={{ gridTemplateColumns }}
187
+ >
188
+ {selection !== "none" ? <div role="gridcell" /> : null}
189
+ {columns.map((column) => (
190
+ <div key={column.id} role="gridcell" className="p-2">
191
+ <div className="h-4 w-2/3 animate-pulse rounded bg-muted" />
192
+ </div>
193
+ ))}
194
+ </div>
195
+ ));
196
+ }
197
+
198
+ function DataGridHeader<Row>({
199
+ columns,
200
+ gridTemplateColumns,
201
+ selection,
202
+ rows,
203
+ getRowId,
204
+ selectedRowIds,
205
+ onToggleAll,
206
+ onStartResize,
207
+ }: {
208
+ columns: readonly DataGridColumn<Row>[];
209
+ gridTemplateColumns: string;
210
+ selection: DataGridSelectionMode;
211
+ rows: readonly Row[];
212
+ getRowId: (row: Row, rowIndex: number) => string;
213
+ selectedRowIds: ReadonlySet<string>;
214
+ onToggleAll: () => void;
215
+ onStartResize: (
216
+ event: React.PointerEvent<HTMLButtonElement>,
217
+ column: DataGridColumn<Row>,
218
+ ) => void;
219
+ }) {
220
+ const allSelected =
221
+ rows.length > 0 &&
222
+ rows.every((row, rowIndex) => selectedRowIds.has(getRowId(row, rowIndex)));
223
+
224
+ return (
225
+ <div
226
+ role="row"
227
+ className="grid border-y border-border/35 text-xs font-medium text-muted-foreground/80"
228
+ style={{ gridTemplateColumns }}
229
+ >
230
+ {selection !== "none" ? (
231
+ <div
232
+ role="columnheader"
233
+ className="flex h-9 items-center justify-center border-r border-border/30"
234
+ >
235
+ {selection === "multiple" ? (
236
+ <input
237
+ type="checkbox"
238
+ aria-label="Select all rows"
239
+ checked={allSelected}
240
+ onChange={onToggleAll}
241
+ />
242
+ ) : null}
243
+ </div>
244
+ ) : null}
245
+ {columns.map((column) => (
246
+ <div
247
+ key={column.id}
248
+ role="columnheader"
249
+ data-data-grid-column-id={column.id}
250
+ className="group relative flex h-9 min-w-0 items-center gap-1 border-r border-border/30 px-2 last:border-r-0"
251
+ >
252
+ <span className="min-w-0 truncate">{column.label ?? column.id}</span>
253
+ {column.resizable === false ? null : (
254
+ <button
255
+ type="button"
256
+ aria-label={`Resize ${column.id} column`}
257
+ className="absolute inset-y-0 right-0 z-10 w-2 cursor-col-resize opacity-0 transition-opacity hover:opacity-100 focus-visible:opacity-100"
258
+ onPointerDown={(event) => onStartResize(event, column)}
259
+ />
260
+ )}
261
+ </div>
262
+ ))}
263
+ </div>
264
+ );
265
+ }
266
+
267
+ function DataGridDefaultRow<Row>({
268
+ row,
269
+ rowIndex,
270
+ rowId,
271
+ columns,
272
+ gridTemplateColumns,
273
+ selection,
274
+ selected,
275
+ rowClassName,
276
+ activeCell,
277
+ setActiveCell,
278
+ toggleRowSelection,
279
+ moveActiveCell,
280
+ commitCell,
281
+ }: {
282
+ row: Row;
283
+ rowIndex: number;
284
+ rowId: string;
285
+ columns: readonly DataGridColumn<Row>[];
286
+ gridTemplateColumns: string;
287
+ selection: DataGridSelectionMode;
288
+ selected: boolean;
289
+ rowClassName?: string;
290
+ activeCell: DataGridActiveCell | null;
291
+ setActiveCell: (cell: DataGridActiveCell | null) => void;
292
+ toggleRowSelection: (rowId: string) => void;
293
+ moveActiveCell: (
294
+ rowIndex: number,
295
+ columnIndex: number,
296
+ direction: DataGridDirection,
297
+ ) => void;
298
+ commitCell: (
299
+ row: Row,
300
+ rowIndex: number,
301
+ column: DataGridColumn<Row>,
302
+ value: unknown,
303
+ ) => void;
304
+ }) {
305
+ return (
306
+ <div
307
+ role="row"
308
+ data-data-grid-row-id={rowId}
309
+ aria-selected={selected || undefined}
310
+ className={cn(
311
+ "grid min-h-10 border-b border-border/30 text-sm",
312
+ rowClassName,
313
+ selected && "bg-muted/35",
314
+ )}
315
+ style={{ gridTemplateColumns }}
316
+ >
317
+ {selection !== "none" ? (
318
+ <div
319
+ role="gridcell"
320
+ className="flex items-center justify-center border-r border-border/30"
321
+ >
322
+ <input
323
+ type={selection === "single" ? "radio" : "checkbox"}
324
+ name={selection === "single" ? "data-grid-selection" : undefined}
325
+ aria-label={`Select row ${rowId}`}
326
+ checked={selected}
327
+ onChange={() => toggleRowSelection(rowId)}
328
+ />
329
+ </div>
330
+ ) : null}
331
+ {columns.map((column, columnIndex) => {
332
+ const value = column.getValue?.(row);
333
+ const active =
334
+ activeCell?.rowId === rowId && activeCell.columnId === column.id;
335
+ const editing = active && activeCell.editing;
336
+ const editable = isColumnEditable(column, row);
337
+ const startEditing = () => {
338
+ if (editable) {
339
+ setActiveCell({ rowId, columnId: column.id, editing: true });
340
+ }
341
+ };
342
+ const cancel = () => {
343
+ setActiveCell({ rowId, columnId: column.id, editing: false });
344
+ };
345
+ const commit = (nextValue: unknown) => {
346
+ commitCell(row, rowIndex, column, nextValue);
347
+ setActiveCell({ rowId, columnId: column.id, editing: false });
348
+ };
349
+ const move = (direction: DataGridDirection) => {
350
+ moveActiveCell(rowIndex, columnIndex, direction);
351
+ };
352
+ const context = {
353
+ row,
354
+ rowIndex,
355
+ column,
356
+ value,
357
+ active,
358
+ editing,
359
+ editable,
360
+ startEditing,
361
+ commit,
362
+ cancel,
363
+ move,
364
+ } satisfies DataGridCellContext<Row>;
365
+
366
+ return (
367
+ <div
368
+ key={column.id}
369
+ role="gridcell"
370
+ data-data-grid-cell="true"
371
+ data-data-grid-row-id={rowId}
372
+ data-data-grid-column-id={column.id}
373
+ tabIndex={active ? 0 : -1}
374
+ className={cn(
375
+ "min-w-0 border-r border-border/30 px-2 py-2 outline-none last:border-r-0",
376
+ active && "bg-accent/50 ring-1 ring-inset ring-ring",
377
+ )}
378
+ onClick={() =>
379
+ setActiveCell({ rowId, columnId: column.id, editing: false })
380
+ }
381
+ onDoubleClick={startEditing}
382
+ onKeyDown={(event) => {
383
+ if (editing) {
384
+ if (event.key === "Escape") {
385
+ event.preventDefault();
386
+ cancel();
387
+ }
388
+ return;
389
+ }
390
+ if (event.key === "Enter" || event.key === "F2") {
391
+ if (editable) {
392
+ event.preventDefault();
393
+ startEditing();
394
+ }
395
+ return;
396
+ }
397
+ const direction = {
398
+ ArrowUp: "up",
399
+ ArrowDown: "down",
400
+ ArrowLeft: "left",
401
+ ArrowRight: "right",
402
+ Tab: event.shiftKey ? "left" : "right",
403
+ }[event.key] as DataGridDirection | undefined;
404
+ if (direction) {
405
+ event.preventDefault();
406
+ move(direction);
407
+ }
408
+ }}
409
+ onPointerDown={(event) => {
410
+ if (event.button === 0) event.currentTarget.focus();
411
+ }}
412
+ >
413
+ {editing && column.renderEditor
414
+ ? column.renderEditor({
415
+ ...context,
416
+ onCommit: (nextValue) => commit(nextValue),
417
+ onCancel: cancel,
418
+ onMove: move,
419
+ })
420
+ : column.renderCell
421
+ ? column.renderCell(context)
422
+ : defaultCellText(value)}
423
+ </div>
424
+ );
425
+ })}
426
+ </div>
427
+ );
428
+ }
429
+
430
+ export function DataGrid<Row>({
431
+ rows,
432
+ columns,
433
+ getRowId,
434
+ columnWidths,
435
+ onColumnWidthsChange,
436
+ selection = "none",
437
+ selectedRowIds,
438
+ onSelectedRowIdsChange,
439
+ activeCell,
440
+ onActiveCellChange,
441
+ onCellCommit,
442
+ renderHeader,
443
+ renderBody,
444
+ renderRow,
445
+ renderFooter,
446
+ emptyState,
447
+ loading = false,
448
+ loadingRowCount = 4,
449
+ contentClassName,
450
+ rowClassName,
451
+ scrollContainerProps,
452
+ className,
453
+ ...gridProps
454
+ }: DataGridProps<Row>) {
455
+ const gridRef = React.useRef<HTMLDivElement>(null);
456
+ const hasMountedActiveCellRef = React.useRef(false);
457
+ const [internalColumnWidths, setInternalColumnWidths] = React.useState<
458
+ Record<string, number>
459
+ >({});
460
+ const [internalSelectedRowIds, setInternalSelectedRowIds] = React.useState<
461
+ ReadonlySet<string>
462
+ >(() => new Set());
463
+ const [internalActiveCell, setInternalActiveCell] =
464
+ React.useState<DataGridActiveCell | null>(() =>
465
+ rows.length > 0 && columns.length > 0
466
+ ? {
467
+ rowId: getRowId(rows[0], 0),
468
+ columnId: columns[0].id,
469
+ editing: false,
470
+ }
471
+ : null,
472
+ );
473
+
474
+ const resolvedColumnWidths = columnWidths ?? internalColumnWidths;
475
+ const resolvedSelectedRowIds = selectedRowIds ?? internalSelectedRowIds;
476
+ const resolvedActiveCell =
477
+ activeCell === undefined ? internalActiveCell : activeCell;
478
+ const gridTemplateColumns = dataGridColumnTemplate(
479
+ columns,
480
+ resolvedColumnWidths,
481
+ selection,
482
+ );
483
+
484
+ const setActive = React.useCallback(
485
+ (cell: DataGridActiveCell | null) => {
486
+ if (activeCell === undefined) setInternalActiveCell(cell);
487
+ onActiveCellChange?.(cell);
488
+ },
489
+ [activeCell, onActiveCellChange],
490
+ );
491
+
492
+ React.useEffect(() => {
493
+ if (!resolvedActiveCell) return;
494
+ const hasRow = rows.some(
495
+ (row, rowIndex) => getRowId(row, rowIndex) === resolvedActiveCell.rowId,
496
+ );
497
+ const hasColumn = columns.some(
498
+ (column) => column.id === resolvedActiveCell.columnId,
499
+ );
500
+ if (!hasRow || !hasColumn) setActive(null);
501
+ }, [columns, getRowId, rows, resolvedActiveCell, setActive]);
502
+
503
+ React.useEffect(() => {
504
+ if (activeCell !== undefined || resolvedActiveCell) return;
505
+ if (rows.length === 0 || columns.length === 0) return;
506
+ setActive({
507
+ rowId: getRowId(rows[0], 0),
508
+ columnId: columns[0].id,
509
+ editing: false,
510
+ });
511
+ }, [activeCell, columns, getRowId, resolvedActiveCell, rows, setActive]);
512
+
513
+ React.useEffect(() => {
514
+ if (!resolvedActiveCell || resolvedActiveCell.editing) return;
515
+ if (!hasMountedActiveCellRef.current) {
516
+ hasMountedActiveCellRef.current = true;
517
+ return;
518
+ }
519
+ const cells = gridRef.current?.querySelectorAll<HTMLElement>(
520
+ "[data-data-grid-cell='true']",
521
+ );
522
+ const target = Array.from(cells ?? []).find(
523
+ (cell) =>
524
+ cell.dataset.dataGridRowId === resolvedActiveCell.rowId &&
525
+ cell.dataset.dataGridColumnId === resolvedActiveCell.columnId,
526
+ );
527
+ target?.focus();
528
+ }, [resolvedActiveCell]);
529
+
530
+ const setColumnWidth = React.useCallback(
531
+ (columnId: string, width: number) => {
532
+ const column = columns.find((candidate) => candidate.id === columnId);
533
+ if (!column) return;
534
+ const nextWidths = {
535
+ ...resolvedColumnWidths,
536
+ [columnId]: clampDataGridColumnWidth(width, column),
537
+ };
538
+ if (columnWidths === undefined) setInternalColumnWidths(nextWidths);
539
+ onColumnWidthsChange?.(nextWidths);
540
+ },
541
+ [columnWidths, columns, onColumnWidthsChange, resolvedColumnWidths],
542
+ );
543
+
544
+ const startResize = React.useCallback(
545
+ (
546
+ event: React.PointerEvent<HTMLButtonElement>,
547
+ column: DataGridColumn<Row>,
548
+ ) => {
549
+ event.preventDefault();
550
+ event.stopPropagation();
551
+ const startX = event.clientX;
552
+ const startWidth = columnWidth(column, resolvedColumnWidths[column.id]);
553
+ const handleMove = (moveEvent: PointerEvent) => {
554
+ setColumnWidth(column.id, startWidth + moveEvent.clientX - startX);
555
+ };
556
+ const handleUp = () => {
557
+ document.removeEventListener("pointermove", handleMove);
558
+ document.removeEventListener("pointerup", handleUp);
559
+ };
560
+ document.addEventListener("pointermove", handleMove);
561
+ document.addEventListener("pointerup", handleUp, { once: true });
562
+ },
563
+ [resolvedColumnWidths, setColumnWidth],
564
+ );
565
+
566
+ const setSelected = React.useCallback(
567
+ (nextRowIds: ReadonlySet<string>) => {
568
+ if (selectedRowIds === undefined) setInternalSelectedRowIds(nextRowIds);
569
+ onSelectedRowIdsChange?.(nextRowIds);
570
+ },
571
+ [onSelectedRowIdsChange, selectedRowIds],
572
+ );
573
+
574
+ const toggleRowSelection = React.useCallback(
575
+ (rowId: string) => {
576
+ if (selection === "none") return;
577
+ const next = new Set(resolvedSelectedRowIds);
578
+ if (selection === "single") {
579
+ if (next.has(rowId)) next.clear();
580
+ else {
581
+ next.clear();
582
+ next.add(rowId);
583
+ }
584
+ } else if (next.has(rowId)) next.delete(rowId);
585
+ else next.add(rowId);
586
+ setSelected(next);
587
+ },
588
+ [resolvedSelectedRowIds, selection, setSelected],
589
+ );
590
+
591
+ const toggleAllRows = React.useCallback(() => {
592
+ if (selection !== "multiple") return;
593
+ const allSelected =
594
+ rows.length > 0 &&
595
+ rows.every((row, rowIndex) =>
596
+ resolvedSelectedRowIds.has(getRowId(row, rowIndex)),
597
+ );
598
+ setSelected(
599
+ allSelected
600
+ ? new Set()
601
+ : new Set(rows.map((row, rowIndex) => getRowId(row, rowIndex))),
602
+ );
603
+ }, [getRowId, resolvedSelectedRowIds, rows, selection, setSelected]);
604
+
605
+ const moveActiveCell = React.useCallback(
606
+ (rowIndex: number, columnIndex: number, direction: DataGridDirection) => {
607
+ if (rows.length === 0 || columns.length === 0) return;
608
+ let nextRowIndex = rowIndex;
609
+ let nextColumnIndex = columnIndex;
610
+ if (direction === "up") nextRowIndex = Math.max(0, rowIndex - 1);
611
+ if (direction === "down") {
612
+ nextRowIndex = Math.min(rows.length - 1, rowIndex + 1);
613
+ }
614
+ if (direction === "left") {
615
+ if (columnIndex > 0) nextColumnIndex = columnIndex - 1;
616
+ else if (rowIndex > 0) {
617
+ nextRowIndex = rowIndex - 1;
618
+ nextColumnIndex = columns.length - 1;
619
+ }
620
+ }
621
+ if (direction === "right") {
622
+ if (columnIndex < columns.length - 1) nextColumnIndex = columnIndex + 1;
623
+ else if (rowIndex < rows.length - 1) {
624
+ nextRowIndex = rowIndex + 1;
625
+ nextColumnIndex = 0;
626
+ }
627
+ }
628
+ setActive({
629
+ rowId: getRowId(rows[nextRowIndex], nextRowIndex),
630
+ columnId: columns[nextColumnIndex].id,
631
+ editing: false,
632
+ });
633
+ },
634
+ [columns, getRowId, rows, setActive],
635
+ );
636
+
637
+ const commitCell = React.useCallback(
638
+ (
639
+ row: Row,
640
+ rowIndex: number,
641
+ column: DataGridColumn<Row>,
642
+ value: unknown,
643
+ ) => {
644
+ onCellCommit?.({
645
+ row,
646
+ rowIndex,
647
+ column,
648
+ previousValue: column.getValue?.(row),
649
+ value,
650
+ });
651
+ },
652
+ [onCellCommit],
653
+ );
654
+
655
+ const slotContext: DataGridSlotContext<Row> = {
656
+ rows,
657
+ columns,
658
+ columnWidths: resolvedColumnWidths,
659
+ gridTemplateColumns,
660
+ selection,
661
+ selectedRowIds: resolvedSelectedRowIds,
662
+ getRowId,
663
+ setActiveCell: setActive,
664
+ toggleRowSelection,
665
+ resizeColumn: setColumnWidth,
666
+ };
667
+
668
+ const { className: scrollClassName, ...restScrollContainerProps } =
669
+ scrollContainerProps ?? {};
670
+ const loadingCount = Number.isFinite(loadingRowCount)
671
+ ? Math.max(1, Math.floor(loadingRowCount))
672
+ : 1;
673
+
674
+ return (
675
+ <div
676
+ {...gridProps}
677
+ ref={gridRef}
678
+ role={gridProps.role ?? "grid"}
679
+ className={cn("w-full min-w-0", className)}
680
+ aria-busy={loading || undefined}
681
+ >
682
+ <div
683
+ {...restScrollContainerProps}
684
+ data-data-grid-scroll-container="true"
685
+ className={cn(
686
+ "w-full min-w-0 max-w-full overflow-x-auto overscroll-x-contain",
687
+ scrollClassName,
688
+ )}
689
+ >
690
+ <div className={cn("w-max min-w-full", contentClassName)}>
691
+ {renderHeader ? (
692
+ renderHeader(slotContext)
693
+ ) : (
694
+ <DataGridHeader
695
+ columns={columns}
696
+ gridTemplateColumns={gridTemplateColumns}
697
+ selection={selection}
698
+ rows={rows}
699
+ getRowId={getRowId}
700
+ selectedRowIds={resolvedSelectedRowIds}
701
+ onToggleAll={toggleAllRows}
702
+ onStartResize={startResize}
703
+ />
704
+ )}
705
+ {renderBody
706
+ ? renderBody(slotContext)
707
+ : loading
708
+ ? loadingRows({
709
+ count: loadingCount,
710
+ columns,
711
+ gridTemplateColumns,
712
+ selection,
713
+ })
714
+ : rows.length > 0
715
+ ? rows.map((row, rowIndex) => {
716
+ const rowId = getRowId(row, rowIndex);
717
+ const rowContext: DataGridRowContext<Row> = {
718
+ ...slotContext,
719
+ row,
720
+ rowIndex,
721
+ rowId,
722
+ selected: resolvedSelectedRowIds.has(rowId),
723
+ };
724
+ if (renderRow) return renderRow(rowContext);
725
+ return (
726
+ <DataGridDefaultRow
727
+ key={rowId}
728
+ row={row}
729
+ rowIndex={rowIndex}
730
+ rowId={rowId}
731
+ columns={columns}
732
+ gridTemplateColumns={gridTemplateColumns}
733
+ selection={selection}
734
+ selected={rowContext.selected}
735
+ rowClassName={
736
+ typeof rowClassName === "function"
737
+ ? rowClassName(row, rowIndex)
738
+ : rowClassName
739
+ }
740
+ activeCell={resolvedActiveCell}
741
+ setActiveCell={setActive}
742
+ toggleRowSelection={toggleRowSelection}
743
+ moveActiveCell={moveActiveCell}
744
+ commitCell={commitCell}
745
+ />
746
+ );
747
+ })
748
+ : emptyState}
749
+ {renderFooter ? renderFooter(slotContext) : null}
750
+ </div>
751
+ </div>
752
+ </div>
753
+ );
754
+ }