@youp-grid/react 0.2.3 → 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/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,9 +35,12 @@ 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();
37
39
  const [activeTooltipCellKey, setActiveTooltipCellKey] = useState();
40
+ const showRowNumberColumn = props.showRowNumberColumn ?? false;
38
41
  const showRowSelectionColumn = props.showRowSelectionColumn ?? false;
39
42
  const pinRowSelectionColumn = props.pinRowSelectionColumn ?? false;
43
+ const showCellContextMenu = props.showCellContextMenu ?? false;
40
44
  const cellTooltipMode = props.cellTooltip?.mode ?? "native";
41
45
  const gridEditable = (props.editable ?? true) && !props.readOnly;
42
46
  const displayRows = rowModel.displayRows;
@@ -79,11 +83,14 @@ export function YoupGrid(props) {
79
83
  const selectedVisibleRowCount = visibleRowIds.filter((rowId) => selectedRowIds.has(rowId)).length;
80
84
  const allVisibleRowsSelected = visibleRowIds.length > 0 && selectedVisibleRowCount === visibleRowIds.length;
81
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);
82
89
  const columnLayouts = useMemo(() => {
83
90
  return getColumnLayouts(rowModel.visibleColumns, {
84
- leftOffset: showRowSelectionColumn && pinRowSelectionColumn ? SELECTION_COLUMN_WIDTH : 0,
91
+ leftOffset: pinnedColumnOffset,
85
92
  });
86
- }, [pinRowSelectionColumn, rowModel.visibleColumns, showRowSelectionColumn]);
93
+ }, [pinnedColumnOffset, rowModel.visibleColumns]);
87
94
  const visibleColumns = useMemo(() => columnLayouts.map((layout) => layout.column), [columnLayouts]);
