@nova-design-system/nova-react 3.28.0 → 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/{index-Kxp9mv-Q.js → index-DgKzi_Pd.js} +405 -316
  3. package/dist/cjs/index.js +1 -1
  4. package/dist/cjs/{nv-accordion-item.entry-fds_kk_3.js → nv-accordion-item.entry-D1o0gC5w.js} +3 -3
  5. package/dist/cjs/{nv-accordion.entry-BLs4N5ZL.js → nv-accordion.entry-DYJtq9Az.js} +1 -1
  6. package/dist/cjs/{nv-alert.entry-Bx1BiC8F.js → nv-alert.entry-CMtCdHmk.js} +2 -2
  7. package/dist/cjs/{nv-avatar.entry-DS88LME3.js → nv-avatar.entry-B6e7aG4W.js} +1 -1
  8. package/dist/cjs/{nv-badge_2.entry-vBPxmUmg.js → nv-badge_2.entry-RD3j0bJM.js} +7 -6
  9. package/dist/cjs/{nv-breadcrumb.entry-BOo3hA5y.js → nv-breadcrumb.entry-DgpqY2fr.js} +1 -1
  10. package/dist/cjs/{nv-breadcrumbs.entry-igHC_6Bd.js → nv-breadcrumbs.entry-CZgcDUw5.js} +1 -1
  11. package/dist/cjs/{nv-button.entry-BRQPmQbs.js → nv-button.entry-DR9NaRxG.js} +1 -1
  12. package/dist/cjs/{nv-buttongroup.entry-D3tG2EZ1.js → nv-buttongroup.entry-qO8r7WqG.js} +1 -1
  13. package/dist/cjs/{nv-calendar.entry-BpNHMTKr.js → nv-calendar.entry-DRlv3Xph.js} +1 -1
  14. package/dist/cjs/{nv-col.entry-lyIZqDsW.js → nv-col.entry-B7utJttP.js} +2 -2
  15. package/dist/cjs/{nv-datagrid.entry-BeemONKu.js → nv-datagrid.entry-digYmlnA.js} +1 -1
  16. package/dist/cjs/{nv-datagridcolumn.entry-B6kE4eVC.js → nv-datagridcolumn.entry-UwaDi-Hr.js} +1 -1
  17. package/dist/cjs/{nv-datetest.entry-C1uuC-ZG.js → nv-datetest.entry-BJtWaM2T.js} +1 -1
  18. package/dist/cjs/{nv-datetimetest.entry-Hthxbjj9.js → nv-datetimetest.entry-WaNPcHxh.js} +1 -1
  19. package/dist/cjs/{nv-dialog.entry-Cr9zwMPo.js → nv-dialog.entry-BI_mFT2G.js} +1 -1
  20. package/dist/cjs/{nv-dialogfooter_2.entry-DaaKojyE.js → nv-dialogfooter_2.entry-DU2ClBUR.js} +6 -4
  21. package/dist/cjs/{nv-drawer.entry-0UaYxCjh.js → nv-drawer.entry-BKF4CyOt.js} +1 -1
  22. package/dist/cjs/{nv-drawerfooter_2.entry-CqtuC7xP.js → nv-drawerfooter_2.entry-BSyUs7_4.js} +4 -2
  23. package/dist/cjs/{nv-fieldcheckbox.entry-DK3aO8C8.js → nv-fieldcheckbox.entry-l5FWToQi.js} +5 -5
  24. package/dist/cjs/{nv-fielddate.entry-BY-xF3KN.js → nv-fielddate.entry-BfviesNp.js} +2 -2
  25. package/dist/cjs/{nv-fielddaterange.entry-BT6qCQc3.js → nv-fielddaterange.entry-BvDkPLXG.js} +2 -2
  26. package/dist/cjs/{nv-fielddropdown.entry-CsVD067i.js → nv-fielddropdown.entry-B-nYjuMt.js} +2 -2
  27. package/dist/cjs/{nv-fielddropdownitem.entry-Crtfwlx7.js → nv-fielddropdownitem.entry-Dxqyb9DN.js} +1 -1
  28. package/dist/cjs/{nv-fieldmultiselect.entry-C2uoE60e.js → nv-fieldmultiselect.entry-CgO1RP0W.js} +115 -86
  29. package/dist/cjs/{nv-fieldnumber.entry-DzW5SiiZ.js → nv-fieldnumber.entry-stgdLi7x.js} +2 -2
  30. package/dist/cjs/{nv-fieldpassword.entry-D4r9Qxos.js → nv-fieldpassword.entry-CHA3JAUd.js} +2 -2
  31. package/dist/cjs/{nv-fieldradio.entry-DX0Ghx--.js → nv-fieldradio.entry-DHavVjB-.js} +1 -1
  32. package/dist/cjs/{nv-fieldselect.entry-_CglE66i.js → nv-fieldselect.entry-Dq5AsOFt.js} +2 -2
  33. package/dist/cjs/{nv-fieldslider.entry-6zt75uDU.js → nv-fieldslider.entry-SlF3BBUW.js} +1 -1
  34. package/dist/cjs/{nv-fieldtext.entry-B4X110wn.js → nv-fieldtext.entry-BYCc8SyD.js} +2 -2
  35. package/dist/cjs/{nv-fieldtextarea.entry-CeAMhA8Y.js → nv-fieldtextarea.entry-96JCOb9L.js} +2 -2
  36. package/dist/cjs/{nv-fieldtime.entry-BvzncwNd.js → nv-fieldtime.entry-BdzjxkaK.js} +15 -12
  37. package/dist/cjs/{nv-icon.entry-C2md2kmq.js → nv-icon.entry-CSRxi6BH.js} +2 -2
  38. package/dist/cjs/{nv-iconbutton_2.entry-D-zRpLjT.js → nv-iconbutton_2.entry-BtDxwMFD.js} +2 -2
  39. package/dist/cjs/{nv-menu.entry-DN_DkosX.js → nv-menu.entry-aX39SPH8.js} +1 -1
  40. package/dist/cjs/{nv-menuitem.entry-Cj6w33rq.js → nv-menuitem.entry-DzSp52G2.js} +2 -2
  41. package/dist/cjs/{nv-notification-bullet.entry-DagStJ3K.js → nv-notification-bullet.entry-D2yOXj46.js} +1 -1
  42. package/dist/cjs/{nv-notification.entry-oV69FpxE.js → nv-notification.entry-BRXHbtT8.js} +2 -2
  43. package/dist/cjs/{nv-notificationcontainer.entry-gQGHHeer.js → nv-notificationcontainer.entry-YNIM2_ah.js} +1 -1
  44. package/dist/cjs/{nv-pagination-nav.entry-BYvcVj1M.js → nv-pagination-nav.entry-uY1nT9aT.js} +1 -1
  45. package/dist/cjs/{nv-paginationtable.entry-CwCFQwbl.js → nv-paginationtable.entry-mr5KYXVC.js} +1 -1
  46. package/dist/cjs/{nv-popover.entry-DySToeSB.js → nv-popover.entry-_9ARKM70.js} +1 -1
  47. package/dist/cjs/{nv-row.entry-46ghuEeG.js → nv-row.entry-BMQvcqlU.js} +2 -2
  48. package/dist/cjs/{nv-sidebar.entry-B6opNG2r.js → nv-sidebar.entry-C4HTjJmz.js} +1 -1
  49. package/dist/cjs/{nv-sidebarcontent.entry-Pb8c2QoA.js → nv-sidebarcontent.entry-llnRwVuj.js} +1 -1
  50. package/dist/cjs/{nv-sidebardivider.entry-LCCO53Z5.js → nv-sidebardivider.entry--rQv8d5T.js} +1 -1
  51. package/dist/cjs/{nv-sidebarfooter.entry-DG5fkLHd.js → nv-sidebarfooter.entry-C5R0sUI_.js} +1 -1
  52. package/dist/cjs/{nv-sidebargroup.entry-DRqkSyQi.js → nv-sidebargroup.entry-DdrpLbU7.js} +1 -1
  53. package/dist/cjs/{nv-sidebarheader.entry-D6WvH2wG.js → nv-sidebarheader.entry-HuBPIsyf.js} +1 -1
  54. package/dist/cjs/{nv-sidebarlogo.entry-BorScwI-.js → nv-sidebarlogo.entry-TZNPoqnA.js} +1 -1
  55. package/dist/cjs/{nv-sidebarnavitem.entry-BEW74Rw3.js → nv-sidebarnavitem.entry-BWc3mF5I.js} +3 -3
  56. package/dist/cjs/{nv-sidebarnavsubitem.entry-EgaIlUfP.js → nv-sidebarnavsubitem.entry-DekANwO8.js} +1 -1
  57. package/dist/cjs/{nv-split.entry-CEC5Tuwz.js → nv-split.entry-BOwOB8FW.js} +1 -1
  58. package/dist/cjs/{nv-stack.entry-BR8lYaoI.js → nv-stack.entry-CaTiSLGN.js} +1 -1
  59. package/dist/cjs/{nv-table.entry-CISZFst5.js → nv-table.entry-LFZaS0Dy.js} +2 -2
  60. package/dist/cjs/{nv-tableheader.entry-DbdpTJGC.js → nv-tableheader.entry-CK50S8xW.js} +1 -1
  61. package/dist/cjs/{nv-timetest.entry-Dg1JEgAv.js → nv-timetest.entry-A9elKwkf.js} +1 -1
  62. package/dist/cjs/{nv-toggle.entry-DDfRpC1R.js → nv-toggle.entry-BGu-6rEs.js} +1 -1
  63. package/dist/cjs/{nv-togglebutton.entry-D7NkdIXP.js → nv-togglebutton.entry-BzAcBOgN.js} +1 -1
  64. package/dist/cjs/{nv-togglebuttongroup.entry-L8gitSUS.js → nv-togglebuttongroup.entry-CIWp7IXc.js} +1 -1
  65. package/dist/cjs/{nv-tooltip.entry-BElnrEqE.js → nv-tooltip.entry-CoGkSHpv.js} +1 -1
  66. package/dist/components/NvDatatable/NvDatatable.js +144 -55
  67. package/dist/types/components/NvDatatable/NvDatatable.d.ts +1 -1
  68. package/dist/types/components/NvDatatable/types.d.ts +5 -0
  69. package/dist/types/index.d.ts +1 -0
  70. package/dist/types/providers/NotificationProvider.d.ts +2 -2
  71. package/package.json +1 -1
