@nova-design-system/nova-react 3.27.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 (83) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/generated/components.server.js +24 -0
  3. package/dist/cjs/index-DgKzi_Pd.js +10208 -0
  4. package/dist/cjs/index.js +4 -1
  5. package/dist/cjs/nova-datetime-CyL2J6O4-0Y9g3rfI.js +10531 -0
  6. package/dist/cjs/{nv-accordion-item.entry-Gn0QFETn.js → nv-accordion-item.entry-D1o0gC5w.js} +3 -3
  7. package/dist/cjs/{nv-accordion.entry-BooppL5C.js → nv-accordion.entry-DYJtq9Az.js} +1 -1
  8. package/dist/cjs/{nv-alert.entry-CDmHhq86.js → nv-alert.entry-CMtCdHmk.js} +2 -2
  9. package/dist/cjs/{nv-avatar.entry-pYMjQvxK.js → nv-avatar.entry-B6e7aG4W.js} +1 -1
  10. package/dist/cjs/{nv-badge_2.entry-BneUUyR7.js → nv-badge_2.entry-RD3j0bJM.js} +8 -7
  11. package/dist/cjs/{nv-breadcrumb.entry-B7CGcJ_l.js → nv-breadcrumb.entry-DgpqY2fr.js} +1 -1
  12. package/dist/cjs/{nv-breadcrumbs.entry-BNKz4ehX.js → nv-breadcrumbs.entry-CZgcDUw5.js} +1 -1
  13. package/dist/cjs/{nv-button.entry-DDCkNY_S.js → nv-button.entry-DR9NaRxG.js} +1 -1
  14. package/dist/cjs/{nv-buttongroup.entry-Bio-SWcx.js → nv-buttongroup.entry-qO8r7WqG.js} +1 -1
  15. package/dist/cjs/{nv-calendar.entry-BoNFEz6W.js → nv-calendar.entry-DRlv3Xph.js} +1 -1
  16. package/dist/cjs/{nv-col.entry-BHTqFTHl.js → nv-col.entry-B7utJttP.js} +2 -2
  17. package/dist/cjs/{nv-datagrid.entry-Bw_sqaMu.js → nv-datagrid.entry-digYmlnA.js} +1 -1
  18. package/dist/cjs/{nv-datagridcolumn.entry-Z-RXn3ak.js → nv-datagridcolumn.entry-UwaDi-Hr.js} +1 -1
  19. package/dist/cjs/nv-datetest.entry-BJtWaM2T.js +69 -0
  20. package/dist/cjs/nv-datetimetest.entry-WaNPcHxh.js +57 -0
  21. package/dist/cjs/{nv-dialog.entry-ByZkK4F9.js → nv-dialog.entry-BI_mFT2G.js} +2 -2
  22. package/dist/cjs/{nv-dialogfooter_2.entry-Bddcz6HA.js → nv-dialogfooter_2.entry-DU2ClBUR.js} +7 -5
  23. package/dist/cjs/{nv-drawer.entry-660dihzy.js → nv-drawer.entry-BKF4CyOt.js} +2 -2
  24. package/dist/cjs/{nv-drawerfooter_2.entry-D6EA-3-u.js → nv-drawerfooter_2.entry-BSyUs7_4.js} +5 -3
  25. package/dist/cjs/{nv-fieldcheckbox.entry-Bm6qurcS.js → nv-fieldcheckbox.entry-l5FWToQi.js} +5 -5
  26. package/dist/cjs/{nv-fielddate.entry-DW9RQ_dD.js → nv-fielddate.entry-BfviesNp.js} +8 -8
  27. package/dist/cjs/{nv-fielddaterange.entry-m3dY0P2v.js → nv-fielddaterange.entry-BvDkPLXG.js} +8 -8
  28. package/dist/cjs/{nv-fielddropdown.entry-DkARzQix.js → nv-fielddropdown.entry-B-nYjuMt.js} +6 -6
  29. package/dist/cjs/{nv-fielddropdownitem.entry-DKG1bO2H.js → nv-fielddropdownitem.entry-Dxqyb9DN.js} +2 -2
  30. package/dist/cjs/{nv-fieldmultiselect.entry-B4As0NxI.js → nv-fieldmultiselect.entry-CgO1RP0W.js} +115 -86
  31. package/dist/cjs/{nv-fieldnumber.entry-DHBPKUcr.js → nv-fieldnumber.entry-stgdLi7x.js} +6 -6
  32. package/dist/cjs/{nv-fieldpassword.entry-DxEaIvRh.js → nv-fieldpassword.entry-CHA3JAUd.js} +6 -6
  33. package/dist/cjs/{nv-fieldradio.entry-MSrPlSzv.js → nv-fieldradio.entry-DHavVjB-.js} +4 -4
  34. package/dist/cjs/{nv-fieldselect.entry-qMAtqdcr.js → nv-fieldselect.entry-Dq5AsOFt.js} +7 -7
  35. package/dist/cjs/{nv-fieldslider.entry-CHdMG-lt.js → nv-fieldslider.entry-SlF3BBUW.js} +4 -4
  36. package/dist/cjs/{nv-fieldtext.entry-tbYFUJP9.js → nv-fieldtext.entry-BYCc8SyD.js} +6 -6
  37. package/dist/cjs/{nv-fieldtextarea.entry-D7b3-4Ar.js → nv-fieldtextarea.entry-96JCOb9L.js} +5 -5
  38. package/dist/cjs/{nv-fieldtime.entry-TSPljEeW.js → nv-fieldtime.entry-BdzjxkaK.js} +15 -12
  39. package/dist/cjs/nv-icon.entry-CSRxi6BH.js +80 -0
  40. package/dist/cjs/{nv-iconbutton_2.entry-Ch0rfD12.js → nv-iconbutton_2.entry-BtDxwMFD.js} +3 -3
  41. package/dist/cjs/{nv-menu.entry-BKLyCbyy.js → nv-menu.entry-aX39SPH8.js} +2 -2
  42. package/dist/cjs/{nv-menuitem.entry-BBK6uQav.js → nv-menuitem.entry-DzSp52G2.js} +2 -2
  43. package/dist/cjs/{nv-notification-bullet.entry-Dk0oaUTe.js → nv-notification-bullet.entry-D2yOXj46.js} +1 -1
  44. package/dist/cjs/{nv-notification.entry-DVUXuj9T.js → nv-notification.entry-BRXHbtT8.js} +2 -2
  45. package/dist/cjs/{nv-notificationcontainer.entry-BQbB0vJ8.js → nv-notificationcontainer.entry-YNIM2_ah.js} +2 -14
  46. package/dist/cjs/{nv-pagination-nav.entry-BON8ve70.js → nv-pagination-nav.entry-uY1nT9aT.js} +2 -2
  47. package/dist/cjs/{nv-paginationtable.entry-pT2Qx3xY.js → nv-paginationtable.entry-mr5KYXVC.js} +2 -2
  48. package/dist/cjs/{nv-popover.entry-91VegZzi.js → nv-popover.entry-_9ARKM70.js} +2 -2
  49. package/dist/cjs/{nv-row.entry-C_75GYAm.js → nv-row.entry-BMQvcqlU.js} +2 -2
  50. package/dist/cjs/{nv-sidebar.entry-XIcDybjF.js → nv-sidebar.entry-C4HTjJmz.js} +2 -2
  51. package/dist/cjs/{nv-sidebarcontent.entry-JSf1Mb0x.js → nv-sidebarcontent.entry-llnRwVuj.js} +2 -2
  52. package/dist/cjs/{nv-sidebardivider.entry-XZ1hYAgj.js → nv-sidebardivider.entry--rQv8d5T.js} +2 -2
  53. package/dist/cjs/{nv-sidebarfooter.entry-BHO9gh6y.js → nv-sidebarfooter.entry-C5R0sUI_.js} +2 -2
  54. package/dist/cjs/{nv-sidebargroup.entry-DAb7dWFF.js → nv-sidebargroup.entry-DdrpLbU7.js} +2 -2
  55. package/dist/cjs/{nv-sidebarheader.entry-BksZP_km.js → nv-sidebarheader.entry-HuBPIsyf.js} +2 -2
  56. package/dist/cjs/{nv-sidebarlogo.entry-BntEWmH_.js → nv-sidebarlogo.entry-TZNPoqnA.js} +2 -2
  57. package/dist/cjs/{nv-sidebarnavitem.entry-DSu34C34.js → nv-sidebarnavitem.entry-BWc3mF5I.js} +3 -3
  58. package/dist/cjs/{nv-sidebarnavsubitem.entry-Y8Ex0_Lr.js → nv-sidebarnavsubitem.entry-DekANwO8.js} +2 -2
  59. package/dist/cjs/{nv-split.entry-BDr6FxCd.js → nv-split.entry-BOwOB8FW.js} +2 -2
  60. package/dist/cjs/{nv-stack.entry-CChjKrA3.js → nv-stack.entry-CaTiSLGN.js} +2 -2
  61. package/dist/cjs/{nv-table.entry-CfqHpj1L.js → nv-table.entry-LFZaS0Dy.js} +3 -3
  62. package/dist/cjs/{nv-tableheader.entry-C61xO9Ra.js → nv-tableheader.entry-CK50S8xW.js} +2 -2
  63. package/dist/cjs/nv-timetest.entry-A9elKwkf.js +75 -0
  64. package/dist/cjs/{nv-toggle.entry-B0HofO36.js → nv-toggle.entry-BGu-6rEs.js} +3 -3
  65. package/dist/cjs/{nv-togglebutton.entry-CzyNiZyK.js → nv-togglebutton.entry-BzAcBOgN.js} +2 -2
  66. package/dist/cjs/{nv-togglebuttongroup.entry-TizvIVlB.js → nv-togglebuttongroup.entry-CIWp7IXc.js} +2 -2
  67. package/dist/cjs/{nv-tooltip.entry-0kcGcQZo.js → nv-tooltip.entry-CoGkSHpv.js} +9 -4
  68. package/dist/components/NvDatatable/NvDatatable.js +384 -65
  69. package/dist/components/NvDatatable/selectionState.js +45 -0
  70. package/dist/components/NvDatatable/selectionState.test.js +74 -0
  71. package/dist/generated/components.js +38 -0
  72. package/dist/generated/components.server.js +21 -0
  73. package/dist/types/components/NvDatatable/NvDatatable.d.ts +1 -1
  74. package/dist/types/components/NvDatatable/selectionState.d.ts +21 -0
  75. package/dist/types/components/NvDatatable/selectionState.test.d.ts +1 -0
  76. package/dist/types/components/NvDatatable/types.d.ts +22 -0
  77. package/dist/types/generated/components.d.ts +51 -1
  78. package/dist/types/generated/components.server.d.ts +51 -1
  79. package/dist/types/index.d.ts +1 -0
  80. package/dist/types/providers/NotificationProvider.d.ts +2 -2
  81. package/package.json +6 -4
  82. package/dist/cjs/index-clzdrCkN.js +0 -9713
  83. package/dist/cjs/nv-icon.entry-BtmsouTL.js +0 -80
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-clzdrCkN.js');
3
+ var index = require('./index-DgKzi_Pd.js');
4
4
  require('@stencil/react-output-target/runtime');
