@youp-grid/react 0.2.2 → 0.2.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Seungyoup Baek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -10,3 +10,18 @@ npm install @youp-grid/core @youp-grid/react
10
10
  import { YoupGrid } from "@youp-grid/react";
11
11
  import "@youp-grid/react/styles.css";
12
12
  ```
13
+
14
+ ## What It Provides
15
+
16
+ - virtualized React grid UI
17
+ - keyboard navigation and inline editing
18
+ - built-in text, number, checkbox, and select editors
19
+ - clipboard paste, fill handle, delete, undo, and redo write paths
20
+ - column menus, resizing, pinning, visibility, density, and row selection UI
21
+ - loading, empty, error, validation, pending, warning, and read-only states
22
+
23
+ The adapter emits changes through callbacks. Applications keep ownership of row data.
24
+
25
+ ## License
26
+
27
+ MIT. See the repository license.
package/dist/YoupGrid.js CHANGED
@@ -8,6 +8,7 @@ const DENSITY_ROW_HEIGHTS = {
8
8
  standard: 38,
9
9
  comfortable: 46,
10
10
  };
11
+ const ROW_NUMBER_COLUMN_WIDTH = 44;
11
12
  const SELECTION_COLUMN_WIDTH = 44;
12
13
  const AUTOSIZE_CELL_EXTRA_WIDTH = 8;
13
14
  const AUTOSIZE_CELL_BORDER_THRESHOLD = 6;
@@ -34,8 +35,13 @@ export function YoupGrid(props) {
34
35
  const [editingCell, setEditingCell] = useState();
35
36
  const [columnChooserOpen, setColumnChooserOpen] = useState(false);
36
37
  const [columnMenuOpenId, setColumnMenuOpenId] = useState();
38
+ const [cellContextMenu, setCellContextMenu] = useState();
39
+ const [activeTooltipCellKey, setActiveTooltipCellKey] = useState();
40
+ const showRowNumberColumn = props.showRowNumberColumn ?? false;
37
41
  const showRowSelectionColumn = props.showRowSelectionColumn ?? false;
38
42
  const pinRowSelectionColumn = props.pinRowSelectionColumn ?? false;
43
+ const showCellContextMenu = props.showCellContextMenu ?? false;
44
+ const cellTooltipMode = props.cellTooltip?.mode ?? "native";
39
45
  const gridEditable = (props.editable ?? true) && !props.readOnly;
40
46
  const displayRows = rowModel.displayRows;
41
47
  const virtualRange = useMemo(() => {
@@ -77,11 +83,14 @@ export function YoupGrid(props) {
77
83
  const selectedVisibleRowCount = visibleRowIds.filter((rowId) => selectedRowIds.has(rowId)).length;
78
84
  const allVisibleRowsSelected = visibleRowIds.length > 0 && selectedVisibleRowCount === visibleRowIds.length;
79
85
  const someVisibleRowsSelected = selectedVisibleRowCount > 0 && !allVisibleRowsSelected;
86
+ const rowNumberColumnOffset = showRowNumberColumn ? ROW_NUMBER_COLUMN_WIDTH : 0;
87
+ const selectionColumnOffset = showRowSelectionColumn && pinRowSelectionColumn ? rowNumberColumnOffset : 0;
88
+ const pinnedColumnOffset = rowNumberColumnOffset + (showRowSelectionColumn && pinRowSelectionColumn ? SELECTION_COLUMN_WIDTH : 0);
80
89
  const columnLayouts = useMemo(() => {
81
90
  return getColumnLayouts(rowModel.visibleColumns, {
82
- leftOffset: showRowSelectionColumn && pinRowSelectionColumn ? SELECTION_COLUMN_WIDTH : 0,
91
+ leftOffset: pinnedColumnOffset,
83
92
  });
84
- }, [pinRowSelectionColumn, rowModel.visibleColumns, showRowSelectionColumn]);
93
+ }, [pinnedColumnOffset, rowModel.visibleColumns]);
85
94
  const visibleColumns = useMemo(() => columnLayouts.map((layout) => layout.column), [columnLayouts]);
86
95
  const visibleRowIndexById = useMemo(() => {
87
96
  return new Map(rowModel.visibleRows.map((row, index) => [row.id, index]));
@@ -133,7 +142,7 @@ export function YoupGrid(props) {
133
142
  };
134
143
  const getGridCellMeta = (row, rowIndex, column) => {
135
144
  return (props.getCellMeta?.(getCellEditContext(row, rowIndex, column)) ??
136
- props.cellMeta?.[`${row.id}:${column.id}`]);
145
+ props.cellMeta?.[getCellKey(row.id, column.id)]);
137
146
  };
138
147
  const createGridCellValueChange = (cell, value) => {
139
148
  if (!cell.editable) {
@@ -167,6 +176,44 @@ export function YoupGrid(props) {
167
176
  setColumnMenuOpenId(undefined);
168
177
  }
169
178
  }, [columnMenuOpenId, visibleColumns]);
179
+ useEffect(() => {
180
+ if (!cellContextMenu) {
181
+ return;
182
+ }
183
+ const closeMenu = () => setCellContextMenu(undefined);
184
+ const handleKeyDown = (event) => {
185
+ if (event.key === "Escape") {
186
+ closeMenu();
187
+ }
188
+ };
189
+ document.addEventListener("click", closeMenu);
190
+ document.addEventListener("contextmenu", closeMenu);
191
+ document.addEventListener("keydown", handleKeyDown);
192
+ return () => {
193
+ document.removeEventListener("click", closeMenu);
194
+ document.removeEventListener("contextmenu", closeMenu);
195
+ document.removeEventListener("keydown", handleKeyDown);
196
+ };
197
+ }, [cellContextMenu]);
198
+ useEffect(() => {
199
+ if (cellTooltipMode !== "rich") {
200
+ setActiveTooltipCellKey(undefined);
201
+ return;
202
+ }
203
+ const cellKey = props.cellTooltip?.autoOpenCellKey;
204
+ if (!cellKey) {
205
+ return;
206
+ }
207
+ setActiveTooltipCellKey(cellKey);
208
+ const duration = props.cellTooltip?.autoOpenDurationMs ?? 2000;
209
+ if (duration <= 0) {
210
+ return;
211
+ }
212
+ const timeout = window.setTimeout(() => {
213
+ setActiveTooltipCellKey((current) => current === cellKey ? undefined : current);
214
+ }, duration);
215
+ return () => window.clearTimeout(timeout);
216
+ }, [cellTooltipMode, props.cellTooltip?.autoOpenCellKey, props.cellTooltip?.autoOpenDurationMs]);
170
217
  useEffect(() => {
171
218
  if (!props.infiniteScroll || !props.onRowsEndReached || !infiniteScrollTrigger.shouldLoadMore) {
172
219
  return;
@@ -312,11 +359,207 @@ export function YoupGrid(props) {
312
359
  const visibleRowIdSet = new Set(visibleRowIds);
313
360
  controller.setSelectedRows(currentSelectedRowIds.filter((rowId) => !visibleRowIdSet.has(rowId)));
314
361
  };
362
+ const openCellContextMenu = (event, cell) => {
363
+ if (!showCellContextMenu || editingCell) {
364
+ return;
365
+ }
366
+ event.preventDefault();
367
+ event.stopPropagation();
368
+ const insideSelection = selectionRange
369
+ ? isCellInRange(cell.rowIndex, cell.columnIndex, selectionRange)
370
+ : false;
371
+ if (!insideSelection) {
372
+ setSelectionRange(undefined);
373
+ }
374
+ setFocusedRowIndex(cell.rowIndex);
375
+ setFocusedColumnIndex(cell.columnIndex);
376
+ setCellContextMenu({
377
+ rowIndex: cell.rowIndex,
378
+ columnIndex: cell.columnIndex,
379
+ clientX: event.clientX,
380
+ clientY: event.clientY,
381
+ anchor: event.currentTarget,
382
+ });
383
+ };
384
+ const getCellContextMenuRange = () => {
385
+ if (!cellContextMenu || !selectionRange) {
386
+ return undefined;
387
+ }
388
+ return isCellInRange(cellContextMenu.rowIndex, cellContextMenu.columnIndex, selectionRange)
389
+ ? selectionRange
390
+ : undefined;
391
+ };
392
+ const copyCellContextMenuSelection = () => {
393
+ if (!cellContextMenu) {
394
+ return;
395
+ }
396
+ void writeClipboardText(getGridClipboardText({
397
+ focusedCell: {
398
+ rowIndex: cellContextMenu.rowIndex,
399
+ columnIndex: cellContextMenu.columnIndex,
400
+ },
401
+ selectionRange: getCellContextMenuRange(),
402
+ rows: rowModel.visibleRows,
403
+ columns: visibleColumns,
404
+ })).catch(() => undefined);
405
+ };
406
+ const pasteCellContextMenuSelection = () => {
407
+ if (!cellContextMenu || !gridEditable) {
408
+ return;
409
+ }
410
+ void readClipboardText()
411
+ .then((text) => {
412
+ applyGridClipboardText({
413
+ text,
414
+ focusedCell: {
415
+ rowIndex: cellContextMenu.rowIndex,
416
+ columnIndex: cellContextMenu.columnIndex,
417
+ },
418
+ selectionRange: getCellContextMenuRange(),
419
+ rows: rowModel.visibleRows,
420
+ columns: visibleColumns,
421
+ canEditCell: canEditGridCell,
422
+ applyCellValueChanges,
423
+ });
424
+ })
425
+ .catch(() => undefined);
426
+ };
427
+ const clearCellContextMenuSelection = () => {
428
+ if (!cellContextMenu || !gridEditable) {
429
+ return;
430
+ }
431
+ deleteGridCellValues({
432
+ focusedCell: {
433
+ rowIndex: cellContextMenu.rowIndex,
434
+ columnIndex: cellContextMenu.columnIndex,
435
+ },
436
+ selectionRange: getCellContextMenuRange(),
437
+ rows: rowModel.visibleRows,
438
+ columns: visibleColumns,
439
+ canEditCell: canEditGridCell,
440
+ applyCellValueChanges,
441
+ });
442
+ };
443
+ const selectCellContextMenuRow = () => {
444
+ if (!cellContextMenu) {
445
+ return;
446
+ }
447
+ const row = rowModel.visibleRows[cellContextMenu.rowIndex];
448
+ if (row) {
449
+ controller.setRowSelected(row.id, true);
450
+ }
451
+ };
452
+ const getCellContextMenuTargetRows = () => {
453
+ if (!cellContextMenu) {
454
+ return [];
455
+ }
456
+ const row = rowModel.visibleRows[cellContextMenu.rowIndex];
457
+ if (!row || isRowGroupNode(row)) {
458
+ return [];
459
+ }
460
+ if (selectedRowIds.has(row.id)) {
461
+ return rowModel.visibleRows.filter((visibleRow) => {
462
+ return !isRowGroupNode(visibleRow) && selectedRowIds.has(visibleRow.id);
463
+ });
464
+ }
465
+ return [row];
466
+ };
467
+ const getCellContextMenuDeleteRowCount = () => getCellContextMenuTargetRows().length;
468
+ const insertCellContextMenuRow = (position) => {
469
+ if (!cellContextMenu || !gridEditable || !props.createRow || !props.onRowsChange) {
470
+ return;
471
+ }
472
+ const anchorRow = rowModel.visibleRows[cellContextMenu.rowIndex];
473
+ if (!anchorRow || isRowGroupNode(anchorRow)) {
474
+ return;
475
+ }
476
+ const rowIndex = anchorRow.index + (position === "below" ? 1 : 0);
477
+ const visibleRowIndex = cellContextMenu.rowIndex + (position === "below" ? 1 : 0);
478
+ const row = props.createRow({
479
+ rows: props.rows,
480
+ rowIndex,
481
+ visibleRowIndex,
482
+ position,
483
+ anchorRow: anchorRow.original,
484
+ anchorRowId: anchorRow.id,
485
+ anchorRowIndex: anchorRow.index,
486
+ });
487
+ const rows = [
488
+ ...props.rows.slice(0, rowIndex),
489
+ row,
490
+ ...props.rows.slice(rowIndex),
491
+ ];
492
+ props.onRowsChange({
493
+ rows,
494
+ changes: [{
495
+ type: "insert",
496
+ row,
497
+ rowIndex,
498
+ visibleRowIndex,
499
+ position,
500
+ anchorRow: anchorRow.original,
501
+ anchorRowId: anchorRow.id,
502
+ anchorRowIndex: anchorRow.index,
503
+ }],
504
+ source: "context-menu",
505
+ });
506
+ setSelectionRange(undefined);
507
+ setFocusedRowIndex(visibleRowIndex);
508
+ };
509
+ const deleteCellContextMenuRows = () => {
510
+ if (!gridEditable || !props.onRowsChange) {
511
+ return;
512
+ }
513
+ const targetRows = getCellContextMenuTargetRows();
514
+ if (targetRows.length === 0) {
515
+ return;
516
+ }
517
+ const visibleRowIndexByRowId = new Map();
518
+ rowModel.visibleRows.forEach((row, index) => {
519
+ if (!isRowGroupNode(row)) {
520
+ visibleRowIndexByRowId.set(row.id, index);
521
+ }
522
+ });
523
+ const deleteRowIds = new Set(targetRows.map((row) => row.id));
524
+ const deleteRowIndexes = new Set(targetRows.map((row) => row.index));
525
+ const rows = props.rows.filter((_, index) => !deleteRowIndexes.has(index));
526
+ props.onRowsChange({
527
+ rows,
528
+ changes: targetRows
529
+ .slice()
530
+ .sort((left, right) => left.index - right.index)
531
+ .map((row) => ({
532
+ type: "delete",
533
+ row: row.original,
534
+ rowId: row.id,
535
+ rowIndex: row.index,
536
+ visibleRowIndex: visibleRowIndexByRowId.get(row.id) ?? row.index,
537
+ })),
538
+ source: "context-menu",
539
+ });
540
+ controller.setSelectedRows(currentSelectedRowIds.filter((rowId) => !deleteRowIds.has(rowId)));
541
+ setSelectionRange(undefined);
542
+ setFocusedRowIndex(Math.min(cellContextMenu?.rowIndex ?? 0, Math.max(0, rowModel.visibleRows.length - targetRows.length - 1)));
543
+ };
544
+ const autoSizeCellContextMenuColumn = () => {
545
+ if (!cellContextMenu) {
546
+ return;
547
+ }
548
+ const column = visibleColumns[cellContextMenu.columnIndex];
549
+ if (column) {
550
+ controller.setColumnWidth(column.id, getAutoSizeColumnWidth({
551
+ anchor: cellContextMenu.anchor,
552
+ column,
553
+ rows: rowModel.visibleRows,
554
+ }));
555
+ }
556
+ };
315
557
  return createElement("div", {
316
558
  className: [
317
559
  "youp-grid",
318
560
  `youp-grid--density-${density}`,
319
561
  !gridEditable ? "youp-grid--read-only" : "",
562
+ showRowNumberColumn ? "youp-grid--row-number-column" : "",
320
563
  pinRowSelectionColumn ? "youp-grid--selection-column-pinned" : "",
321
564
  props.className,
322
565
  ].filter(Boolean).join(" "),
@@ -349,7 +592,7 @@ export function YoupGrid(props) {
349
592
  className: "youp-grid__viewport",
350
593
  role: "grid",
351
594
  "aria-rowcount": displayRows.length,
352
- "aria-colcount": rowModel.visibleColumns.length + (showRowSelectionColumn ? 1 : 0),
595
+ "aria-colcount": rowModel.visibleColumns.length + (showRowNumberColumn ? 1 : 0) + (showRowSelectionColumn ? 1 : 0),
353
596
  "aria-busy": loading || undefined,
354
597
  "aria-readonly": !gridEditable || undefined,
355
598
  onCopy: (event) => {
@@ -379,14 +622,19 @@ export function YoupGrid(props) {
379
622
  });
380
623
  },
381
624
  }, createElement("div", { className: "youp-grid__header", role: "rowgroup", ref: headerRef }, hasHeaderGroups
382
- ? createElement("div", { className: "youp-grid__row youp-grid__row--header-group", role: "row" }, showRowSelectionColumn
383
- ? renderSelectionHeaderGroupCell()
625
+ ? createElement("div", { className: "youp-grid__row youp-grid__row--header-group", role: "row" }, showRowNumberColumn
626
+ ? renderRowNumberHeaderGroupCell()
627
+ : undefined, showRowSelectionColumn
628
+ ? renderSelectionHeaderGroupCell(selectionColumnOffset)
384
629
  : undefined, headerGroupLayouts.map((layout) => renderHeaderGroupCell(layout)))
385
- : undefined, createElement("div", { className: "youp-grid__row youp-grid__row--header", role: "row" }, showRowSelectionColumn
630
+ : undefined, createElement("div", { className: "youp-grid__row youp-grid__row--header", role: "row" }, showRowNumberColumn
631
+ ? renderRowNumberHeaderCell()
632
+ : undefined, showRowSelectionColumn
386
633
  ? renderSelectionHeaderCell({
387
634
  checked: allVisibleRowsSelected,
388
635
  indeterminate: someVisibleRowsSelected,
389
636
  disabled: visibleRowIds.length === 0,
637
+ leftOffset: selectionColumnOffset,
390
638
  toggleSelected: setVisibleRowsSelected,
391
639
  })
392
640
  : undefined, columnLayouts.map((layout) => {
@@ -452,7 +700,9 @@ export function YoupGrid(props) {
452
700
  return renderGroupRow({
453
701
  row,
454
702
  columns: columnLayouts,
703
+ showRowNumberColumn,
455
704
  showSelectionColumn: showRowSelectionColumn,
705
+ selectionColumnOffset,
456
706
  rowHeight,
457
707
  toggleExpanded: controller.toggleRowGroupExpanded,
458
708
  });
@@ -462,7 +712,9 @@ export function YoupGrid(props) {
462
712
  row,
463
713
  columns: columnLayouts,
464
714
  selected: selectedRowIds.has(row.id),
715
+ showRowNumberColumn,
465
716
  showSelectionColumn: showRowSelectionColumn,
717
+ selectionColumnOffset,
466
718
  displayIndex,
467
719
  rowIndex,
468
720
  rowHeight,
@@ -475,10 +727,12 @@ export function YoupGrid(props) {
475
727
  editingCell,
476
728
  editable: gridEditable,
477
729
  disabledReason: props.disabledReason,
730
+ treeData: props.treeData ?? false,
478
731
  canEditCell: canEditGridCell,
479
732
  getCellMeta: getGridCellMeta,
480
733
  setRowSelected: (selected) => controller.setRowSelected(row.id, selected),
481
734
  setFocusedCell,
735
+ toggleTreeRowExpanded: controller.toggleTreeRowExpanded,
482
736
  startFillHandle: (event) => {
483
737
  if (!gridEditable) {
484
738
  return;
@@ -524,6 +778,13 @@ export function YoupGrid(props) {
524
778
  },
525
779
  cancelEditing: cancelEditingCell,
526
780
  commitEditing: commitEditingValue,
781
+ cellTooltipMode,
782
+ activeTooltipCellKey,
783
+ openCellTooltip: setActiveTooltipCellKey,
784
+ closeCellTooltip: (cellKey) => {
785
+ setActiveTooltipCellKey((current) => current === cellKey ? undefined : current);
786
+ },
787
+ openCellContextMenu: showCellContextMenu ? openCellContextMenu : undefined,
527
788
  onRowClick: props.onRowClick,
528
789
  onRowDoubleClick: props.onRowDoubleClick,
529
790
  onCellKeyDown: (event, cell) => {
@@ -567,8 +828,27 @@ export function YoupGrid(props) {
567
828
  enabled: showAggregationFooter,
568
829
  aggregation: rowModel.aggregation,
569
830
  columns: columnLayouts,
831
+ showRowNumberColumn,
570
832
  showSelectionColumn: showRowSelectionColumn,
571
- })), renderPagination({
833
+ selectionColumnOffset,
834
+ })), renderCellContextMenu({
835
+ state: cellContextMenu,
836
+ editable: gridEditable,
837
+ hasSelectedRows: selectedRowIds.size > 0,
838
+ canInsertRows: gridEditable && Boolean(props.createRow && props.onRowsChange),
839
+ canDeleteRows: gridEditable && Boolean(props.onRowsChange && getCellContextMenuDeleteRowCount() > 0),
840
+ deleteRowLabel: getCellContextMenuDeleteRowCount() > 1 ? "Delete selected rows" : "Delete row",
841
+ copy: copyCellContextMenuSelection,
842
+ paste: pasteCellContextMenuSelection,
843
+ clearContents: clearCellContextMenuSelection,
844
+ selectRow: selectCellContextMenuRow,
845
+ clearRowSelection: () => controller.setSelectedRows([]),
846
+ insertRowAbove: () => insertCellContextMenuRow("above"),
847
+ insertRowBelow: () => insertCellContextMenuRow("below"),
848
+ deleteRows: deleteCellContextMenuRows,
849
+ autoSizeColumn: autoSizeCellContextMenuColumn,
850
+ closeMenu: () => setCellContextMenu(undefined),
851
+ }), renderPagination({
572
852
  enabled: props.showPagination ?? true,
573
853
  cursorPagination: controller.state.cursorPagination,
574
854
  pageCount: rowModel.pageCount,
@@ -601,7 +881,7 @@ function renderAggregationFooter(context) {
601
881
  return undefined;
602
882
  }
603
883
  const aggregationByColumn = groupAggregationByColumn(context.aggregation);
604
- return createElement("div", { className: "youp-grid__aggregation", role: "rowgroup" }, createElement("div", { className: "youp-grid__row youp-grid__row--aggregation", role: "row" }, context.showSelectionColumn ? renderSelectionAggregationCell() : undefined, context.columns.map((layout) => {
884
+ return createElement("div", { className: "youp-grid__aggregation", role: "rowgroup" }, createElement("div", { className: "youp-grid__row youp-grid__row--aggregation", role: "row" }, context.showRowNumberColumn ? renderRowNumberAggregationCell() : undefined, context.showSelectionColumn ? renderSelectionAggregationCell(context.selectionColumnOffset) : undefined, context.columns.map((layout) => {
605
885
  const results = aggregationByColumn.get(layout.column.id) ?? [];
606
886
  return createElement("div", {
607
887
  key: layout.column.id,
@@ -611,11 +891,19 @@ function renderAggregationFooter(context) {
611
891
  }, results.map(formatAggregationResult).join(" · "));
612
892
  })));
613
893
  }
614
- function renderSelectionAggregationCell() {
894
+ function renderRowNumberAggregationCell() {
895
+ return createElement("div", {
896
+ className: "youp-grid__cell youp-grid__row-number-cell youp-grid__row-number-cell--aggregation",
897
+ role: "gridcell",
898
+ style: getRowNumberCellStyle(),
899
+ "aria-hidden": true,
900
+ });
901
+ }
902
+ function renderSelectionAggregationCell(leftOffset) {
615
903
  return createElement("div", {
616
904
  className: "youp-grid__selection-cell youp-grid__selection-cell--aggregation",
617
905
  role: "gridcell",
618
- style: getSelectionCellStyle(),
906
+ style: getSelectionCellStyle(leftOffset),
619
907
  "aria-hidden": true,
620
908
  });
621
909
  }
@@ -650,13 +938,31 @@ function renderHeaderGroupCell(layout) {
650
938
  style: getHeaderGroupStyle(layout),
651
939
  }, layout.headerGroup ?? "");
652
940
  }
653
- function renderSelectionHeaderGroupCell() {
941
+ function renderRowNumberHeaderGroupCell() {
942
+ return createElement("div", {
943
+ key: "__row-number-group",
944
+ className: "youp-grid__cell youp-grid__cell--header-group youp-grid__row-number-cell youp-grid__row-number-cell--header-group",
945
+ role: "columnheader",
946
+ "aria-hidden": true,
947
+ style: getRowNumberCellStyle(),
948
+ });
949
+ }
950
+ function renderRowNumberHeaderCell() {
951
+ return createElement("div", {
952
+ key: "__row-number",
953
+ className: "youp-grid__cell youp-grid__cell--header youp-grid__row-number-cell youp-grid__row-number-cell--header",
954
+ role: "columnheader",
955
+ "aria-label": "Row number",
956
+ style: getRowNumberCellStyle(),
957
+ }, "#");
958
+ }
959
+ function renderSelectionHeaderGroupCell(leftOffset) {
654
960
  return createElement("div", {
655
961
  key: "__selection-group",
656
962
  className: "youp-grid__cell youp-grid__cell--header-group youp-grid__selection-cell youp-grid__selection-cell--header-group",
657
963
  role: "columnheader",
658
964
  "aria-hidden": true,
659
- style: getSelectionCellStyle(),
965
+ style: getSelectionCellStyle(leftOffset),
660
966
  });
661
967
  }
662
968
  function renderSelectionHeaderCell(context) {
@@ -664,7 +970,7 @@ function renderSelectionHeaderCell(context) {
664
970
  key: "__selection",
665
971
  className: "youp-grid__cell youp-grid__cell--header youp-grid__selection-cell youp-grid__selection-cell--header",
666
972
  role: "columnheader",
667
- style: getSelectionCellStyle(),
973
+ style: getSelectionCellStyle(context.leftOffset),
668
974
  }, createElement(SelectionHeaderCheckbox, context));
669
975
  }
670
976
  function SelectionHeaderCheckbox(context) {
@@ -811,6 +1117,63 @@ function renderColumnMenuButton(context) {
811
1117
  },
812
1118
  }, context.label);
813
1119
  }
1120
+ function renderCellContextMenu(context) {
1121
+ if (!context.state) {
1122
+ return undefined;
1123
+ }
1124
+ const runAction = (action) => {
1125
+ action();
1126
+ context.closeMenu();
1127
+ };
1128
+ return createElement("div", {
1129
+ className: "youp-grid__cell-context-menu",
1130
+ role: "menu",
1131
+ style: {
1132
+ left: context.state.clientX,
1133
+ top: context.state.clientY,
1134
+ },
1135
+ onClick: (event) => {
1136
+ event.stopPropagation();
1137
+ },
1138
+ onContextMenu: (event) => {
1139
+ event.preventDefault();
1140
+ event.stopPropagation();
1141
+ },
1142
+ }, renderColumnMenuButton({
1143
+ label: "Copy",
1144
+ onClick: () => runAction(context.copy),
1145
+ }), renderColumnMenuButton({
1146
+ label: "Paste",
1147
+ disabled: !context.editable,
1148
+ onClick: () => runAction(context.paste),
1149
+ }), renderColumnMenuButton({
1150
+ label: "Clear contents",
1151
+ disabled: !context.editable,
1152
+ onClick: () => runAction(context.clearContents),
1153
+ }), createElement("div", { className: "youp-grid__column-menu-separator", role: "separator" }), renderColumnMenuButton({
1154
+ label: "Select row",
1155
+ onClick: () => runAction(context.selectRow),
1156
+ }), renderColumnMenuButton({
1157
+ label: "Clear row selection",
1158
+ disabled: !context.hasSelectedRows,
1159
+ onClick: () => runAction(context.clearRowSelection),
1160
+ }), createElement("div", { className: "youp-grid__column-menu-separator", role: "separator" }), renderColumnMenuButton({
1161
+ label: "Insert row above",
1162
+ disabled: !context.canInsertRows,
1163
+ onClick: () => runAction(context.insertRowAbove),
1164
+ }), renderColumnMenuButton({
1165
+ label: "Insert row below",
1166
+ disabled: !context.canInsertRows,
1167
+ onClick: () => runAction(context.insertRowBelow),
1168
+ }), renderColumnMenuButton({
1169
+ label: context.deleteRowLabel,
1170
+ disabled: !context.canDeleteRows,
1171
+ onClick: () => runAction(context.deleteRows),
1172
+ }), createElement("div", { className: "youp-grid__column-menu-separator", role: "separator" }), renderColumnMenuButton({
1173
+ label: "Auto-size column",
1174
+ onClick: () => runAction(context.autoSizeColumn),
1175
+ }));
1176
+ }
814
1177
  function renderGroupRow(context) {
815
1178
  const width = getColumnsWidth(context.columns);
816
1179
  return createElement("div", {
@@ -819,7 +1182,7 @@ function renderGroupRow(context) {
819
1182
  role: "row",
820
1183
  "aria-rowindex": context.row.index + 1,
821
1184
  style: { height: context.rowHeight },
822
- }, context.showSelectionColumn ? renderSelectionGroupCell() : undefined, createElement("div", {
1185
+ }, context.showRowNumberColumn ? renderRowNumberGroupCell() : undefined, context.showSelectionColumn ? renderSelectionGroupCell(context.selectionColumnOffset) : undefined, createElement("div", {
823
1186
  className: "youp-grid__cell youp-grid__cell--group",
824
1187
  role: "gridcell",
825
1188
  "aria-colspan": context.columns.length,
@@ -835,11 +1198,19 @@ function renderGroupRow(context) {
835
1198
  onClick: () => context.toggleExpanded(context.row.groupId),
836
1199
  }, createElement("span", { className: "youp-grid__group-caret", "aria-hidden": true }, context.row.expanded ? "v" : ">"), createElement("span", { className: "youp-grid__group-label" }, context.row.label), createElement("span", { className: "youp-grid__group-count" }, `${context.row.rowCount} rows`))));
837
1200
  }
838
- function renderSelectionGroupCell() {
1201
+ function renderRowNumberGroupCell() {
1202
+ return createElement("div", {
1203
+ className: "youp-grid__cell youp-grid__row-number-cell youp-grid__row-number-cell--group",
1204
+ role: "gridcell",
1205
+ style: getRowNumberCellStyle(),
1206
+ "aria-hidden": true,
1207
+ });
1208
+ }
1209
+ function renderSelectionGroupCell(leftOffset) {
839
1210
  return createElement("div", {
840
1211
  className: "youp-grid__selection-cell youp-grid__selection-cell--group",
841
1212
  role: "gridcell",
842
- style: getSelectionCellStyle(),
1213
+ style: getSelectionCellStyle(leftOffset),
843
1214
  "aria-hidden": true,
844
1215
  });
845
1216
  }
@@ -867,11 +1238,18 @@ function renderRow(context) {
867
1238
  context.onRowDoubleClick?.(createRowEvent(context, event));
868
1239
  }
869
1240
  },
870
- }, context.showSelectionColumn
1241
+ }, context.showRowNumberColumn
1242
+ ? renderRowNumberCell({
1243
+ rowIndex: context.rowIndex,
1244
+ displayIndex: context.displayIndex,
1245
+ })
1246
+ : undefined, context.showSelectionColumn
871
1247
  ? renderSelectionCell({
872
1248
  rowIndex: context.rowIndex,
873
1249
  selected: context.selected,
874
1250
  setSelected: context.setRowSelected,
1251
+ leftOffset: context.selectionColumnOffset,
1252
+ ariaColIndex: context.showRowNumberColumn ? 2 : 1,
875
1253
  })
876
1254
  : undefined, context.columns.map((layout, columnIndex) => {
877
1255
  const focused = context.focusedCell.rowIndex === context.rowIndex && context.focusedCell.columnIndex === columnIndex;
@@ -897,7 +1275,7 @@ function renderRow(context) {
897
1275
  layout,
898
1276
  rowIndex: context.rowIndex,
899
1277
  columnIndex,
900
- ariaColumnOffset: context.showSelectionColumn ? 1 : 0,
1278
+ ariaColumnOffset: (context.showRowNumberColumn ? 1 : 0) + (context.showSelectionColumn ? 1 : 0),
901
1279
  focused,
902
1280
  selected,
903
1281
  fillTargeted,
@@ -906,8 +1284,10 @@ function renderRow(context) {
906
1284
  editingCell: context.editingCell,
907
1285
  editable,
908
1286
  disabledReason: context.disabledReason,
1287
+ treeData: context.treeData,
909
1288
  meta,
910
1289
  setFocusedCell: context.setFocusedCell,
1290
+ toggleTreeRowExpanded: context.toggleTreeRowExpanded,
911
1291
  startFillHandle: context.startFillHandle,
912
1292
  autoSizeColumn: context.autoSizeColumn,
913
1293
  applyCellValue: context.applyCellValue,
@@ -916,16 +1296,30 @@ function renderRow(context) {
916
1296
  cancelEditing: context.cancelEditing,
917
1297
  commitEditing: context.commitEditing,
918
1298
  onKeyDown: context.onCellKeyDown,
1299
+ openContextMenu: context.openCellContextMenu,
1300
+ cellTooltipMode: context.cellTooltipMode,
1301
+ activeTooltipCellKey: context.activeTooltipCellKey,
1302
+ openCellTooltip: context.openCellTooltip,
1303
+ closeCellTooltip: context.closeCellTooltip,
919
1304
  renderCell: context.renderCell,
920
1305
  });
921
1306
  }));
922
1307
  }
1308
+ function renderRowNumberCell(context) {
1309
+ return createElement("div", {
1310
+ className: "youp-grid__cell youp-grid__row-number-cell",
1311
+ role: "gridcell",
1312
+ "aria-colindex": 1,
1313
+ "aria-label": `Row ${context.rowIndex + 1}`,
1314
+ style: getRowNumberCellStyle(),
1315
+ }, context.displayIndex + 1);
1316
+ }
923
1317
  function renderSelectionCell(context) {
924
1318
  return createElement("div", {
925
1319
  className: "youp-grid__cell youp-grid__selection-cell",
926
1320
  role: "gridcell",
927
- "aria-colindex": 1,
928
- style: getSelectionCellStyle(),
1321
+ "aria-colindex": context.ariaColIndex,
1322
+ style: getSelectionCellStyle(context.leftOffset),
929
1323
  }, createElement("input", {
930
1324
  className: "youp-grid__selection-checkbox",
931
1325
  type: "checkbox",
@@ -960,6 +1354,12 @@ function renderCell(context) {
960
1354
  const value = column.accessor(context.row.original);
961
1355
  const editable = context.editable;
962
1356
  const cellAlign = getColumnAlign(column);
1357
+ const cellKey = getCellKey(context.row.id, column.id);
1358
+ const showTreePrefix = context.treeData && context.columnIndex === 0;
1359
+ const hasRichTooltip = context.cellTooltipMode === "rich" && hasTooltipMessage(context.meta);
1360
+ const tooltipId = hasRichTooltip && context.activeTooltipCellKey === cellKey
1361
+ ? getCellTooltipId(cellKey)
1362
+ : undefined;
963
1363
  const cellContext = {
964
1364
  row: context.row,
965
1365
  column,
@@ -968,6 +1368,9 @@ function renderCell(context) {
968
1368
  focused: context.focused,
969
1369
  editable,
970
1370
  meta: context.meta,
1371
+ treeDepth: context.row.depth,
1372
+ hasChildren: context.row.hasChildren,
1373
+ expanded: context.row.expanded,
971
1374
  };
972
1375
  const cellState = {
973
1376
  row: context.row,
@@ -984,8 +1387,16 @@ function renderCell(context) {
984
1387
  cell: cellState,
985
1388
  row: context.row.original,
986
1389
  disabledReason: context.disabledReason,
1390
+ cellTooltipMode: context.cellTooltipMode,
987
1391
  applyCellValue: context.applyCellValue,
988
1392
  });
1393
+ const renderedContent = showTreePrefix && !context.editing
1394
+ ? renderTreeCellContent({
1395
+ row: context.row,
1396
+ content: cellContent,
1397
+ toggleExpanded: context.toggleTreeRowExpanded,
1398
+ })
1399
+ : cellContent;
989
1400
  return createElement("div", {
990
1401
  key: column.id,
991
1402
  className: getCellClassName([
@@ -996,11 +1407,14 @@ function renderCell(context) {
996
1407
  context.fillTargeted ? "youp-grid__cell--fill-target" : "",
997
1408
  context.editing ? "youp-grid__cell--editing" : "",
998
1409
  !editable ? "youp-grid__cell--disabled" : "",
1410
+ showTreePrefix ? "youp-grid__cell--tree" : "",
1411
+ tooltipId ? "youp-grid__cell--tooltip-open" : "",
999
1412
  context.meta ? `youp-grid__cell--status-${context.meta.status}` : "",
1000
1413
  ].filter(Boolean).join(" "), context.layout),
1001
1414
  role: "gridcell",
1002
1415
  tabIndex: context.focused && !context.editing ? 0 : -1,
1003
- title: getCellTitle(context.meta, context.disabledReason, editable),
1416
+ title: getCellTitle(context.meta, context.disabledReason, editable, context.cellTooltipMode),
1417
+ "aria-describedby": tooltipId,
1004
1418
  "aria-colindex": context.columnIndex + context.ariaColumnOffset + 1,
1005
1419
  "aria-readonly": !editable || undefined,
1006
1420
  "data-youp-row-index": context.rowIndex,
@@ -1026,6 +1440,34 @@ function renderCell(context) {
1026
1440
  }
1027
1441
  },
1028
1442
  onKeyDown: (event) => context.onKeyDown(event, cellState),
1443
+ onContextMenu: context.openContextMenu
1444
+ ? (event) => context.openContextMenu?.(event, cellState)
1445
+ : undefined,
1446
+ onMouseEnter: () => {
1447
+ if (hasRichTooltip) {
1448
+ context.openCellTooltip(cellKey);
1449
+ }
1450
+ },
1451
+ onMouseLeave: () => {
1452
+ if (hasRichTooltip) {
1453
+ context.closeCellTooltip(cellKey);
1454
+ }
1455
+ },
1456
+ onFocus: () => {
1457
+ if (hasRichTooltip) {
1458
+ context.openCellTooltip(cellKey);
1459
+ }
1460
+ },
1461
+ onBlur: (event) => {
1462
+ if (!hasRichTooltip) {
1463
+ return;
1464
+ }
1465
+ const nextTarget = event.relatedTarget;
1466
+ if (nextTarget && event.currentTarget.contains(nextTarget)) {
1467
+ return;
1468
+ }
1469
+ context.closeCellTooltip(cellKey);
1470
+ },
1029
1471
  }, context.editing
1030
1472
  ? renderCellEditor({
1031
1473
  cell: cellState,
@@ -1034,7 +1476,7 @@ function renderCell(context) {
1034
1476
  commitEditing: context.commitEditing,
1035
1477
  onKeyDown: context.onKeyDown,
1036
1478
  })
1037
- : cellContent, renderCellStatus(context.meta), !context.editing && context.showFillHandle
1479
+ : renderedContent, renderCellStatus(context.meta, context.cellTooltipMode), renderCellTooltip(context.meta, tooltipId), !context.editing && context.showFillHandle
1038
1480
  ? createElement("span", {
1039
1481
  className: "youp-grid__fill-handle",
1040
1482
  role: "button",
@@ -1062,7 +1504,7 @@ function renderDefaultCellContent(context) {
1062
1504
  type: "checkbox",
1063
1505
  checked: Boolean(context.cell.value),
1064
1506
  disabled: !context.cell.editable,
1065
- title: getCellTitle(context.cell.meta, context.disabledReason, context.cell.editable),
1507
+ title: getCellTitle(context.cell.meta, context.disabledReason, context.cell.editable, context.cellTooltipMode),
1066
1508
  "aria-label": context.cell.column.headerName,
1067
1509
  onChange: (event) => {
1068
1510
  context.applyCellValue(context.cell, event.currentTarget.checked);
@@ -1078,7 +1520,37 @@ function renderDefaultCellContent(context) {
1078
1520
  const text = formatCellValue(context.cell.column, context.row, context.cell.value);
1079
1521
  const placeholder = context.cell.column.placeholder;
1080
1522
  const hasPlaceholder = text.length === 0 && Boolean(placeholder);
1081
- return createElement("span", { className: hasPlaceholder ? "youp-grid__cell-placeholder" : undefined }, hasPlaceholder ? placeholder : text);
1523
+ return createElement("span", {
1524
+ className: [
1525
+ "youp-grid__cell-text",
1526
+ hasPlaceholder ? "youp-grid__cell-placeholder" : "",
1527
+ ].filter(Boolean).join(" "),
1528
+ }, hasPlaceholder ? placeholder : text);
1529
+ }
1530
+ function renderTreeCellContent(context) {
1531
+ const depth = Math.max(0, context.row.depth ?? 0);
1532
+ const toggle = context.row.hasChildren
1533
+ ? createElement("button", {
1534
+ className: "youp-grid__tree-toggle",
1535
+ type: "button",
1536
+ "aria-label": context.row.expanded ? "Collapse row" : "Expand row",
1537
+ "aria-expanded": context.row.expanded,
1538
+ onClick: (event) => {
1539
+ event.stopPropagation();
1540
+ context.toggleExpanded(context.row.id);
1541
+ },
1542
+ onDoubleClick: (event) => {
1543
+ event.stopPropagation();
1544
+ },
1545
+ onKeyDown: (event) => {
1546
+ event.stopPropagation();
1547
+ },
1548
+ }, createElement("span", { className: "youp-grid__tree-caret", "aria-hidden": true }, context.row.expanded ? "v" : ">"))
1549
+ : createElement("span", { className: "youp-grid__tree-toggle-spacer", "aria-hidden": true });
1550
+ return createElement("span", {
1551
+ className: "youp-grid__cell-tree-content",
1552
+ style: { paddingLeft: depth * 18 },
1553
+ }, toggle, createElement("span", { className: "youp-grid__cell-tree-value" }, context.content));
1082
1554
  }
1083
1555
  function renderCellEditor(context) {
1084
1556
  if (context.cell.column.editor === "select") {
@@ -1121,16 +1593,26 @@ function renderCellEditor(context) {
1121
1593
  },
1122
1594
  });
1123
1595
  }
1124
- function renderCellStatus(meta) {
1596
+ function renderCellStatus(meta, tooltipMode) {
1125
1597
  if (!meta) {
1126
1598
  return undefined;
1127
1599
  }
1128
1600
  return createElement("span", {
1129
1601
  className: `youp-grid__cell-status youp-grid__cell-status--${meta.status}`,
1130
- title: typeof meta.message === "string" ? meta.message : undefined,
1602
+ title: tooltipMode === "native" && typeof meta.message === "string" ? meta.message : undefined,
1131
1603
  "aria-hidden": true,
1132
1604
  });
1133
1605
  }
1606
+ function renderCellTooltip(meta, tooltipId) {
1607
+ if (!tooltipId || !meta?.message) {
1608
+ return undefined;
1609
+ }
1610
+ return createElement("span", {
1611
+ id: tooltipId,
1612
+ className: "youp-grid__cell-tooltip",
1613
+ role: "tooltip",
1614
+ }, meta.message);
1615
+ }
1134
1616
  function renderColumnToolbar(context) {
1135
1617
  if (!context.showColumnChooser && !context.showCsvExport && !context.showDensityControl) {
1136
1618
  return undefined;
@@ -1300,9 +1782,16 @@ function getHeaderGroupStyle(layout) {
1300
1782
  }
1301
1783
  return style;
1302
1784
  }
1303
- function getSelectionCellStyle() {
1785
+ function getRowNumberCellStyle() {
1304
1786
  return {
1305
1787
  left: 0,
1788
+ width: ROW_NUMBER_COLUMN_WIDTH,
1789
+ flex: `0 0 ${ROW_NUMBER_COLUMN_WIDTH}px`,
1790
+ };
1791
+ }
1792
+ function getSelectionCellStyle(leftOffset = 0) {
1793
+ return {
1794
+ left: leftOffset,
1306
1795
  width: SELECTION_COLUMN_WIDTH,
1307
1796
  flex: `0 0 ${SELECTION_COLUMN_WIDTH}px`,
1308
1797
  };
@@ -1740,22 +2229,38 @@ function commitEditingCell(context) {
1740
2229
  };
1741
2230
  }
1742
2231
  function handleGridCopy(context) {
2232
+ context.event.preventDefault();
2233
+ context.event.clipboardData.setData("text/plain", getGridClipboardText(context));
2234
+ }
2235
+ function getGridClipboardText(context) {
1743
2236
  const range = context.selectionRange ?? {
1744
2237
  anchor: context.focusedCell,
1745
2238
  focus: context.focusedCell,
1746
2239
  };
1747
- const text = serializeGridRange({
2240
+ return serializeGridRange({
1748
2241
  rows: context.rows,
1749
2242
  columns: context.columns,
1750
2243
  range,
1751
2244
  });
1752
- context.event.preventDefault();
1753
- context.event.clipboardData.setData("text/plain", text);
1754
2245
  }
1755
2246
  function handleGridPaste(context) {
1756
- const values = parseClipboardText(context.event.clipboardData.getData("text/plain"));
2247
+ const applied = applyGridClipboardText({
2248
+ text: context.event.clipboardData.getData("text/plain"),
2249
+ focusedCell: context.focusedCell,
2250
+ selectionRange: context.selectionRange,
2251
+ rows: context.rows,
2252
+ columns: context.columns,
2253
+ applyCellValueChanges: context.applyCellValueChanges,
2254
+ canEditCell: context.canEditCell,
2255
+ });
2256
+ if (applied) {
2257
+ context.event.preventDefault();
2258
+ }
2259
+ }
2260
+ function applyGridClipboardText(context) {
2261
+ const values = parseClipboardText(context.text);
1757
2262
  if (values.length === 0) {
1758
- return;
2263
+ return false;
1759
2264
  }
1760
2265
  const normalizedRange = context.selectionRange ? normalizeCellRange(context.selectionRange) : undefined;
1761
2266
  const startCell = normalizedRange
@@ -1767,7 +2272,6 @@ function handleGridPaste(context) {
1767
2272
  const fillRange = normalizedRange && values.length === 1 && values[0]?.length === 1
1768
2273
  ? normalizedRange
1769
2274
  : undefined;
1770
- context.event.preventDefault();
1771
2275
  const changes = [];
1772
2276
  for (const cell of getClipboardPasteCells({
1773
2277
  values,
@@ -1797,6 +2301,19 @@ function handleGridPaste(context) {
1797
2301
  });
1798
2302
  }
1799
2303
  context.applyCellValueChanges(changes, "paste");
2304
+ return true;
2305
+ }
2306
+ function writeClipboardText(text) {
2307
+ if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
2308
+ return Promise.resolve();
2309
+ }
2310
+ return navigator.clipboard.writeText(text);
2311
+ }
2312
+ function readClipboardText() {
2313
+ if (typeof navigator === "undefined" || !navigator.clipboard?.readText) {
2314
+ return Promise.resolve("");
2315
+ }
2316
+ return navigator.clipboard.readText();
1800
2317
  }
1801
2318
  function deleteGridCellValues(context) {
1802
2319
  const range = normalizeCellRange(context.selectionRange ?? {
@@ -2020,8 +2537,8 @@ function normalizeEditorOptions(options) {
2020
2537
  function getEditorOptionValue(option) {
2021
2538
  return typeof option === "object" ? option.value : option;
2022
2539
  }
2023
- function getCellTitle(meta, disabledReason, editable) {
2024
- if (typeof meta?.message === "string") {
2540
+ function getCellTitle(meta, disabledReason, editable, tooltipMode) {
2541
+ if (tooltipMode === "native" && typeof meta?.message === "string") {
2025
2542
  return meta.message;
2026
2543
  }
2027
2544
  if (!editable && typeof disabledReason === "string") {
@@ -2029,6 +2546,15 @@ function getCellTitle(meta, disabledReason, editable) {
2029
2546
  }
2030
2547
  return undefined;
2031
2548
  }
2549
+ function hasTooltipMessage(meta) {
2550
+ return Boolean(meta?.message);
2551
+ }
2552
+ function getCellKey(rowId, columnId) {
2553
+ return `${String(rowId)}:${columnId}`;
2554
+ }
2555
+ function getCellTooltipId(cellKey) {
2556
+ return `youp-grid-cell-tooltip-${cellKey.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
2557
+ }
2032
2558
  function isTextEditingKey(event) {
2033
2559
  if (event.metaKey || event.ctrlKey || event.altKey) {
2034
2560
  return false;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { YoupGrid } from "./YoupGrid.ts";
2
2
  export { useYoupGrid } from "./useYoupGrid.ts";
3
- export type { YoupGridCanEditCellContext, YoupGridCellEditCommit, YoupGridCellEditCommitReason, YoupGridCellContext, YoupGridCellMeta, YoupGridCellMetaStatus, YoupGridCellsValueChange, YoupGridCellsValueChangeSource, YoupGridController, YoupGridDensity, YoupGridHeaderContext, YoupGridOptions, YoupGridProps, YoupGridRowEvent, YoupGridRowsEndReachedEvent, YoupGridStateChange, } from "./types.ts";
3
+ export type { YoupGridCanEditCellContext, YoupGridCellEditCommit, YoupGridCellEditCommitReason, YoupGridCellContext, YoupGridCellMeta, YoupGridCellMetaStatus, YoupGridCellTooltipMode, YoupGridCellTooltipOptions, YoupGridCellsValueChange, YoupGridCellsValueChangeSource, YoupGridController, YoupGridCreateRowContext, YoupGridDensity, YoupGridHeaderContext, YoupGridOptions, YoupGridProps, YoupGridRowChange, YoupGridRowDeleteChange, YoupGridRowEvent, YoupGridRowInsertChange, YoupGridRowInsertPosition, YoupGridRowsChange, YoupGridRowsEndReachedEvent, YoupGridRowsChangeSource, YoupGridStateChange, } from "./types.ts";
package/dist/styles.css CHANGED
@@ -164,8 +164,8 @@
164
164
  --youp-grid-header-scroll-left: 0px;
165
165
  }
166
166
 
167
- .youp-grid__header .youp-grid__cell--header:not(.youp-grid__cell--pinned-left):not(.youp-grid__cell--pinned-right),
168
- .youp-grid__header .youp-grid__cell--header-group:not(.youp-grid__cell--pinned-left):not(.youp-grid__cell--pinned-right) {
167
+ .youp-grid__header .youp-grid__cell--header:not(.youp-grid__row-number-cell):not(.youp-grid__cell--pinned-left):not(.youp-grid__cell--pinned-right),
168
+ .youp-grid__header .youp-grid__cell--header-group:not(.youp-grid__row-number-cell):not(.youp-grid__cell--pinned-left):not(.youp-grid__cell--pinned-right) {
169
169
  transform: translateX(calc(-1 * var(--youp-grid-header-scroll-left)));
170
170
  }
171
171
 
@@ -215,6 +215,63 @@
215
215
  background: var(--youp-grid-row-selected);
216
216
  }
217
217
 
218
+ .youp-grid__row-number-cell {
219
+ display: grid;
220
+ place-items: center;
221
+ min-width: 44px;
222
+ padding: 0;
223
+ color: var(--youp-grid-muted);
224
+ font-weight: 800;
225
+ text-align: center;
226
+ user-select: none;
227
+ background: #fff;
228
+ position: sticky;
229
+ left: 0;
230
+ z-index: 3;
231
+ }
232
+
233
+ .youp-grid__cell.youp-grid__row-number-cell {
234
+ min-width: 44px;
235
+ }
236
+
237
+ .youp-grid__row-number-cell--header,
238
+ .youp-grid__row-number-cell--header-group {
239
+ z-index: 4;
240
+ background: var(--youp-grid-header);
241
+ }
242
+
243
+ .youp-grid__row-number-cell--header {
244
+ align-content: center;
245
+ overflow: hidden;
246
+ }
247
+
248
+ .youp-grid__row-number-cell--header-group {
249
+ display: grid;
250
+ padding: 0;
251
+ }
252
+
253
+ .youp-grid__row--selected .youp-grid__row-number-cell {
254
+ background: var(--youp-grid-row-selected);
255
+ }
256
+
257
+ .youp-grid__row:hover .youp-grid__row-number-cell {
258
+ background: var(--youp-grid-row-hover);
259
+ }
260
+
261
+ .youp-grid__row--selected:hover .youp-grid__row-number-cell {
262
+ background: var(--youp-grid-row-selected);
263
+ }
264
+
265
+ .youp-grid__row--header:hover .youp-grid__row-number-cell,
266
+ .youp-grid__row--header-group:hover .youp-grid__row-number-cell {
267
+ background: var(--youp-grid-header);
268
+ }
269
+
270
+ .youp-grid__row--aggregation:hover .youp-grid__row-number-cell,
271
+ .youp-grid__row--group:hover .youp-grid__row-number-cell {
272
+ background: #f8fafc;
273
+ }
274
+
218
275
  .youp-grid__selection-cell {
219
276
  display: grid;
220
277
  place-items: center;
@@ -355,6 +412,89 @@
355
412
  color: #94a3b8;
356
413
  }
357
414
 
415
+ .youp-grid__cell-text {
416
+ display: block;
417
+ min-width: 0;
418
+ overflow: hidden;
419
+ text-overflow: ellipsis;
420
+ }
421
+
422
+ .youp-grid__cell--tree {
423
+ display: flex;
424
+ align-items: center;
425
+ }
426
+
427
+ .youp-grid__cell-tree-content {
428
+ display: inline-flex;
429
+ min-width: 0;
430
+ align-items: center;
431
+ gap: 4px;
432
+ overflow: hidden;
433
+ text-overflow: ellipsis;
434
+ }
435
+
436
+ .youp-grid__cell-tree-value {
437
+ min-width: 0;
438
+ overflow: hidden;
439
+ text-overflow: ellipsis;
440
+ }
441
+
442
+ .youp-grid__tree-toggle,
443
+ .youp-grid__tree-toggle-spacer {
444
+ display: inline-flex;
445
+ flex: 0 0 auto;
446
+ align-items: center;
447
+ justify-content: center;
448
+ width: 18px;
449
+ height: 18px;
450
+ }
451
+
452
+ .youp-grid__tree-toggle {
453
+ padding: 0;
454
+ border: 0;
455
+ color: var(--youp-grid-muted);
456
+ font: inherit;
457
+ cursor: pointer;
458
+ background: transparent;
459
+ }
460
+
461
+ .youp-grid__tree-toggle:focus-visible {
462
+ outline: 2px solid #2563eb;
463
+ outline-offset: 1px;
464
+ }
465
+
466
+ .youp-grid__tree-caret {
467
+ width: 12px;
468
+ line-height: 1;
469
+ text-align: center;
470
+ }
471
+
472
+ .youp-grid__cell--tooltip-open {
473
+ z-index: 8;
474
+ overflow: visible;
475
+ }
476
+
477
+ .youp-grid__cell-tooltip {
478
+ position: absolute;
479
+ top: calc(100% + 6px);
480
+ left: var(--youp-grid-cell-inline-padding);
481
+ z-index: 20;
482
+ width: max-content;
483
+ min-width: 120px;
484
+ max-width: 260px;
485
+ padding: 6px 8px;
486
+ border: 1px solid #0f172a;
487
+ border-radius: 6px;
488
+ color: #fff;
489
+ font-size: 12px;
490
+ line-height: 1.4;
491
+ text-align: left;
492
+ white-space: normal;
493
+ pointer-events: none;
494
+ background: #0f172a;
495
+ box-shadow: 0 8px 24px rgb(15 23 42 / 18%);
496
+ }
497
+
358
498
  .youp-grid__cell-checkbox {
359
499
  width: 16px;
360
500
  height: 16px;
@@ -573,6 +713,7 @@
573
713
 
574
714
  .youp-grid__cell--aggregation.youp-grid__cell--pinned-left,
575
715
  .youp-grid__cell--aggregation.youp-grid__cell--pinned-right,
716
+ .youp-grid__row-number-cell--aggregation,
576
717
  .youp-grid__selection-cell--aggregation {
577
718
  background: #f8fafc;
578
719
  }
@@ -585,6 +726,7 @@
585
726
  background: #f8fafc;
586
727
  }
587
728
 
729
+ .youp-grid__row-number-cell--group,
588
730
  .youp-grid__selection-cell--group {
589
731
  background: #f8fafc;
590
732
  }
@@ -686,6 +828,18 @@
686
828
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.16);
687
829
  }