@@ -7,8 +7,7 @@ if (typeof window !== 'undefined' &&
7
7
  defineNvPaginationtable();
8
8
  }
9
9
  import { applyRowSelection, applySelectAllSelection, shouldIgnoreSelectAllEvent, } from './selectionState';
10
- function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filtering, selection, renderPagination, renderFiltering, stickyHeader, ...htmlProps }) {
11
- const SELECTION_EVENT_SUPPRESSION_MS = 16;
10
+ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filtering, selection, renderPagination, renderFiltering, onRowClick, stickyHeader, ...htmlProps }) {
12
11
  const areSetsEqual = useCallback((a, b) => {
13
12
  if (a.size !== b.size) {
14
13
  return false;
@@ -32,9 +31,14 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
32
31
  const selectionMode = selection?.mode ?? 'none';
33
32
  const selectionEnabled = selectionMode !== 'none';
34
33
  const selectAllEnabled = selection?.enableSelectAll ?? true;
34
+ const rowClickEnabled = typeof onRowClick === 'function';
35
35
  const getSelectionRowId = useCallback((row) => selection?.getRowId?.(row) ?? '', [selection]);
36
36
  const lastRowRef = useRef(null);
37
- const suppressRowSelectionEventsRef = useRef(false);
37
+ const selectAllIntentRef = useRef(false);
38
+ const rowSelectionIntentRef = useRef(null);
39
+ const selectAllIntentResetTimerRef = useRef(null);
40
+ const rowSelectionIntentResetTimerRef = useRef(null);
41
+ const INTENT_RESET_DELAY_MS = 16;
38
42
  const debouncedSetFilter = useRef(null);
39
43
  const setGlobalFilterDebounced = useCallback((value) => {
40
44
  const debounceMs = filtering?.debounceMs ?? 300;
@@ -53,6 +57,12 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
53
57
  if (debouncedSetFilter.current) {
54
58
  clearTimeout(debouncedSetFilter.current);
55
59
  }
60
+ if (selectAllIntentResetTimerRef.current) {
61
+ clearTimeout(selectAllIntentResetTimerRef.current);
62
+ }
63
+ if (rowSelectionIntentResetTimerRef.current) {
64
+ clearTimeout(rowSelectionIntentResetTimerRef.current);
65
+ }
56
66
  };
57
67
  }, []);
58
68
  useEffect(() => {
@@ -237,8 +247,14 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
237
247
  ]);
238
248
  const table = useReactTable(tableConfig);
239
249
  const tableRows = table.getRowModel().rows;
250
+ const getCurrentVisibleRowIds = useCallback(() => {
251
+ return table
252
+ .getRowModel()
253
+ .rows.map((row) => getSelectionRowId(row.original))
254
+ .filter((id) => !!id);
255
+ }, [table, getSelectionRowId]);
240
256
  const setSelectionState = useCallback((nextSelectedIds) => {
241
- const current = new Set(selection?.selectedRowIds ?? selectedRowIdsRef.current);
257
+ const current = new Set(selectedRowIdsRef.current);
242
258
  const nextRaw = typeof nextSelectedIds === 'function'
243
259
  ? nextSelectedIds(new Set(current))
244
260
  : nextSelectedIds;
@@ -246,27 +262,14 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
246
262
  if (areSetsEqual(current, next)) {
247
263
  return;
248
264
  }
265
+ const hasExplicitSelectionIntent = selectAllIntentRef.current || rowSelectionIntentRef.current !== null;
266
+ if (!hasExplicitSelectionIntent && next.size < current.size) {
267
+ return;
268
+ }
249
269
  selectedRowIdsRef.current = next;
250
270
  setSelectedRowIdsState(next);
251
271
  selection?.onSelectionChange?.(new Set(next));
252
- }, [selection, selection?.selectedRowIds, areSetsEqual]);
253
- const resolveCheckedChange = useCallback((event) => {
254
- if (event && typeof event === 'object') {
255
- const eventLike = event;
256
- const target = eventLike.target;
257
- const currentTarget = eventLike.currentTarget;
258
- if (typeof eventLike.detail === 'boolean') {
259
- return eventLike.detail;
260
- }
261
- if (typeof target?.checked === 'boolean') {
262
- return target.checked;
263
- }
264
- if (typeof currentTarget?.checked === 'boolean') {
265
- return currentTarget.checked;
266
- }
267
- }
268
- return undefined;
269
- }, []);
272
+ }, [selection, areSetsEqual]);
270
273
  const isRowSelected = useCallback((row) => {
271
274
  if (!selectionEnabled) {
272
275
  return false;
@@ -277,22 +280,18 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
277
280
  const visibleRowIds = useMemo(() => tableRows
278
281
  .map((row) => getSelectionRowId(row.original))
279
282
  .filter((id) => !!id), [tableRows, getSelectionRowId]);
280
- const visibleRowIdsSet = useMemo(() => new Set(visibleRowIds), [visibleRowIds]);
281
283
  const setRowSelection = useCallback((row, isChecked) => {
282
284
  if (!selectionEnabled) {
283
285
  return;
284
286
  }
285
- if (suppressRowSelectionEventsRef.current) {
286
- return;
287
- }
288
287
  const rowId = getSelectionRowId(row);
289
288
  if (!rowId) {
290
289
  return;
291
290
  }
292
- if (!visibleRowIdsSet.has(rowId)) {
291
+ const currentVisibleRowIdsSet = new Set(getCurrentVisibleRowIds());
292
+ if (!currentVisibleRowIdsSet.has(rowId)) {
293
293
  return;
294
294
  }
295
- suppressRowSelectionEventsRef.current = true;
296
295
  setSelectionState((current) => {
297
296
  return applyRowSelection({
298
297
  currentSelectedIds: current,
@@ -301,15 +300,12 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
301
300
  selectionMode,
302
301
  });
303
302
  });
304
- setTimeout(() => {
305
- suppressRowSelectionEventsRef.current = false;
306
- }, SELECTION_EVENT_SUPPRESSION_MS);
307
303
  }, [
308
304
  selectionEnabled,
309
305
  getSelectionRowId,
310
- visibleRowIdsSet,
311
306
  selectionMode,
312
307
  setSelectionState,
308
+ getCurrentVisibleRowIds,
313
309
  ]);
314
310
  const selectAllState = useMemo(() => {
315
311
  if (!selectionEnabled ||
@@ -333,34 +329,37 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
333
329
  visibleRowIds,
334
330
  selectedRowIdsState,
335
331
  ]);
332
+ const selectAllStateRef = useRef(selectAllState);
333
+ selectAllStateRef.current = selectAllState;
336
334
  const handleSelectAllChanged = useCallback((isChecked) => {
337
335
  if (!selectionEnabled ||
338
336
  selectionMode !== 'multiple' ||
339
- !selectAllEnabled ||
340
- !visibleRowIds.length) {
337
+ !selectAllEnabled) {
338
+ return;
339
+ }
340
+ const currentVisibleRowIds = getCurrentVisibleRowIds();
341
+ if (!currentVisibleRowIds.length) {
341
342
  return;
342
343
  }
343
- if (shouldIgnoreSelectAllEvent({ isChecked, selectAllState })) {
344
+ if (shouldIgnoreSelectAllEvent({
345
+ isChecked,
346
+ selectAllState: selectAllStateRef.current,
347
+ })) {
344
348
  return;
345
349
  }
346
- suppressRowSelectionEventsRef.current = true;
347
350
  setSelectionState((current) => {
348
351
  return applySelectAllSelection({
349
352
  currentSelectedIds: current,
350
- visibleRowIds,
353
+ visibleRowIds: currentVisibleRowIds,
351
354
  isChecked,
352
355
  });
353
356
  });
354
- setTimeout(() => {
355
- suppressRowSelectionEventsRef.current = false;
356
- }, SELECTION_EVENT_SUPPRESSION_MS);
357
357
  }, [
358
358
  selectionEnabled,
359
359
  selectionMode,
360
360
  selectAllEnabled,
361
- visibleRowIds,
362
- selectAllState,
363
361
  setSelectionState,
362
+ getCurrentVisibleRowIds,
364
363
  ]);
365
364
  useEffect(() => {
366
365
  if (!selectionEnabled || selection?.deselectOnFilter === false) {
@@ -474,7 +473,11 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
474
473
  if (pagination?.labels?.pageSizeOption)
475
474
  labelProps.labelPageSizeOption = pagination.labels.pageSizeOption;
476
475
  return (React.createElement("div", { "data-testid": "default-pagination-wrapper", style: { marginTop: '16px' } },
477
- React.createElement(NvPaginationtable, { pageIndex: api.pageIndex, pageSize: api.pageSize, pageCount: api.pageCount, rowCount: api.rowCount, ...labelProps, onPageIndexChanged: (e) => api.setPageIndex(e.detail), onPageSizeChanged: (e) => api.setPageSize(e.detail) })));
476
+ React.createElement(NvPaginationtable, { pageIndex: api.pageIndex, pageSize: api.pageSize, pageCount: api.pageCount, rowCount: api.rowCount, ...labelProps, onPageIndexChanged: (e) => {
477
+ api.setPageIndex(e.detail);
478
+ }, onPageSizeChanged: (e) => {
479
+ api.setPageSize(e.detail);
480
+ } })));
478
481
  };
479
482
  const filteringAPI = useMemo(() => {
480
483
  if (!filtering) {
@@ -495,10 +498,69 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
495
498
  filteredRows: filteredRowModel.rows.length,
496
499
  };
497
500
  }, [filtering, globalFilterState, table, setGlobalFilterDebounced]);
501
+ const markSelectAllIntentFromKey = (event) => {
502
+ if (event.key === ' ' || event.key === 'Enter') {
503
+ if (selectAllIntentResetTimerRef.current) {
504
+ clearTimeout(selectAllIntentResetTimerRef.current);
505
+ }
506
+ selectAllIntentRef.current = true;
507
+ }
508
+ };
509
+ const scheduleSelectAllIntentReset = () => {
510
+ if (selectAllIntentResetTimerRef.current) {
511
+ clearTimeout(selectAllIntentResetTimerRef.current);
512
+ }
513
+ selectAllIntentResetTimerRef.current = setTimeout(() => {
514
+ selectAllIntentRef.current = false;
515
+ selectAllIntentResetTimerRef.current = null;
516
+ }, INTENT_RESET_DELAY_MS);
517
+ };
518
+ const markRowSelectionIntentFromKey = (event, rowId) => {
519
+ if (event.key === ' ' || event.key === 'Enter') {
520
+ if (rowSelectionIntentResetTimerRef.current) {
521
+ clearTimeout(rowSelectionIntentResetTimerRef.current);
522
+ }
523
+ rowSelectionIntentRef.current = rowId;
524
+ }
525
+ };
526
+ const scheduleRowSelectionIntentReset = (rowId) => {
527
+ if (rowSelectionIntentResetTimerRef.current) {
528
+ clearTimeout(rowSelectionIntentResetTimerRef.current);
529
+ }
530
+ rowSelectionIntentResetTimerRef.current = setTimeout(() => {
531
+ if (rowSelectionIntentRef.current === rowId) {
532
+ rowSelectionIntentRef.current = null;
533
+ }
534
+ rowSelectionIntentResetTimerRef.current = null;
535
+ }, INTENT_RESET_DELAY_MS);
536
+ };
537
+ const renderRowSelectionCheckbox = useCallback((row, rowIndex) => {
538
+ const rowId = getSelectionRowId(row);
539
+ return (React.createElement(NvFieldcheckbox, { "data-testid": `datatable-row-${rowIndex}-checkbox`, checked: isRowSelected(row), label: `Select row ${rowIndex + 1}`, hideLabel: true, onPointerDownCapture: () => {
540
+ if (rowSelectionIntentResetTimerRef.current) {
541
+ clearTimeout(rowSelectionIntentResetTimerRef.current);
542
+ }
543
+ rowSelectionIntentRef.current = rowId;
544
+ }, onKeyDownCapture: (event) => {
545
+ markRowSelectionIntentFromKey(event, rowId);
546
+ }, onBlurCapture: () => {
547
+ scheduleRowSelectionIntentReset(rowId);
548
+ }, onCheckedChanged: (event) => {
549
+ if (!rowId || rowSelectionIntentRef.current !== rowId) {
550
+ return;
551
+ }
552
+ try {
553
+ setRowSelection(row, event.detail);
554
+ }
555
+ finally {
556
+ rowSelectionIntentRef.current = null;
557
+ }
558
+ } }));
559
+ }, [getSelectionRowId, isRowSelected, setRowSelection]);
498
560
  return (React.createElement(React.Fragment, null,
499
561
  filteringAPI && renderFiltering && renderFiltering(filteringAPI),
500
562
  React.createElement(NvTable, { ...htmlProps },
501
- React.createElement("table", null,
563
+ React.createElement("table", { "data-row-click-enabled": rowClickEnabled ? 'true' : 'false' },
502
564
  React.createElement("thead", { "data-sticky-top": stickyHeader ? 'true' : undefined }, table.getHeaderGroups().map((headerGroup) => (React.createElement("tr", { key: headerGroup.id }, headerGroup.headers.map((header) => {
503
565
  const isSelectionHeader = header.id === '__selection__';
504
566
  const canSort = header.column.getCanSort();
@@ -512,10 +574,22 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
512
574
  : header.column.columnDef.enableResizing
513
575
  ? null
514
576
  : true }, isSelectionHeader ? (selectionMode === 'multiple' && selectAllEnabled ? (React.createElement("div", { className: "flex items-center justify-center" },
515
- React.createElement(NvFieldcheckbox, { "data-testid": "datatable-select-all-checkbox", checked: selectAllState === 'checked', indeterminate: selectAllState === 'indeterminate', label: "Select all rows", hideLabel: true, onCheckedChanged: (event) => {
516
- const checked = resolveCheckedChange(event);
517
- if (typeof checked === 'boolean') {
518
- handleSelectAllChanged(checked);
577
+ React.createElement(NvFieldcheckbox, { "data-testid": "datatable-select-all-checkbox", checked: selectAllState === 'checked', indeterminate: selectAllState === 'indeterminate', label: "Select all rows", hideLabel: true, onPointerDownCapture: () => {
578
+ if (selectAllIntentResetTimerRef.current) {
579
+ clearTimeout(selectAllIntentResetTimerRef.current);
580
+ }
581
+ selectAllIntentRef.current = true;
582
+ }, onKeyDownCapture: markSelectAllIntentFromKey, onBlurCapture: () => {
583
+ scheduleSelectAllIntentReset();
584
+ }, onCheckedChanged: (event) => {
585
+ if (!selectAllIntentRef.current) {
586
+ return;
587
+ }
588
+ try {
589
+ handleSelectAllChanged(event.detail);
590
+ }
591
+ finally {
592
+ selectAllIntentRef.current = false;
519
593
  }
520
594
  } }))) : null) : (React.createElement(NvTableheader, { sortable: canSort ? true : undefined, sortDirection: sortDirection || (canSort ? 'none' : undefined), onSortDirectionChanged: canSort
521
595
  ? header.column.getToggleSortingHandler()
@@ -525,14 +599,29 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
525
599
  }))))),
526
600
  React.createElement("tbody", null, tableRows.map((row, index) => {
527
601
  const isLastRow = isInfiniteScroll && index === tableRows.length - 1;
528
- return (React.createElement("tr", { key: row.id, "data-testid": `datatable-row-${index}`, ref: isLastRow ? lastRowRef : undefined }, row.getVisibleCells().map((cell) => (React.createElement("td", { key: cell.id, "data-testid": cell.column.id === '__selection__'
529
- ? 'datatable-cell-selection'
530
- : `datatable-cell-${cell.column.id}` }, cell.column.id === '__selection__' ? (React.createElement(NvFieldcheckbox, { "data-testid": `datatable-row-${index}-checkbox`, checked: isRowSelected(row.original), label: `Select row ${index + 1}`, hideLabel: true, onCheckedChanged: (event) => {
531
- const checked = resolveCheckedChange(event);
532
- if (typeof checked === 'boolean') {
533
- setRowSelection(row.original, checked);
602
+ const stableRowKey = getSelectionRowId(row.original) || row.id;
603
+ return (React.createElement("tr", { key: stableRowKey, "data-testid": `datatable-row-${index}`, ref: isLastRow ? lastRowRef : undefined, className: rowClickEnabled ? 'nv-datatable-row-clickable' : undefined, tabIndex: rowClickEnabled ? 0 : undefined, onClick: rowClickEnabled
604
+ ? () => onRowClick?.({ row: row.original, rowIndex: index })
605
+ : undefined, onKeyDown: rowClickEnabled
606
+ ? (event) => {
607
+ if (event.currentTarget !== event.target) {
608
+ return;
609
+ }
610
+ if (event.key === 'Enter' || event.key === ' ') {
611
+ event.preventDefault();
612
+ onRowClick?.({
613
+ row: row.original,
614
+ rowIndex: index,
615
+ });
616
+ }
534
617
  }
535
- } })) : (flexRender(cell.column.columnDef.cell, cell.getContext())))))));
618
+ : undefined }, row.getVisibleCells().map((cell) => (React.createElement("td", { key: cell.id, "data-testid": cell.column.id === '__selection__'
619
+ ? 'datatable-cell-selection'
620
+ : `datatable-cell-${cell.column.id}`, onClick: cell.column.id === '__selection__'
621
+ ? (event) => event.stopPropagation()
622
+ : undefined }, cell.column.id === '__selection__'
623
+ ? renderRowSelectionCheckbox(row.original, index)
624
+ : flexRender(cell.column.columnDef.cell, cell.getContext()))))));
536
625
  })))),
537
626
  paginationAPI &&
538
627
  (renderPagination
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { NvDatatableProps } from './types';
3
- declare function NvDatatable<T>({ mode, columns, rows, pagination, sorting, filtering, selection, renderPagination, renderFiltering, stickyHeader, ...htmlProps }: NvDatatableProps<T>): React.JSX.Element;
3
+ declare function NvDatatable<T>({ mode, columns, rows, pagination, sorting, filtering, selection, renderPagination, renderFiltering, onRowClick, stickyHeader, ...htmlProps }: NvDatatableProps<T>): React.JSX.Element;
4
4
  declare namespace NvDatatable {
5
5
  var displayName: string;
6
6
  }
@@ -9,11 +9,16 @@ export type NvDatatableProps<T> = {
9
9
  sorting?: NvDatatableSortingConfig;
10
10
  filtering?: NvDatatableFilteringConfig<T>;
11
11
  selection?: NvDatatableSelectionConfigWithMode<T>;
12
+ onRowClick?: (args: NvDatatableRowClickEventArgs<T>) => void;
12
13
  renderPagination?: (api: NvDatatableRenderPaginationAPI) => React.ReactNode;
13
14
  renderFiltering?: (api: NvDatatableRenderFilteringAPI) => React.ReactNode;
14
15
  stickyHeader?: boolean;
15
16
  suppressHydrationWarning?: boolean;
16
17
  } & Pick<ComponentProps<'div'>, 'className' | 'style'>;
18
+ export interface NvDatatableRowClickEventArgs<T> {
19
+ row: T;
20
+ rowIndex: number;
21
+ }
17
22
  export type NvDatatableSelectionMode = 'none' | 'single' | 'multiple';
18
23
  export interface NvDatatableSelectionConfig<T> {
19
24
  mode?: NvDatatableSelectionMode;
@@ -1,5 +1,6 @@
1
1
  export * from './generated/components';
2
2
  export * from '@nova-design-system/nova-webcomponents/constants';
3
+ export { type IconName } from '@nova-design-system/nova-webcomponents';
3
4
  export { defineCustomElements } from '@nova-design-system/nova-webcomponents/loader';
4
5
  export { useNotifications, NotificationProvider, type NotificationOptions, type NotificationContextValue, type NotificationProviderProps, type Notification, } from './providers/NotificationProvider';
5
6
  export * from './components/NvDatatable';
@@ -1,5 +1,5 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { type NotificationPosition, type NotificationEmphasis, FeedbackColors } from '../index';
2
+ import { type NotificationPosition, type NotificationEmphasis, type IconName, FeedbackColors } from '../index';
3
3
  export interface NotificationAction {
4
4
  label: string;
5
5
  onClick: () => void;
@@ -11,7 +11,7 @@ export interface NotificationOptions {
11
11
  dismissible?: boolean;
12
12
  emphasis?: `${NotificationEmphasis}`;
13
13
  feedback?: `${FeedbackColors}`;
14
- icon?: string;
14
+ icon?: `${IconName}`;
15
15
  actions?: NotificationAction[];
16
16
  actionSlot?: React.ReactElement;
17
17
  duration?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nova-design-system/nova-react",
3
- "version": "3.28.0",
3
+ "version": "3.29.0",
4
4
  "description": "Nova is a design system created by Elia Group to empower creators to efficiently build solutions that people love to use.",
5
5
  "author": "Elia Group",
6
6
  "homepage": "https://nova.eliagroup.io",