5
5
  require('react');
6
6
  require('react-dom');
@@ -57,7 +57,7 @@ const NvTogglebutton = class {
57
57
  /****************************************************************************/
58
58
  //#region RENDER
59
59
  render() {
60
- return (index.h(index.Host, { key: 'd619862ae9db76a7ce34b05c4974d40e731a576e', role: "button", tabindex: "0", "aria-pressed": String(this.active), onClick: this.handleClick }, index.h("slot", { key: '5bb9a32e26c3c404a9d2bab2bf7d573a501296a5' })));
60
+ return (index.h(index.Host, { key: '8e700d38ba155b977d4efa0d9e01b41e73fc4413', role: "button", tabindex: "0", "aria-pressed": String(this.active), onClick: this.handleClick }, index.h("slot", { key: '92427015839f98b0d9238b8df6af8e08ee511153' })));
61
61
  }
62
62
  get el() { return index.getElement(this); }
63
63
  };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-clzdrCkN.js');
3
+ var index = require('./index-DgKzi_Pd.js');
4
4
  require('@stencil/react-output-target/runtime');
5
5
  require('react');
6
6
  require('react-dom');
@@ -156,7 +156,7 @@ const NvTogglebuttongroup = class {
156
156
  /****************************************************************************/
157
157
  //#region RENDER
158
158
  render() {
159
- return (index.h(index.Host, { key: '93a5693197f8b41b18ecef0132168e7f209ec86a' }, index.h("slot", { key: '3085cde261575115286c295eb416063ef14a7bf2' })));
159
+ return (index.h(index.Host, { key: '2026cb95db8b57f2a1cd4ea53608f336843b63fe' }, index.h("slot", { key: '5c06ab59d6c99e789c3a1c3a8a4842a0772ca3a2' })));
160
160
  }
161
161
  get el() { return index.getElement(this); }
162
162
  static get watchers() { return {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-clzdrCkN.js');
3
+ var index = require('./index-DgKzi_Pd.js');
4
4
  require('@stencil/react-output-target/runtime');
5
5
  require('react');
6
6
  require('react-dom');
@@ -39,15 +39,20 @@ const NvTooltip = class {
39
39
  /****************************************************************************/
40
40
  //#region LIFECYCLE
41
41
  componentDidRender() {
42
- if (this.triggerElement || !this.defaultSlot)
42
+ if (!this.popoverElement)
43
43
  return;
44
- this.popoverElement.triggerElement = this.defaultSlot?.nextSibling;
44
+ const trigger = this.triggerElement ??
45
+ this.defaultSlot?.nextElementSibling ??
46
+ this.el.querySelector(':scope > :not(nv-popover):not(slot):not([slot])');
47
+ if (trigger && this.popoverElement.triggerElement !== trigger) {
48
+ this.popoverElement.triggerElement = trigger;
49
+ }
45
50
  }
46
51
  //#endregion LIFECYCLE
47
52
  /****************************************************************************/
48
53
  //#region RENDER
49
54
  render() {
50
- return (index.h(index.Host, { key: 'f01e67c6f67c35beba40681c4bcd109886bbdbcf' }, index.h("nv-popover", { key: '47cd75161715752aeeae29223606e21c617a271a', ref: el => (this.popoverElement = el), triggerMode: "hover", hasArrow: true, placement: this.placement, strategy: this.strategy, triggerElement: this.triggerElement, groupName: 'tooltip', enterDelay: this.enterDelay, onOpenChanged: e => this.openChanged.emit(e.detail) }, index.h("p", { key: 'fe5b09a6c0e4a67a50c9f43cb8344cee68e197f5', slot: "content" }, this.message), index.h("slot", { key: '4ef23e294baa1d5e60236ffec4a26439610030c1', name: "content" })), index.h("slot", { key: '5d8486923cd7cf99da69f4fa4d319dd5b5999cfa', ref: el => (this.defaultSlot = el) })));
55
+ return (index.h(index.Host, { key: '8285fbb412cb45cc7baf2936f0405c0438395c8d' }, index.h("nv-popover", { key: '29d70e9baa826de35d2cdca88f48610d70ec55e4', ref: el => (this.popoverElement = el), triggerMode: "hover", hasArrow: true, placement: this.placement, strategy: this.strategy, groupName: 'tooltip', enterDelay: this.enterDelay, onOpenChanged: e => this.openChanged.emit(e.detail) }, index.h("p", { key: 'a9c907fa96f9c4108a8d84cec53981218c060811', slot: "content" }, this.message), index.h("slot", { key: 'af1b7ac8069071fe2d41e925f1c0090f8b6aaab5', name: "content" })), index.h("slot", { key: 'e5d3c6d16fb7e4a682b66e0016d9e4e30e5647e0', ref: el => (this.defaultSlot = el) })));
51
56
  }
52
57
  get el() { return index.getElement(this); }
53
58
  };
@@ -1,12 +1,24 @@
1
1
  import React, { useMemo, useState, useEffect, useRef, useCallback, } from 'react';
2
2
  import { flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, getFilteredRowModel, useReactTable, } from '@tanstack/react-table';
3
- import { NvTable, NvTableheader, NvPaginationtable, } from '../../generated/components';
3
+ import { NvTable, NvTableheader, NvPaginationtable, NvFieldcheckbox, } from '../../generated/components';
4
4
  import { defineCustomElement as defineNvPaginationtable } from '@nova-design-system/nova-webcomponents/dist/components/nv-paginationtable.js';
5
5
  if (typeof window !== 'undefined' &&
6
6
  !customElements.get('nv-paginationtable')) {
7
7
  defineNvPaginationtable();
8
8
  }
9
- function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filtering, renderPagination, renderFiltering, stickyHeader, ...htmlProps }) {
9
+ import { applyRowSelection, applySelectAllSelection, shouldIgnoreSelectAllEvent, } from './selectionState';
10
+ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filtering, selection, renderPagination, renderFiltering, onRowClick, stickyHeader, ...htmlProps }) {
11
+ const areSetsEqual = useCallback((a, b) => {
12
+ if (a.size !== b.size) {
13
+ return false;
14
+ }
15
+ for (const value of a) {
16
+ if (!b.has(value)) {
17
+ return false;
18
+ }
19
+ }
20
+ return true;
21
+ }, []);
10
22
  const isInfiniteScroll = pagination?.infinite && mode === 'server';
11
23
  const [paginationState, setPaginationState] = useState({
12
24
  pageIndex: 0,
@@ -14,7 +26,19 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
14
26
  });
15
27
  const [sortingState, setSortingState] = useState(sorting?.sortState || []);
16
28
  const [globalFilterState, setGlobalFilterState] = useState(filtering?.filterState);
29
+ const [selectedRowIdsState, setSelectedRowIdsState] = useState(() => new Set(selection?.selectedRowIds ?? []));
30
+ const selectedRowIdsRef = useRef(new Set(selection?.selectedRowIds ?? []));
31
+ const selectionMode = selection?.mode ?? 'none';
32
+ const selectionEnabled = selectionMode !== 'none';
33
+ const selectAllEnabled = selection?.enableSelectAll ?? true;
34
+ const rowClickEnabled = typeof onRowClick === 'function';
35
+ const getSelectionRowId = useCallback((row) => selection?.getRowId?.(row) ?? '', [selection]);
17
36
  const lastRowRef = useRef(null);
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;
18
42
  const debouncedSetFilter = useRef(null);
19
43
  const setGlobalFilterDebounced = useCallback((value) => {
20
44
  const debounceMs = filtering?.debounceMs ?? 300;
@@ -33,6 +57,12 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
33
57
  if (debouncedSetFilter.current) {
34
58
  clearTimeout(debouncedSetFilter.current);
35
59
  }
60
+ if (selectAllIntentResetTimerRef.current) {
61
+ clearTimeout(selectAllIntentResetTimerRef.current);
62
+ }
63
+ if (rowSelectionIntentResetTimerRef.current) {
64
+ clearTimeout(rowSelectionIntentResetTimerRef.current);
65
+ }
36
66
  };
37
67
  }, []);