688
830
 
831
+ .youp-grid__cell-context-menu {
832
+ position: fixed;
833
+ z-index: 40;
834
+ display: grid;
835
+ min-width: 180px;
836
+ padding: 6px;
837
+ border: 1px solid var(--youp-grid-border);
838
+ border-radius: 6px;
839
+ background: #fff;
840
+ box-shadow: 0 16px 34px rgba(15, 23, 42, 0.16);
841
+ }
842
+
689
843
  .youp-grid__column-menu-item {
690
844
  min-height: 28px;
691
845
  padding: 5px 8px;
package/dist/types.d.ts CHANGED
@@ -24,11 +24,51 @@ export type YoupGridCellsValueChange<TRow> = {
24
24
  changes: YoupGridCellValueChange<TRow>[];
25
25
  source: YoupGridCellsValueChangeSource;
26
26
  };
27
+ export type YoupGridRowsChangeSource = "context-menu";
28
+ export type YoupGridRowInsertPosition = "above" | "below";
29
+ export type YoupGridCreateRowContext<TRow> = {
30
+ rows: readonly TRow[];
31
+ rowIndex: number;
32
+ visibleRowIndex: number;
33
+ position: YoupGridRowInsertPosition;
34
+ anchorRow: TRow;
35
+ anchorRowId: GridRowId;
36
+ anchorRowIndex: number;
37
+ };
38
+ export type YoupGridRowInsertChange<TRow> = {
39
+ type: "insert";
40
+ row: TRow;
41
+ rowIndex: number;
42
+ visibleRowIndex: number;
43
+ position: YoupGridRowInsertPosition;
44
+ anchorRow: TRow;
45
+ anchorRowId: GridRowId;
46
+ anchorRowIndex: number;
47
+ };
48
+ export type YoupGridRowDeleteChange<TRow> = {
49
+ type: "delete";
50
+ row: TRow;
51
+ rowId: GridRowId;
52
+ rowIndex: number;
53
+ visibleRowIndex: number;
54
+ };
55
+ export type YoupGridRowChange<TRow> = YoupGridRowInsertChange<TRow> | YoupGridRowDeleteChange<TRow>;
56
+ export type YoupGridRowsChange<TRow> = {
57
+ rows: TRow[];
58
+ changes: YoupGridRowChange<TRow>[];
59
+ source: YoupGridRowsChangeSource;
60
+ };
27
61
  export type YoupGridCellMetaStatus = "loading" | "error" | "warning" | "success";
28
62
  export type YoupGridCellMeta = {
29
63
  status: YoupGridCellMetaStatus;
30
64
  message?: ReactNode;
31
65
  };
66
+ export type YoupGridCellTooltipMode = "native" | "rich" | "none";
67
+ export type YoupGridCellTooltipOptions = {
68
+ mode?: YoupGridCellTooltipMode;
69
+ autoOpenCellKey?: string | null;
70
+ autoOpenDurationMs?: number;
71
+ };
32
72
  export type YoupGridCanEditCellContext<TRow> = {
33
73
  row: TRow;
34
74
  rowNode: RowNode<TRow>;
@@ -61,6 +101,8 @@ export type YoupGridOptions<TRow> = {
61
101
  defaultState?: GridState;
62
102
  onStateChange?: (change: YoupGridStateChange<TRow>) => void;
63
103
  getRowId?: (row: TRow, index: number) => GridRowId;
104
+ treeData?: boolean;
105
+ getParentRowId?: (row: TRow, index: number) => GridRowId | null | undefined;
64
106
  rowModelType?: GridRowModelType;
65
107
  serverRowCount?: number;
66
108
  serverFilteredRowCount?: number;
@@ -101,6 +143,8 @@ export type YoupGridController<TRow> = {
101
143
  setRowSelected: (rowId: GridRowId, selected: boolean) => void;
102
144
  setSelectedRows: (rowIds: readonly GridRowId[]) => void;
103
145
  toggleRowSelected: (rowId: GridRowId) => void;
146
+ setTreeExpandedRows: (rowIds: readonly GridRowId[]) => void;
147
+ toggleTreeRowExpanded: (rowId: GridRowId) => void;
104
148
  };
105
149
  export type YoupGridProps<TRow> = YoupGridOptions<TRow> & {
106
150
  className?: string;
@@ -117,8 +161,10 @@ export type YoupGridProps<TRow> = YoupGridOptions<TRow> & {
117
161
  showFilters?: boolean;
118
162
  showAggregationFooter?: boolean;
119
163
  showPagination?: boolean;
164
+ showRowNumberColumn?: boolean;
120
165
  showRowSelectionColumn?: boolean;
121
166
  pinRowSelectionColumn?: boolean;
167
+ showCellContextMenu?: boolean;
122
168
  csvFileName?: string;
123
169
  density?: YoupGridDensity;
124
170
  defaultDensity?: YoupGridDensity;
@@ -129,11 +175,14 @@ export type YoupGridProps<TRow> = YoupGridOptions<TRow> & {
129
175
  errorContent?: ReactNode;
130
176
  cellMeta?: Record<string, YoupGridCellMeta | undefined>;
131
177
  getCellMeta?: (context: YoupGridCanEditCellContext<TRow>) => YoupGridCellMeta | undefined;
178
+ cellTooltip?: YoupGridCellTooltipOptions;
132
179
  renderCell?: (context: YoupGridCellContext<TRow>) => ReactNode;
133
180
  renderHeader?: (context: YoupGridHeaderContext<TRow>) => ReactNode;
134
181
  onCellValueChange?: (change: YoupGridCellValueChange<TRow>) => void;
135
182
  onCellEditCommit?: (commit: YoupGridCellEditCommit<TRow>) => void;
136
183
  onCellsValueChange?: (change: YoupGridCellsValueChange<TRow>) => void;
184
+ createRow?: (context: YoupGridCreateRowContext<TRow>) => TRow;
185
+ onRowsChange?: (change: YoupGridRowsChange<TRow>) => void;
137
186
  onRowClick?: (event: YoupGridRowEvent<TRow>) => void;
138
187
  onRowDoubleClick?: (event: YoupGridRowEvent<TRow>) => void;
139
188
  onRowsEndReached?: (event: YoupGridRowsEndReachedEvent<TRow>) => void;
@@ -147,6 +196,9 @@ export type YoupGridCellContext<TRow> = {
147
196
  focused: boolean;
148
197
  editable: boolean;
149
198
  meta?: YoupGridCellMeta;
199
+ treeDepth?: number;
200
+ hasChildren?: boolean;
201
+ expanded?: boolean;
150
202
  };
151
203
  export type YoupGridHeaderContext<TRow> = {
152
204
  column: ResolvedColumnDef<TRow>;
@@ -1,4 +1,4 @@
1
- import { acknowledgeRemoteCache as acknowledgeCoreRemoteCache, buildRowModel, cancelRemoteRequest as cancelCoreRemoteRequest, clearFilter as clearCoreFilter, clearSort as clearCoreSort, createGridState, failRemoteRequest as failCoreRemoteRequest, finishRemoteRequest as finishCoreRemoteRequest, invalidateRemoteCache as invalidateCoreRemoteCache, setColumnHidden as setCoreColumnHidden, setColumnPinned as setCoreColumnPinned, setColumnWidth as setCoreColumnWidth, setCursorPage as setCoreCursorPage, setCursorPageSize as setCoreCursorPageSize, setCursorPagination as setCoreCursorPagination, setAggregation as setCoreAggregation, setFilter as setCoreFilter, setPagination, setRemoteCache as setCoreRemoteCache, setRowGrouping as setCoreRowGrouping, setRowSelected as setCoreRowSelected, setSelectedRows as setCoreSelectedRows, setSort as setCoreSort, startRemoteRequest as startCoreRemoteRequest, toggleRowGroupExpanded as toggleCoreRowGroupExpanded, toggleRowSelected as toggleCoreRowSelected, toggleSort as toggleCoreSort, } from "@youp-grid/core";
1
+ import { acknowledgeRemoteCache as acknowledgeCoreRemoteCache, buildRowModel, cancelRemoteRequest as cancelCoreRemoteRequest, clearFilter as clearCoreFilter, clearSort as clearCoreSort, createGridState, failRemoteRequest as failCoreRemoteRequest, finishRemoteRequest as finishCoreRemoteRequest, invalidateRemoteCache as invalidateCoreRemoteCache, setColumnHidden as setCoreColumnHidden, setColumnPinned as setCoreColumnPinned, setColumnWidth as setCoreColumnWidth, setCursorPage as setCoreCursorPage, setCursorPageSize as setCoreCursorPageSize, setCursorPagination as setCoreCursorPagination, setAggregation as setCoreAggregation, setFilter as setCoreFilter, setPagination, setRemoteCache as setCoreRemoteCache, setRowGrouping as setCoreRowGrouping, setRowSelected as setCoreRowSelected, setSelectedRows as setCoreSelectedRows, setSort as setCoreSort, setTreeExpandedRows as setCoreTreeExpandedRows, startRemoteRequest as startCoreRemoteRequest, toggleRowGroupExpanded as toggleCoreRowGroupExpanded, toggleRowSelected as toggleCoreRowSelected, toggleSort as toggleCoreSort, toggleTreeRowExpanded as toggleCoreTreeRowExpanded, } from "@youp-grid/core";
2
2
  import { useCallback, useMemo, useState } from "react";
3
3
  export function useYoupGrid(options) {
4
4
  const isControlled = options.state !== undefined;
@@ -12,6 +12,8 @@ export function useYoupGrid(options) {
12
12
  columns: options.columns,
13
13
  state,
14
14
  getRowId: options.getRowId,
15
+ treeData: options.treeData,
16
+ getParentRowId: options.getParentRowId,
15
17
  rowModelType: options.rowModelType,
16
18
  serverRowCount: options.serverRowCount,
17
19
  serverFilteredRowCount: options.serverFilteredRowCount,
@@ -20,6 +22,8 @@ export function useYoupGrid(options) {
20
22
  options.rows,
21
23
  options.columns,
22
24
  options.getRowId,
25
+ options.treeData,
26
+ options.getParentRowId,
23
27
  options.rowModelType,
24
28
  options.serverRowCount,
25
29
  options.serverFilteredRowCount,
@@ -31,6 +35,8 @@ export function useYoupGrid(options) {
31
35
  columns: options.columns,
32
36
  state: nextState,
33
37
  getRowId: options.getRowId,
38
+ treeData: options.treeData,
39
+ getParentRowId: options.getParentRowId,
34
40
  rowModelType: options.rowModelType,
35
41
  serverRowCount: options.serverRowCount,
36
42
  serverFilteredRowCount: options.serverFilteredRowCount,
@@ -93,5 +99,7 @@ export function useYoupGrid(options) {
93
99
  setRowSelected: (rowId, selected) => commitState(setCoreRowSelected(state, rowId, selected)),
94
100
  setSelectedRows: (rowIds) => commitState(setCoreSelectedRows(state, rowIds)),
95
101
  toggleRowSelected: (rowId) => commitState(toggleCoreRowSelected(state, rowId)),
102
+ setTreeExpandedRows: (rowIds) => commitState(setCoreTreeExpandedRows(state, rowIds)),
103
+ toggleTreeRowExpanded: (rowId) => commitState(toggleCoreTreeRowExpanded(state, rowId)),
96
104
  };
97
105
  }
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "@youp-grid/react",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "React adapter for Youp Grid.",
5
+ "license": "MIT",
6
+ "author": "SeungyoupBaek",
5
7
  "type": "module",
8
+ "keywords": [
9
+ "data-grid",
10
+ "grid",
11
+ "react",
12
+ "typescript",
13
+ "table"
14
+ ],
6
15
  "main": "./dist/index.js",
7
16
  "types": "./dist/index.d.ts",
8
17
  "exports": {
@@ -14,13 +23,18 @@
14
23
  },
15
24
  "files": [
16
25
  "dist",
17
- "README.md"
26
+ "README.md",
27
+ "LICENSE"
18
28
  ],
19
29
  "repository": {
20
30
  "type": "git",
21
31
  "url": "git+ssh://git@github.com/SeungyoupBaek/youp-grid.git",
22
32
  "directory": "packages/react"
23
33
  },
34
+ "bugs": {
35
+ "url": "https://github.com/SeungyoupBaek/youp-grid/issues"
36
+ },
37
+ "homepage": "https://github.com/SeungyoupBaek/youp-grid/tree/main/packages/react#readme",
24
38
  "publishConfig": {
25
39
  "access": "public"
26
40
  },
@@ -28,7 +42,7 @@
28
42
  "build": "tsc -p tsconfig.build.json && cp src/styles.css dist/styles.css"
29
43
  },
30
44
  "dependencies": {
31
- "@youp-grid/core": "0.2.2"
45
+ "@youp-grid/core": "0.2.4"
32
46
  },
33
47
  "peerDependencies": {
34
48
  "react": ">=18.2.0"