88
95
  const visibleRowIndexById = useMemo(() => {
89
96
  return new Map(rowModel.visibleRows.map((row, index) => [row.id, index]));
@@ -169,6 +176,25 @@ export function YoupGrid(props) {
169
176
  setColumnMenuOpenId(undefined);
170
177
  }
171
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]);
172
198
  useEffect(() => {
173
199
  if (cellTooltipMode !== "rich") {
174
200
  setActiveTooltipCellKey(undefined);
@@ -333,11 +359,207 @@ export function YoupGrid(props) {
333
359
  const visibleRowIdSet = new Set(visibleRowIds);
334
360
  controller.setSelectedRows(currentSelectedRowIds.filter((rowId) => !visibleRowIdSet.has(rowId)));
335
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
+ };
336
557
  return createElement("div", {
337
558
  className: [
338
559
  "youp-grid",
339
560
  `youp-grid--density-${density}`,
340
561
  !gridEditable ? "youp-grid--read-only" : "",
562
+ showRowNumberColumn ? "youp-grid--row-number-column" : "",
341
563
  pinRowSelectionColumn ? "youp-grid--selection-column-pinned" : "",
342
564
  props.className,
343
565
  ].filter(Boolean).join(" "),
@@ -370,7 +592,7 @@ export function YoupGrid(props) {
370
592
  className: "youp-grid__viewport",
371
593
  role: "grid",
372
594
  "aria-rowcount": displayRows.length,
373
- "aria-colcount": rowModel.visibleColumns.length + (showRowSelectionColumn ? 1 : 0),
595
+ "aria-colcount": rowModel.visibleColumns.length + (showRowNumberColumn ? 1 : 0) + (showRowSelectionColumn ? 1 : 0),
374
596
  "aria-busy": loading || undefined,
375
597
  "aria-readonly": !gridEditable || undefined,
376
598
  onCopy: (event) => {
@@ -400,14 +622,19 @@ export function YoupGrid(props) {
400
622
  });
401
623
  },
402
624
  }, createElement("div", { className: "youp-grid__header", role: "rowgroup", ref: headerRef }, hasHeaderGroups
403
- ? createElement("div", { className: "youp-grid__row youp-grid__row--header-group", role: "row" }, showRowSelectionColumn
404
- ? renderSelectionHeaderGroupCell()
625
+ ? createElement("div", { className: "youp-grid__row youp-grid__row--header-group", role: "row" }, showRowNumberColumn
626
+ ? renderRowNumberHeaderGroupCell()
627
+ : undefined, showRowSelectionColumn
628
+ ? renderSelectionHeaderGroupCell(selectionColumnOffset)
405
629
  : undefined, headerGroupLayouts.map((layout) => renderHeaderGroupCell(layout)))
406
- : 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
407
633
  ? renderSelectionHeaderCell({
408
634
  checked: allVisibleRowsSelected,
409
635
  indeterminate: someVisibleRowsSelected,
410
636
  disabled: visibleRowIds.length === 0,
637
+ leftOffset: selectionColumnOffset,
411
638
  toggleSelected: setVisibleRowsSelected,
412
639
  })
413
640
  : undefined, columnLayouts.map((layout) => {
@@ -473,7 +700,9 @@ export function YoupGrid(props) {
473
700
  return renderGroupRow({
474
701
  row,
475
702
  columns: columnLayouts,
703
+ showRowNumberColumn,
476
704
  showSelectionColumn: showRowSelectionColumn,
705
+ selectionColumnOffset,
477
706
  rowHeight,
478
707
  toggleExpanded: controller.toggleRowGroupExpanded,
479
708
  });
@@ -483,7 +712,9 @@ export function YoupGrid(props) {
483
712
  row,
484
713
  columns: columnLayouts,
485
714
  selected: selectedRowIds.has(row.id),
715
+ showRowNumberColumn,
486
716
  showSelectionColumn: showRowSelectionColumn,
717
+ selectionColumnOffset,
487
718
  displayIndex,
488
719
  rowIndex,
489
720
  rowHeight,
@@ -553,6 +784,7 @@ export function YoupGrid(props) {
553
784
  closeCellTooltip: (cellKey) => {
554
785
  setActiveTooltipCellKey((current) => current === cellKey ? undefined : current);
555
786
  },
787
+ openCellContextMenu: showCellContextMenu ? openCellContextMenu : undefined,
556
788
  onRowClick: props.onRowClick,
557
789
  onRowDoubleClick: props.onRowDoubleClick,
558
790
  onCellKeyDown: (event, cell) => {
@@ -596,8 +828,27 @@ export function YoupGrid(props) {
596
828
  enabled: showAggregationFooter,
597
829
  aggregation: rowModel.aggregation,
598
830
  columns: columnLayouts,
831
+ showRowNumberColumn,
599
832
  showSelectionColumn: showRowSelectionColumn,
600
- })), 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({
601
852
  enabled: props.showPagination ?? true,
602
853
  cursorPagination: controller.state.cursorPagination,
603
854
  pageCount: rowModel.pageCount,
@@ -630,7 +881,7 @@ function renderAggregationFooter(context) {
630
881
  return undefined;
631
882
  }
632
883
  const aggregationByColumn = groupAggregationByColumn(context.aggregation);
633
- 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) => {
634
885
  const results = aggregationByColumn.get(layout.column.id) ?? [];
635
886
  return createElement("div", {
636
887
  key: layout.column.id,
@@ -640,11 +891,19 @@ function renderAggregationFooter(context) {
640
891
  }, results.map(formatAggregationResult).join(" · "));
641
892
  })));
642
893
  }
643
- 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) {
644
903
  return createElement("div", {
645
904
  className: "youp-grid__selection-cell youp-grid__selection-cell--aggregation",
646
905
  role: "gridcell",
647
- style: getSelectionCellStyle(),
906
+ style: getSelectionCellStyle(leftOffset),
648
907
  "aria-hidden": true,
649
908
  });
650
909
  }
@@ -679,13 +938,31 @@ function renderHeaderGroupCell(layout) {
679
938
  style: getHeaderGroupStyle(layout),
680
939
  }, layout.headerGroup ?? "");
681
940
  }
682
- 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) {
683
960
  return createElement("div", {
684
961
  key: "__selection-group",
685
962
  className: "youp-grid__cell youp-grid__cell--header-group youp-grid__selection-cell youp-grid__selection-cell--header-group",
686
963
  role: "columnheader",
687
964
  "aria-hidden": true,
688
- style: getSelectionCellStyle(),
965
+ style: getSelectionCellStyle(leftOffset),
689
966
  });
690
967
  }
691
968
  function renderSelectionHeaderCell(context) {
@@ -693,7 +970,7 @@ function renderSelectionHeaderCell(context) {
693
970
  key: "__selection",
694
971
  className: "youp-grid__cell youp-grid__cell--header youp-grid__selection-cell youp-grid__selection-cell--header",
695
972
  role: "columnheader",
696
- style: getSelectionCellStyle(),
973
+ style: getSelectionCellStyle(context.leftOffset),
697
974
  }, createElement(SelectionHeaderCheckbox, context));
698
975
  }
699
976
  function SelectionHeaderCheckbox(context) {
@@ -840,6 +1117,63 @@ function renderColumnMenuButton(context) {
840
1117
  },
841
1118
  }, context.label);
842
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
+ }
843
1177
  function renderGroupRow(context) {
844
1178
  const width = getColumnsWidth(context.columns);
845
1179
  return createElement("div", {
@@ -848,7 +1182,7 @@ function renderGroupRow(context) {
848
1182
  role: "row",
849
1183
  "aria-rowindex": context.row.index + 1,
850
1184
  style: { height: context.rowHeight },
851
- }, context.showSelectionColumn ? renderSelectionGroupCell() : undefined, createElement("div", {
1185
+ }, context.showRowNumberColumn ? renderRowNumberGroupCell() : undefined, context.showSelectionColumn ? renderSelectionGroupCell(context.selectionColumnOffset) : undefined, createElement("div", {
852
1186
  className: "youp-grid__cell youp-grid__cell--group",
853
1187
  role: "gridcell",
854
1188
  "aria-colspan": context.columns.length,
@@ -864,11 +1198,19 @@ function renderGroupRow(context) {
864
1198
  onClick: () => context.toggleExpanded(context.row.groupId),
865
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`))));
866
1200
  }
867
- 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) {
868
1210
  return createElement("div", {
869
1211
  className: "youp-grid__selection-cell youp-grid__selection-cell--group",
870
1212
  role: "gridcell",
871
- style: getSelectionCellStyle(),
1213
+ style: getSelectionCellStyle(leftOffset),
872
1214
  "aria-hidden": true,
873
1215
  });
874
1216
  }
@@ -896,11 +1238,18 @@ function renderRow(context) {
896
1238
  context.onRowDoubleClick?.(createRowEvent(context, event));
897
1239
  }
898
1240
  },
899
- }, context.showSelectionColumn
1241
+ }, context.showRowNumberColumn
1242
+ ? renderRowNumberCell({
1243
+ rowIndex: context.rowIndex,
1244
+ displayIndex: context.displayIndex,
1245
+ })
1246
+ : undefined, context.showSelectionColumn
900
1247
  ? renderSelectionCell({
901
1248
  rowIndex: context.rowIndex,
902
1249
  selected: context.selected,
903
1250
  setSelected: context.setRowSelected,
1251
+ leftOffset: context.selectionColumnOffset,
1252
+ ariaColIndex: context.showRowNumberColumn ? 2 : 1,
904
1253
  })
905
1254
  : undefined, context.columns.map((layout, columnIndex) => {
906
1255
  const focused = context.focusedCell.rowIndex === context.rowIndex && context.focusedCell.columnIndex === columnIndex;
@@ -926,7 +1275,7 @@ function renderRow(context) {
926
1275
  layout,
927
1276
  rowIndex: context.rowIndex,
928
1277
  columnIndex,
929
- ariaColumnOffset: context.showSelectionColumn ? 1 : 0,
1278
+ ariaColumnOffset: (context.showRowNumberColumn ? 1 : 0) + (context.showSelectionColumn ? 1 : 0),
930
1279
  focused,
931
1280
  selected,
932
1281
  fillTargeted,
@@ -947,6 +1296,7 @@ function renderRow(context) {
947
1296
  cancelEditing: context.cancelEditing,
948
1297
  commitEditing: context.commitEditing,
949
1298
  onKeyDown: context.onCellKeyDown,
1299
+ openContextMenu: context.openCellContextMenu,
950
1300
  cellTooltipMode: context.cellTooltipMode,
951
1301
  activeTooltipCellKey: context.activeTooltipCellKey,
952
1302
  openCellTooltip: context.openCellTooltip,
@@ -955,12 +1305,21 @@ function renderRow(context) {
955
1305
  });
956
1306
  }));
957
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
+ }
958
1317
  function renderSelectionCell(context) {
959
1318
  return createElement("div", {
960
1319
  className: "youp-grid__cell youp-grid__selection-cell",
961
1320
  role: "gridcell",
962
- "aria-colindex": 1,
963
- style: getSelectionCellStyle(),
1321
+ "aria-colindex": context.ariaColIndex,
1322
+ style: getSelectionCellStyle(context.leftOffset),
964
1323
  }, createElement("input", {
965
1324
  className: "youp-grid__selection-checkbox",
966
1325
  type: "checkbox",
@@ -1081,6 +1440,9 @@ function renderCell(context) {
1081
1440
  }
1082
1441
  },
1083
1442
  onKeyDown: (event) => context.onKeyDown(event, cellState),
1443
+ onContextMenu: context.openContextMenu
1444
+ ? (event) => context.openContextMenu?.(event, cellState)
1445
+ : undefined,
1084
1446
  onMouseEnter: () => {
1085
1447
  if (hasRichTooltip) {
1086
1448
  context.openCellTooltip(cellKey);
@@ -1420,9 +1782,16 @@ function getHeaderGroupStyle(layout) {
1420
1782
  }
1421
1783
  return style;
1422
1784
  }
1423
- function getSelectionCellStyle() {
1785
+ function getRowNumberCellStyle() {
1424
1786
  return {
1425
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,
1426
1795
  width: SELECTION_COLUMN_WIDTH,
1427
1796
  flex: `0 0 ${SELECTION_COLUMN_WIDTH}px`,
1428
1797
  };
@@ -1860,22 +2229,38 @@ function commitEditingCell(context) {
1860
2229
  };
1861
2230
  }
1862
2231
  function handleGridCopy(context) {
2232
+ context.event.preventDefault();
2233
+ context.event.clipboardData.setData("text/plain", getGridClipboardText(context));
2234
+ }
2235
+ function getGridClipboardText(context) {
1863
2236
  const range = context.selectionRange ?? {
1864
2237
  anchor: context.focusedCell,
1865
2238
  focus: context.focusedCell,
1866
2239
  };
1867
- const text = serializeGridRange({
2240
+ return serializeGridRange({
1868
2241
  rows: context.rows,
1869
2242
  columns: context.columns,
1870
2243
  range,
1871
2244
  });
1872
- context.event.preventDefault();
1873
- context.event.clipboardData.setData("text/plain", text);
1874
2245
  }
1875
2246
  function handleGridPaste(context) {
1876
- 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);
1877
2262
  if (values.length === 0) {
1878
- return;
2263
+ return false;
1879
2264
  }
1880
2265
  const normalizedRange = context.selectionRange ? normalizeCellRange(context.selectionRange) : undefined;
1881
2266
  const startCell = normalizedRange
@@ -1887,7 +2272,6 @@ function handleGridPaste(context) {
1887
2272
  const fillRange = normalizedRange && values.length === 1 && values[0]?.length === 1
1888
2273
  ? normalizedRange
1889
2274
  : undefined;
1890
- context.event.preventDefault();
1891
2275
  const changes = [];
1892
2276
  for (const cell of getClipboardPasteCells({
1893
2277
  values,
@@ -1917,6 +2301,19 @@ function handleGridPaste(context) {
1917
2301
  });
1918
2302
  }
1919
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();
1920
2317
  }
1921
2318
  function deleteGridCellValues(context) {
1922
2319
  const range = normalizeCellRange(context.selectionRange ?? {
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, YoupGridCellTooltipMode, YoupGridCellTooltipOptions, 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;
@@ -656,6 +713,7 @@
656
713
 
657
714
  .youp-grid__cell--aggregation.youp-grid__cell--pinned-left,
658
715
  .youp-grid__cell--aggregation.youp-grid__cell--pinned-right,
716
+ .youp-grid__row-number-cell--aggregation,
659
717
  .youp-grid__selection-cell--aggregation {
660
718
  background: #f8fafc;
661
719
  }
@@ -668,6 +726,7 @@
668
726
  background: #f8fafc;
669
727
  }
670
728
 
729
+ .youp-grid__row-number-cell--group,
671
730
  .youp-grid__selection-cell--group {
672
731
  background: #f8fafc;
673
732
  }
@@ -769,6 +828,18 @@
769
828
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.16);
770
829
  }
771
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
+
772
843
  .youp-grid__column-menu-item {
773
844
  min-height: 28px;
774
845
  padding: 5px 8px;
package/dist/types.d.ts CHANGED
@@ -24,6 +24,40 @@ 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;
@@ -127,8 +161,10 @@ export type YoupGridProps<TRow> = YoupGridOptions<TRow> & {
127
161
  showFilters?: boolean;
128
162
  showAggregationFooter?: boolean;
129
163
  showPagination?: boolean;
164
+ showRowNumberColumn?: boolean;
130
165
  showRowSelectionColumn?: boolean;
131
166
  pinRowSelectionColumn?: boolean;
167
+ showCellContextMenu?: boolean;
132
168
  csvFileName?: string;
133
169
  density?: YoupGridDensity;
134
170
  defaultDensity?: YoupGridDensity;
@@ -145,6 +181,8 @@ export type YoupGridProps<TRow> = YoupGridOptions<TRow> & {
145
181
  onCellValueChange?: (change: YoupGridCellValueChange<TRow>) => void;
146
182
  onCellEditCommit?: (commit: YoupGridCellEditCommit<TRow>) => void;
147
183
  onCellsValueChange?: (change: YoupGridCellsValueChange<TRow>) => void;
184
+ createRow?: (context: YoupGridCreateRowContext<TRow>) => TRow;
185
+ onRowsChange?: (change: YoupGridRowsChange<TRow>) => void;
148
186
  onRowClick?: (event: YoupGridRowEvent<TRow>) => void;
149
187
  onRowDoubleClick?: (event: YoupGridRowEvent<TRow>) => void;
150
188
  onRowsEndReached?: (event: YoupGridRowsEndReachedEvent<TRow>) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youp-grid/react",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "React adapter for Youp Grid.",
5
5
  "license": "MIT",
6
6
  "author": "SeungyoupBaek",
@@ -42,7 +42,7 @@
42
42
  "build": "tsc -p tsconfig.build.json && cp src/styles.css dist/styles.css"
43
43
  },
44
44
  "dependencies": {
45
- "@youp-grid/core": "0.2.3"
45
+ "@youp-grid/core": "0.2.4"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "react": ">=18.2.0"