38
68
  useEffect(() => {
@@ -40,60 +70,89 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
40
70
  setGlobalFilterState(filtering.filterState);
41
71
  }
42
72
  }, [filtering?.filterState]);
43
- const tableColumns = useMemo(() => columns
44
- .filter((col) => !col.hidden)
45
- .map((col) => {
46
- const headerName = col.headerName || String(col.field);
47
- const columnDef = {
48
- accessorKey: col.field,
49
- accessorFn: col.valueFormatter
50
- ? (row) => {
51
- const rawValue = row[col.field];
52
- return col.valueFormatter({
53
- value: rawValue,
54
- row,
55
- field: col.field,
56
- });
57
- }
58
- : undefined,
59
- header: col.renderHeader
60
- ? () => col.renderHeader({
61
- headerName,
62
- field: col.field,
63
- })
64
- : headerName,
65
- size: col.width,
66
- enableResizing: col.resizable ?? true,
67
- enableSorting: sorting ? col.sortable ?? true : false,
68
- cell: (context) => {
69
- const value = context.getValue();
70
- const row = context.row.original;
71
- const rowIndex = context.row.index;
72
- if (col.renderCell) {
73
- return col.renderCell({
74
- value,
75
- row,
76
- field: col.field,
77
- rowIndex,
78
- });
79
- }
80
- return value;
81
- },
82
- };
83
- if (col.sortingFn !== undefined) {
84
- columnDef.sortingFn = col.sortingFn;
85
- }
86
- if (col.sortDescFirst !== undefined) {
87
- columnDef.sortDescFirst = col.sortDescFirst;
88
- }
89
- if (col.invertSorting !== undefined) {
90
- columnDef.invertSorting = col.invertSorting;
73
+ if (selectionEnabled && !selection?.getRowId) {
74
+ throw new Error('NvDatatable selection requires selection.getRowId when selection.mode is not "none".');
75
+ }
76
+ useEffect(() => {
77
+ if (selection?.selectedRowIds) {
78
+ const controlledIds = new Set(selection.selectedRowIds);
79
+ if (!areSetsEqual(selectedRowIdsState, controlledIds)) {
80
+ selectedRowIdsRef.current = controlledIds;
81
+ setSelectedRowIdsState(controlledIds);
82
+ }
91
83
  }
92
- if (col.sortUndefined !== undefined) {
93
- columnDef.sortUndefined = col.sortUndefined;
84
+ }, [selection?.selectedRowIds, selectedRowIdsState, areSetsEqual]);
85
+ useEffect(() => {
86
+ selectedRowIdsRef.current = selectedRowIdsState;
87
+ }, [selectedRowIdsState]);
88
+ const tableColumns = useMemo(() => {
89
+ const dataColumns = columns
90
+ .filter((col) => !col.hidden)
91
+ .map((col) => {
92
+ const headerName = col.headerName || String(col.field);
93
+ const columnDef = {
94
+ accessorKey: col.field,
95
+ accessorFn: col.valueFormatter
96
+ ? (row) => {
97
+ const rawValue = row[col.field];
98
+ return col.valueFormatter({
99
+ value: rawValue,
100
+ row,
101
+ field: col.field,
102
+ });
103
+ }
104
+ : undefined,
105
+ header: col.renderHeader
106
+ ? () => col.renderHeader({
107
+ headerName,
108
+ field: col.field,
109
+ })
110
+ : headerName,
111
+ size: col.width,
112
+ enableResizing: col.resizable ?? true,
113
+ enableSorting: sorting ? col.sortable ?? true : false,
114
+ cell: (context) => {
115
+ const value = context.getValue();
116
+ const row = context.row.original;
117
+ const rowIndex = context.row.index;
118
+ if (col.renderCell) {
119
+ return col.renderCell({
120
+ value,
121
+ row,
122
+ field: col.field,
123
+ rowIndex,
124
+ });
125
+ }
126
+ return value;
127
+ },
128
+ };
129
+ if (col.sortingFn !== undefined) {
130
+ columnDef.sortingFn = col.sortingFn;
131
+ }
132
+ if (col.sortDescFirst !== undefined) {
133
+ columnDef.sortDescFirst = col.sortDescFirst;
134
+ }
135
+ if (col.invertSorting !== undefined) {
136
+ columnDef.invertSorting = col.invertSorting;
137
+ }
138
+ if (col.sortUndefined !== undefined) {
139
+ columnDef.sortUndefined = col.sortUndefined;
140
+ }
141
+ return columnDef;
142
+ });
143
+ if (!selectionEnabled) {
144
+ return dataColumns;
94
145
  }
95
- return columnDef;
96
- }), [columns, sorting]);
146
+ const selectionColumn = {
147
+ id: '__selection__',
148
+ header: '',
149
+ size: selection?.checkboxColumnWidth ?? 48,
150
+ enableResizing: false,
151
+ enableSorting: false,
152
+ cell: () => null,
153
+ };
154
+ return [selectionColumn, ...dataColumns];
155
+ }, [columns, sorting, selectionEnabled, selection?.checkboxColumnWidth]);
97
156
  const tableConfig = useMemo(() => {
98
157
  const baseConfig = {
99
158
  data: rows,
@@ -187,6 +246,158 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
187
246
  mode,
188
247
  ]);
189
248
  const table = useReactTable(tableConfig);
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]);
256
+ const setSelectionState = useCallback((nextSelectedIds) => {
257
+ const current = new Set(selectedRowIdsRef.current);
258
+ const nextRaw = typeof nextSelectedIds === 'function'
259
+ ? nextSelectedIds(new Set(current))
260
+ : nextSelectedIds;
261
+ const next = new Set(nextRaw);
262
+ if (areSetsEqual(current, next)) {
263
+ return;
264
+ }
265
+ const hasExplicitSelectionIntent = selectAllIntentRef.current || rowSelectionIntentRef.current !== null;
266
+ if (!hasExplicitSelectionIntent && next.size < current.size) {
267
+ return;
268
+ }
269
+ selectedRowIdsRef.current = next;
270
+ setSelectedRowIdsState(next);
271
+ selection?.onSelectionChange?.(new Set(next));
272
+ }, [selection, areSetsEqual]);
273
+ const isRowSelected = useCallback((row) => {
274
+ if (!selectionEnabled) {
275
+ return false;
276
+ }
277
+ const rowId = getSelectionRowId(row);
278
+ return !!rowId && selectedRowIdsState.has(rowId);
279
+ }, [selectionEnabled, getSelectionRowId, selectedRowIdsState]);
280
+ const visibleRowIds = useMemo(() => tableRows
281
+ .map((row) => getSelectionRowId(row.original))
282
+ .filter((id) => !!id), [tableRows, getSelectionRowId]);
283
+ const setRowSelection = useCallback((row, isChecked) => {
284
+ if (!selectionEnabled) {
285
+ return;
286
+ }
287
+ const rowId = getSelectionRowId(row);
288
+ if (!rowId) {
289
+ return;
290
+ }
291
+ const currentVisibleRowIdsSet = new Set(getCurrentVisibleRowIds());
292
+ if (!currentVisibleRowIdsSet.has(rowId)) {
293
+ return;
294
+ }
295
+ setSelectionState((current) => {
296
+ return applyRowSelection({
297
+ currentSelectedIds: current,
298
+ rowId,
299
+ isChecked,
300
+ selectionMode,
301
+ });
302
+ });
303
+ }, [
304
+ selectionEnabled,
305
+ getSelectionRowId,
306
+ selectionMode,
307
+ setSelectionState,
308
+ getCurrentVisibleRowIds,
309
+ ]);
310
+ const selectAllState = useMemo(() => {
311
+ if (!selectionEnabled ||
312
+ selectionMode !== 'multiple' ||
313
+ !selectAllEnabled ||
314
+ !visibleRowIds.length) {
315
+ return 'unchecked';
316
+ }
317
+ const selectedCount = visibleRowIds.filter((id) => selectedRowIdsState.has(id)).length;
318
+ if (selectedCount === 0) {
319
+ return 'unchecked';
320
+ }
321
+ if (selectedCount === visibleRowIds.length) {
322
+ return 'checked';
323
+ }
324
+ return 'indeterminate';
325
+ }, [
326
+ selectionEnabled,
327
+ selectionMode,
328
+ selectAllEnabled,
329
+ visibleRowIds,
330
+ selectedRowIdsState,
331
+ ]);
332
+ const selectAllStateRef = useRef(selectAllState);
333
+ selectAllStateRef.current = selectAllState;
334
+ const handleSelectAllChanged = useCallback((isChecked) => {
335
+ if (!selectionEnabled ||
336
+ selectionMode !== 'multiple' ||
337
+ !selectAllEnabled) {
338
+ return;
339
+ }
340
+ const currentVisibleRowIds = getCurrentVisibleRowIds();
341
+ if (!currentVisibleRowIds.length) {
342
+ return;
343
+ }
344
+ if (shouldIgnoreSelectAllEvent({
345
+ isChecked,
346
+ selectAllState: selectAllStateRef.current,
347
+ })) {
348
+ return;
349
+ }
350
+ setSelectionState((current) => {
351
+ return applySelectAllSelection({
352
+ currentSelectedIds: current,
353
+ visibleRowIds: currentVisibleRowIds,
354
+ isChecked,
355
+ });
356
+ });
357
+ }, [
358
+ selectionEnabled,
359
+ selectionMode,
360
+ selectAllEnabled,
361
+ setSelectionState,
362
+ getCurrentVisibleRowIds,
363
+ ]);
364
+ useEffect(() => {
365
+ if (!selectionEnabled || selection?.deselectOnFilter === false) {
366
+ return;
367
+ }
368
+ if (mode !== 'client' || !filtering) {
369
+ return;
370
+ }
371
+ const filterVisibleRows = table
372
+ .getFilteredRowModel()
373
+ .rows.map((row) => row.original);
374
+ const visibleIds = new Set(filterVisibleRows
375
+ .map((row) => getSelectionRowId(row))
376
+ .filter((id) => !!id));
377
+ const currentSelection = selection?.selectedRowIds ?? selectedRowIdsState;
378
+ const next = new Set();
379
+ currentSelection.forEach((id) => {
380
+ if (visibleIds.has(id)) {
381
+ next.add(id);
382
+ }
383
+ });
384
+ if (!areSetsEqual(currentSelection, next)) {
385
+ setSelectedRowIdsState(next);
386
+ selection?.onSelectionChange?.(new Set(next));
387
+ }
388
+ }, [
389
+ areSetsEqual,
390
+ selectionEnabled,
391
+ selection?.deselectOnFilter,
392
+ selection?.onSelectionChange,
393
+ selection?.selectedRowIds,
394
+ mode,
395
+ filtering,
396
+ globalFilterState,
397
+ table,
398
+ selectedRowIdsState,
399
+ getSelectionRowId,
400
+ ]);
190
401
  useEffect(() => {
191
402
  if (mode === 'server' &&
192
403
  !pagination?.infinite &&
@@ -253,7 +464,6 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
253
464
  mode,
254
465
  isInfiniteScroll,
255
466
  ]);
256
- const tableRows = table.getRowModel().rows;
257
467
  const renderDefaultPagination = (api) => {
258
468
  const labelProps = {};
259
469
  if (pagination?.labels?.page)
@@ -263,7 +473,11 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
263
473
  if (pagination?.labels?.pageSizeOption)
264
474
  labelProps.labelPageSizeOption = pagination.labels.pageSizeOption;
265
475
  return (React.createElement("div", { "data-testid": "default-pagination-wrapper", style: { marginTop: '16px' } },
266
- 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
+ } })));
267
481
  };
268
482
  const filteringAPI = useMemo(() => {
269
483
  if (!filtering) {
@@ -284,25 +498,130 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
284
498
  filteredRows: filteredRowModel.rows.length,
285
499
  };
286
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]);
287
560
  return (React.createElement(React.Fragment, null,
288
561
  filteringAPI && renderFiltering && renderFiltering(filteringAPI),
289
562
  React.createElement(NvTable, { ...htmlProps },
290
- React.createElement("table", null,
563
+ React.createElement("table", { "data-row-click-enabled": rowClickEnabled ? 'true' : 'false' },
291
564
  React.createElement("thead", { "data-sticky-top": stickyHeader ? 'true' : undefined }, table.getHeaderGroups().map((headerGroup) => (React.createElement("tr", { key: headerGroup.id }, headerGroup.headers.map((header) => {
565
+ const isSelectionHeader = header.id === '__selection__';
292
566
  const canSort = header.column.getCanSort();
293
567
  const sortDirection = header.column.getIsSorted();
294
- return (React.createElement("th", { key: header.id, "data-testid": `datatable-header-${header.id}`, style: {
568
+ return (React.createElement("th", { key: header.id, "data-testid": isSelectionHeader
569
+ ? 'datatable-header-selection'
570
+ : `datatable-header-${header.id}`, style: {
295
571
  width: header.column.columnDef.size + 'px',
296
- }, "data-no-resize": header.column.columnDef.enableResizing ? null : true },
297
- React.createElement(NvTableheader, { sortable: canSort ? true : undefined, sortDirection: sortDirection || (canSort ? 'none' : undefined), onSortDirectionChanged: canSort
298
- ? header.column.getToggleSortingHandler()
299
- : undefined }, header.isPlaceholder
300
- ? null
301
- : flexRender(header.column.columnDef.header, header.getContext()))));
572
+ }, "data-no-resize": isSelectionHeader
573
+ ? true
574
+ : header.column.columnDef.enableResizing
575
+ ? null
576
+ : true }, isSelectionHeader ? (selectionMode === 'multiple' && selectAllEnabled ? (React.createElement("div", { className: "flex items-center justify-center" },
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;
593
+ }
594
+ } }))) : null) : (React.createElement(NvTableheader, { sortable: canSort ? true : undefined, sortDirection: sortDirection || (canSort ? 'none' : undefined), onSortDirectionChanged: canSort
595
+ ? header.column.getToggleSortingHandler()
596
+ : undefined }, header.isPlaceholder
597
+ ? null
598
+ : flexRender(header.column.columnDef.header, header.getContext())))));
302
599
  }))))),
303
600
  React.createElement("tbody", null, tableRows.map((row, index) => {
304
601
  const isLastRow = isInfiniteScroll && index === tableRows.length - 1;
305
- 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": `datatable-cell-${cell.column.id}` }, flexRender(cell.column.columnDef.cell, cell.getContext()))))));
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
+ }
617
+ }
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()))))));
306
625
  })))),
307
626
  paginationAPI &&
308
627
  (renderPagination
@@ -0,0 +1,45 @@
1
+ export function applyRowSelection(params) {
2
+ const { currentSelectedIds, rowId, isChecked, selectionMode } = params;
3
+ const next = new Set(currentSelectedIds);
4
+ const currentlySelected = next.has(rowId);
5
+ if (selectionMode === 'single') {
6
+ if (isChecked) {
7
+ if (!(next.size === 1 && currentlySelected)) {
8
+ next.clear();
9
+ next.add(rowId);
10
+ }
11
+ }
12
+ else if (currentlySelected) {
13
+ next.delete(rowId);
14
+ }
15
+ return next;
16
+ }
17
+ if (selectionMode === 'multiple') {
18
+ if (isChecked && !currentlySelected) {
19
+ next.add(rowId);
20
+ }
21
+ else if (!isChecked && currentlySelected) {
22
+ next.delete(rowId);
23
+ }
24
+ }
25
+ return next;
26
+ }
27
+ export function shouldIgnoreSelectAllEvent(params) {
28
+ const { isChecked, selectAllState } = params;
29
+ if ((isChecked && selectAllState === 'checked') ||
30
+ (!isChecked && selectAllState === 'unchecked')) {
31
+ return true;
32
+ }
33
+ return !isChecked && selectAllState === 'indeterminate';
34
+ }
35
+ export function applySelectAllSelection(params) {
36
+ const { currentSelectedIds, visibleRowIds, isChecked } = params;
37
+ const next = new Set(currentSelectedIds);
38
+ if (isChecked) {
39
+ visibleRowIds.forEach((id) => next.add(id));
40
+ }
41
+ else {
42
+ visibleRowIds.forEach((id) => next.delete(id));
43
+ }
44
+ return next;
45
+ }