@nova-design-system/nova-react 3.27.0 → 3.28.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 (79) hide show
  1. package/dist/cjs/generated/components.server.js +24 -0
  2. package/dist/cjs/{index-clzdrCkN.js → index-Kxp9mv-Q.js} +723 -317
  3. package/dist/cjs/index.js +4 -1
  4. package/dist/cjs/nova-datetime-CyL2J6O4-0Y9g3rfI.js +10531 -0
  5. package/dist/cjs/{nv-accordion-item.entry-Gn0QFETn.js → nv-accordion-item.entry-fds_kk_3.js} +1 -1
  6. package/dist/cjs/{nv-accordion.entry-BooppL5C.js → nv-accordion.entry-BLs4N5ZL.js} +1 -1
  7. package/dist/cjs/{nv-alert.entry-CDmHhq86.js → nv-alert.entry-Bx1BiC8F.js} +1 -1
  8. package/dist/cjs/{nv-avatar.entry-pYMjQvxK.js → nv-avatar.entry-DS88LME3.js} +1 -1
  9. package/dist/cjs/{nv-badge_2.entry-BneUUyR7.js → nv-badge_2.entry-vBPxmUmg.js} +2 -2
  10. package/dist/cjs/{nv-breadcrumb.entry-B7CGcJ_l.js → nv-breadcrumb.entry-BOo3hA5y.js} +1 -1
  11. package/dist/cjs/{nv-breadcrumbs.entry-BNKz4ehX.js → nv-breadcrumbs.entry-igHC_6Bd.js} +1 -1
  12. package/dist/cjs/{nv-button.entry-DDCkNY_S.js → nv-button.entry-BRQPmQbs.js} +1 -1
  13. package/dist/cjs/{nv-buttongroup.entry-Bio-SWcx.js → nv-buttongroup.entry-D3tG2EZ1.js} +1 -1
  14. package/dist/cjs/{nv-calendar.entry-BoNFEz6W.js → nv-calendar.entry-BpNHMTKr.js} +1 -1
  15. package/dist/cjs/{nv-col.entry-BHTqFTHl.js → nv-col.entry-lyIZqDsW.js} +1 -1
  16. package/dist/cjs/{nv-datagrid.entry-Bw_sqaMu.js → nv-datagrid.entry-BeemONKu.js} +1 -1
  17. package/dist/cjs/{nv-datagridcolumn.entry-Z-RXn3ak.js → nv-datagridcolumn.entry-B6kE4eVC.js} +1 -1
  18. package/dist/cjs/nv-datetest.entry-C1uuC-ZG.js +69 -0
  19. package/dist/cjs/nv-datetimetest.entry-Hthxbjj9.js +57 -0
  20. package/dist/cjs/{nv-dialog.entry-ByZkK4F9.js → nv-dialog.entry-Cr9zwMPo.js} +2 -2
  21. package/dist/cjs/{nv-dialogfooter_2.entry-Bddcz6HA.js → nv-dialogfooter_2.entry-DaaKojyE.js} +3 -3
  22. package/dist/cjs/{nv-drawer.entry-660dihzy.js → nv-drawer.entry-0UaYxCjh.js} +2 -2
  23. package/dist/cjs/{nv-drawerfooter_2.entry-D6EA-3-u.js → nv-drawerfooter_2.entry-CqtuC7xP.js} +3 -3
  24. package/dist/cjs/{nv-fieldcheckbox.entry-Bm6qurcS.js → nv-fieldcheckbox.entry-DK3aO8C8.js} +5 -5
  25. package/dist/cjs/{nv-fielddate.entry-DW9RQ_dD.js → nv-fielddate.entry-BY-xF3KN.js} +7 -7
  26. package/dist/cjs/{nv-fielddaterange.entry-m3dY0P2v.js → nv-fielddaterange.entry-BT6qCQc3.js} +7 -7
  27. package/dist/cjs/{nv-fielddropdown.entry-DkARzQix.js → nv-fielddropdown.entry-CsVD067i.js} +5 -5
  28. package/dist/cjs/{nv-fielddropdownitem.entry-DKG1bO2H.js → nv-fielddropdownitem.entry-Crtfwlx7.js} +2 -2
  29. package/dist/cjs/{nv-fieldmultiselect.entry-B4As0NxI.js → nv-fieldmultiselect.entry-C2uoE60e.js} +1 -1
  30. package/dist/cjs/{nv-fieldnumber.entry-DHBPKUcr.js → nv-fieldnumber.entry-DzW5SiiZ.js} +5 -5
  31. package/dist/cjs/{nv-fieldpassword.entry-DxEaIvRh.js → nv-fieldpassword.entry-D4r9Qxos.js} +5 -5
  32. package/dist/cjs/{nv-fieldradio.entry-MSrPlSzv.js → nv-fieldradio.entry-DX0Ghx--.js} +4 -4
  33. package/dist/cjs/{nv-fieldselect.entry-qMAtqdcr.js → nv-fieldselect.entry-_CglE66i.js} +6 -6
  34. package/dist/cjs/{nv-fieldslider.entry-CHdMG-lt.js → nv-fieldslider.entry-6zt75uDU.js} +4 -4
  35. package/dist/cjs/{nv-fieldtext.entry-tbYFUJP9.js → nv-fieldtext.entry-B4X110wn.js} +5 -5
  36. package/dist/cjs/{nv-fieldtextarea.entry-D7b3-4Ar.js → nv-fieldtextarea.entry-CeAMhA8Y.js} +4 -4
  37. package/dist/cjs/{nv-fieldtime.entry-TSPljEeW.js → nv-fieldtime.entry-BvzncwNd.js} +4 -4
  38. package/dist/cjs/nv-icon.entry-C2md2kmq.js +80 -0
  39. package/dist/cjs/{nv-iconbutton_2.entry-Ch0rfD12.js → nv-iconbutton_2.entry-D-zRpLjT.js} +3 -3
  40. package/dist/cjs/{nv-menu.entry-BKLyCbyy.js → nv-menu.entry-DN_DkosX.js} +2 -2
  41. package/dist/cjs/{nv-menuitem.entry-BBK6uQav.js → nv-menuitem.entry-Cj6w33rq.js} +2 -2
  42. package/dist/cjs/{nv-notification-bullet.entry-Dk0oaUTe.js → nv-notification-bullet.entry-DagStJ3K.js} +1 -1
  43. package/dist/cjs/{nv-notification.entry-DVUXuj9T.js → nv-notification.entry-oV69FpxE.js} +2 -2
  44. package/dist/cjs/{nv-notificationcontainer.entry-BQbB0vJ8.js → nv-notificationcontainer.entry-gQGHHeer.js} +2 -14
  45. package/dist/cjs/{nv-pagination-nav.entry-BON8ve70.js → nv-pagination-nav.entry-BYvcVj1M.js} +2 -2
  46. package/dist/cjs/{nv-paginationtable.entry-pT2Qx3xY.js → nv-paginationtable.entry-CwCFQwbl.js} +2 -2
  47. package/dist/cjs/{nv-popover.entry-91VegZzi.js → nv-popover.entry-DySToeSB.js} +2 -2
  48. package/dist/cjs/{nv-row.entry-C_75GYAm.js → nv-row.entry-46ghuEeG.js} +2 -2
  49. package/dist/cjs/{nv-sidebar.entry-XIcDybjF.js → nv-sidebar.entry-B6opNG2r.js} +2 -2
  50. package/dist/cjs/{nv-sidebarcontent.entry-JSf1Mb0x.js → nv-sidebarcontent.entry-Pb8c2QoA.js} +2 -2
  51. package/dist/cjs/{nv-sidebardivider.entry-XZ1hYAgj.js → nv-sidebardivider.entry-LCCO53Z5.js} +2 -2
  52. package/dist/cjs/{nv-sidebarfooter.entry-BHO9gh6y.js → nv-sidebarfooter.entry-DG5fkLHd.js} +2 -2
  53. package/dist/cjs/{nv-sidebargroup.entry-DAb7dWFF.js → nv-sidebargroup.entry-DRqkSyQi.js} +2 -2
  54. package/dist/cjs/{nv-sidebarheader.entry-BksZP_km.js → nv-sidebarheader.entry-D6WvH2wG.js} +2 -2
  55. package/dist/cjs/{nv-sidebarlogo.entry-BntEWmH_.js → nv-sidebarlogo.entry-BorScwI-.js} +2 -2
  56. package/dist/cjs/{nv-sidebarnavitem.entry-DSu34C34.js → nv-sidebarnavitem.entry-BEW74Rw3.js} +3 -3
  57. package/dist/cjs/{nv-sidebarnavsubitem.entry-Y8Ex0_Lr.js → nv-sidebarnavsubitem.entry-EgaIlUfP.js} +2 -2
  58. package/dist/cjs/{nv-split.entry-BDr6FxCd.js → nv-split.entry-CEC5Tuwz.js} +2 -2
  59. package/dist/cjs/{nv-stack.entry-CChjKrA3.js → nv-stack.entry-BR8lYaoI.js} +2 -2
  60. package/dist/cjs/{nv-table.entry-CfqHpj1L.js → nv-table.entry-CISZFst5.js} +2 -2
  61. package/dist/cjs/{nv-tableheader.entry-C61xO9Ra.js → nv-tableheader.entry-DbdpTJGC.js} +2 -2
  62. package/dist/cjs/nv-timetest.entry-Dg1JEgAv.js +75 -0
  63. package/dist/cjs/{nv-toggle.entry-B0HofO36.js → nv-toggle.entry-DDfRpC1R.js} +3 -3
  64. package/dist/cjs/{nv-togglebutton.entry-CzyNiZyK.js → nv-togglebutton.entry-D7NkdIXP.js} +2 -2
  65. package/dist/cjs/{nv-togglebuttongroup.entry-TizvIVlB.js → nv-togglebuttongroup.entry-L8gitSUS.js} +2 -2
  66. package/dist/cjs/{nv-tooltip.entry-0kcGcQZo.js → nv-tooltip.entry-BElnrEqE.js} +9 -4
  67. package/dist/components/NvDatatable/NvDatatable.js +293 -63
  68. package/dist/components/NvDatatable/selectionState.js +45 -0
  69. package/dist/components/NvDatatable/selectionState.test.js +74 -0
  70. package/dist/generated/components.js +38 -0
  71. package/dist/generated/components.server.js +21 -0
  72. package/dist/types/components/NvDatatable/NvDatatable.d.ts +1 -1
  73. package/dist/types/components/NvDatatable/selectionState.d.ts +21 -0
  74. package/dist/types/components/NvDatatable/selectionState.test.d.ts +1 -0
  75. package/dist/types/components/NvDatatable/types.d.ts +17 -0
  76. package/dist/types/generated/components.d.ts +51 -1
  77. package/dist/types/generated/components.server.d.ts +51 -1
  78. package/package.json +6 -4
  79. 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-Kxp9mv-Q.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-Kxp9mv-Q.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,25 @@
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, stickyHeader, ...htmlProps }) {
11
+ const SELECTION_EVENT_SUPPRESSION_MS = 16;
12
+ const areSetsEqual = useCallback((a, b) => {
13
+ if (a.size !== b.size) {
14
+ return false;
15
+ }
16
+ for (const value of a) {
17
+ if (!b.has(value)) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }, []);
10
23
  const isInfiniteScroll = pagination?.infinite && mode === 'server';
11
24
  const [paginationState, setPaginationState] = useState({
12
25
  pageIndex: 0,
@@ -14,7 +27,14 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
14
27
  });
15
28
  const [sortingState, setSortingState] = useState(sorting?.sortState || []);
16
29
  const [globalFilterState, setGlobalFilterState] = useState(filtering?.filterState);
30
+ const [selectedRowIdsState, setSelectedRowIdsState] = useState(() => new Set(selection?.selectedRowIds ?? []));
31
+ const selectedRowIdsRef = useRef(new Set(selection?.selectedRowIds ?? []));
32
+ const selectionMode = selection?.mode ?? 'none';
33
+ const selectionEnabled = selectionMode !== 'none';
34
+ const selectAllEnabled = selection?.enableSelectAll ?? true;
35
+ const getSelectionRowId = useCallback((row) => selection?.getRowId?.(row) ?? '', [selection]);
17
36
  const lastRowRef = useRef(null);
37
+ const suppressRowSelectionEventsRef = useRef(false);
18
38
  const debouncedSetFilter = useRef(null);
19
39
  const setGlobalFilterDebounced = useCallback((value) => {
20
40
  const debounceMs = filtering?.debounceMs ?? 300;
@@ -40,60 +60,89 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
40
60
  setGlobalFilterState(filtering.filterState);
41
61
  }
42
62
  }, [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;
63
+ if (selectionEnabled && !selection?.getRowId) {
64
+ throw new Error('NvDatatable selection requires selection.getRowId when selection.mode is not "none".');
65
+ }
66
+ useEffect(() => {
67
+ if (selection?.selectedRowIds) {
68
+ const controlledIds = new Set(selection.selectedRowIds);
69
+ if (!areSetsEqual(selectedRowIdsState, controlledIds)) {
70
+ selectedRowIdsRef.current = controlledIds;
71
+ setSelectedRowIdsState(controlledIds);
72
+ }
91
73
  }
92
- if (col.sortUndefined !== undefined) {
93
- columnDef.sortUndefined = col.sortUndefined;
74
+ }, [selection?.selectedRowIds, selectedRowIdsState, areSetsEqual]);
75
+ useEffect(() => {
76
+ selectedRowIdsRef.current = selectedRowIdsState;
77
+ }, [selectedRowIdsState]);
78
+ const tableColumns = useMemo(() => {
79
+ const dataColumns = columns
80
+ .filter((col) => !col.hidden)
81
+ .map((col) => {
82
+ const headerName = col.headerName || String(col.field);
83
+ const columnDef = {
84
+ accessorKey: col.field,
85
+ accessorFn: col.valueFormatter
86
+ ? (row) => {
87
+ const rawValue = row[col.field];
88
+ return col.valueFormatter({
89
+ value: rawValue,
90
+ row,
91
+ field: col.field,
92
+ });
93
+ }
94
+ : undefined,
95
+ header: col.renderHeader
96
+ ? () => col.renderHeader({
97
+ headerName,
98
+ field: col.field,
99
+ })
100
+ : headerName,
101
+ size: col.width,
102
+ enableResizing: col.resizable ?? true,
103
+ enableSorting: sorting ? col.sortable ?? true : false,
104
+ cell: (context) => {
105
+ const value = context.getValue();
106
+ const row = context.row.original;
107
+ const rowIndex = context.row.index;
108
+ if (col.renderCell) {
109
+ return col.renderCell({
110
+ value,
111
+ row,
112
+ field: col.field,
113
+ rowIndex,
114
+ });
115
+ }
116
+ return value;
117
+ },
118
+ };
119
+ if (col.sortingFn !== undefined) {
120
+ columnDef.sortingFn = col.sortingFn;
121
+ }
122
+ if (col.sortDescFirst !== undefined) {
123
+ columnDef.sortDescFirst = col.sortDescFirst;
124
+ }
125
+ if (col.invertSorting !== undefined) {
126
+ columnDef.invertSorting = col.invertSorting;
127
+ }
128
+ if (col.sortUndefined !== undefined) {
129
+ columnDef.sortUndefined = col.sortUndefined;
130
+ }
131
+ return columnDef;
132
+ });
133
+ if (!selectionEnabled) {
134
+ return dataColumns;
94
135
  }
95
- return columnDef;
96
- }), [columns, sorting]);
136
+ const selectionColumn = {
137
+ id: '__selection__',
138
+ header: '',
139
+ size: selection?.checkboxColumnWidth ?? 48,
140
+ enableResizing: false,
141
+ enableSorting: false,
142
+ cell: () => null,
143
+ };
144
+ return [selectionColumn, ...dataColumns];
145
+ }, [columns, sorting, selectionEnabled, selection?.checkboxColumnWidth]);
97
146
  const tableConfig = useMemo(() => {
98
147
  const baseConfig = {
99
148
  data: rows,
@@ -187,6 +236,169 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
187
236
  mode,
188
237
  ]);
189
238
  const table = useReactTable(tableConfig);
239
+ const tableRows = table.getRowModel().rows;
240
+ const setSelectionState = useCallback((nextSelectedIds) => {
241
+ const current = new Set(selection?.selectedRowIds ?? selectedRowIdsRef.current);
242
+ const nextRaw = typeof nextSelectedIds === 'function'
243
+ ? nextSelectedIds(new Set(current))
244
+ : nextSelectedIds;
245
+ const next = new Set(nextRaw);
246
+ if (areSetsEqual(current, next)) {
247
+ return;
248
+ }
249
+ selectedRowIdsRef.current = next;
250
+ setSelectedRowIdsState(next);
251
+ 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
+ }, []);
270
+ const isRowSelected = useCallback((row) => {
271
+ if (!selectionEnabled) {
272
+ return false;
273
+ }
274
+ const rowId = getSelectionRowId(row);
275
+ return !!rowId && selectedRowIdsState.has(rowId);
276
+ }, [selectionEnabled, getSelectionRowId, selectedRowIdsState]);
277
+ const visibleRowIds = useMemo(() => tableRows
278
+ .map((row) => getSelectionRowId(row.original))
279
+ .filter((id) => !!id), [tableRows, getSelectionRowId]);
280
+ const visibleRowIdsSet = useMemo(() => new Set(visibleRowIds), [visibleRowIds]);
281
+ const setRowSelection = useCallback((row, isChecked) => {
282
+ if (!selectionEnabled) {
283
+ return;
284
+ }
285
+ if (suppressRowSelectionEventsRef.current) {
286
+ return;
287
+ }
288
+ const rowId = getSelectionRowId(row);
289
+ if (!rowId) {
290
+ return;
291
+ }
292
+ if (!visibleRowIdsSet.has(rowId)) {
293
+ return;
294
+ }
295
+ suppressRowSelectionEventsRef.current = true;
296
+ setSelectionState((current) => {
297
+ return applyRowSelection({
298
+ currentSelectedIds: current,
299
+ rowId,
300
+ isChecked,
301
+ selectionMode,
302
+ });
303
+ });
304
+ setTimeout(() => {
305
+ suppressRowSelectionEventsRef.current = false;
306
+ }, SELECTION_EVENT_SUPPRESSION_MS);
307
+ }, [
308
+ selectionEnabled,
309
+ getSelectionRowId,
310
+ visibleRowIdsSet,
311
+ selectionMode,
312
+ setSelectionState,
313
+ ]);
314
+ const selectAllState = useMemo(() => {
315
+ if (!selectionEnabled ||
316
+ selectionMode !== 'multiple' ||
317
+ !selectAllEnabled ||
318
+ !visibleRowIds.length) {
319
+ return 'unchecked';
320
+ }
321
+ const selectedCount = visibleRowIds.filter((id) => selectedRowIdsState.has(id)).length;
322
+ if (selectedCount === 0) {
323
+ return 'unchecked';
324
+ }
325
+ if (selectedCount === visibleRowIds.length) {
326
+ return 'checked';
327
+ }
328
+ return 'indeterminate';
329
+ }, [
330
+ selectionEnabled,
331
+ selectionMode,
332
+ selectAllEnabled,
333
+ visibleRowIds,
334
+ selectedRowIdsState,
335
+ ]);
336
+ const handleSelectAllChanged = useCallback((isChecked) => {
337
+ if (!selectionEnabled ||
338
+ selectionMode !== 'multiple' ||
339
+ !selectAllEnabled ||
340
+ !visibleRowIds.length) {
341
+ return;
342
+ }
343
+ if (shouldIgnoreSelectAllEvent({ isChecked, selectAllState })) {
344
+ return;
345
+ }
346
+ suppressRowSelectionEventsRef.current = true;
347
+ setSelectionState((current) => {
348
+ return applySelectAllSelection({
349
+ currentSelectedIds: current,
350
+ visibleRowIds,
351
+ isChecked,
352
+ });
353
+ });
354
+ setTimeout(() => {
355
+ suppressRowSelectionEventsRef.current = false;
356
+ }, SELECTION_EVENT_SUPPRESSION_MS);
357
+ }, [
358
+ selectionEnabled,
359
+ selectionMode,
360
+ selectAllEnabled,
361
+ visibleRowIds,
362
+ selectAllState,
363
+ setSelectionState,
364
+ ]);
365
+ useEffect(() => {
366
+ if (!selectionEnabled || selection?.deselectOnFilter === false) {
367
+ return;
368
+ }
369
+ if (mode !== 'client' || !filtering) {
370
+ return;
371
+ }
372
+ const filterVisibleRows = table
373
+ .getFilteredRowModel()
374
+ .rows.map((row) => row.original);
375
+ const visibleIds = new Set(filterVisibleRows
376
+ .map((row) => getSelectionRowId(row))
377
+ .filter((id) => !!id));
378
+ const currentSelection = selection?.selectedRowIds ?? selectedRowIdsState;
379
+ const next = new Set();
380
+ currentSelection.forEach((id) => {
381
+ if (visibleIds.has(id)) {
382
+ next.add(id);
383
+ }
384
+ });
385
+ if (!areSetsEqual(currentSelection, next)) {
386
+ setSelectedRowIdsState(next);
387
+ selection?.onSelectionChange?.(new Set(next));
388
+ }
389
+ }, [
390
+ areSetsEqual,
391
+ selectionEnabled,
392
+ selection?.deselectOnFilter,
393
+ selection?.onSelectionChange,
394
+ selection?.selectedRowIds,
395
+ mode,
396
+ filtering,
397
+ globalFilterState,
398
+ table,
399
+ selectedRowIdsState,
400
+ getSelectionRowId,
401
+ ]);
190
402
  useEffect(() => {
191
403
  if (mode === 'server' &&
192
404
  !pagination?.infinite &&
@@ -253,7 +465,6 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
253
465
  mode,
254
466
  isInfiniteScroll,
255
467
  ]);
256
- const tableRows = table.getRowModel().rows;
257
468
  const renderDefaultPagination = (api) => {
258
469
  const labelProps = {};
259
470
  if (pagination?.labels?.page)
@@ -289,20 +500,39 @@ function NvDatatable({ mode = 'client', columns, rows, pagination, sorting, filt
289
500
  React.createElement(NvTable, { ...htmlProps },
290
501
  React.createElement("table", null,
291
502
  React.createElement("thead", { "data-sticky-top": stickyHeader ? 'true' : undefined }, table.getHeaderGroups().map((headerGroup) => (React.createElement("tr", { key: headerGroup.id }, headerGroup.headers.map((header) => {
503
+ const isSelectionHeader = header.id === '__selection__';
292
504
  const canSort = header.column.getCanSort();
293
505
  const sortDirection = header.column.getIsSorted();
294
- return (React.createElement("th", { key: header.id, "data-testid": `datatable-header-${header.id}`, style: {
506
+ return (React.createElement("th", { key: header.id, "data-testid": isSelectionHeader
507
+ ? 'datatable-header-selection'
508
+ : `datatable-header-${header.id}`, style: {
295
509
  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()))));
510
+ }, "data-no-resize": isSelectionHeader
511
+ ? true
512
+ : header.column.columnDef.enableResizing
513
+ ? null
514
+ : 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);
519
+ }
520
+ } }))) : null) : (React.createElement(NvTableheader, { sortable: canSort ? true : undefined, sortDirection: sortDirection || (canSort ? 'none' : undefined), onSortDirectionChanged: canSort
521
+ ? header.column.getToggleSortingHandler()
522
+ : undefined }, header.isPlaceholder
523
+ ? null
524
+ : flexRender(header.column.columnDef.header, header.getContext())))));
302
525
  }))))),
303
526
  React.createElement("tbody", null, tableRows.map((row, index) => {
304
527
  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()))))));
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);
534
+ }
535
+ } })) : (flexRender(cell.column.columnDef.cell, cell.getContext())))))));
306
536
  })))),
307
537
  paginationAPI &&
308
538
  (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
+ }
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { applyRowSelection, applySelectAllSelection, shouldIgnoreSelectAllEvent, } from './selectionState';
3
+ describe('selectionState', () => {
4
+ it('single row selection replaces existing selection', () => {
5
+ const selected = new Set(['1']);
6
+ const next = applyRowSelection({
7
+ currentSelectedIds: selected,
8
+ rowId: '2',
9
+ isChecked: true,
10
+ selectionMode: 'single',
11
+ });
12
+ expect(Array.from(next)).toEqual(['2']);
13
+ });
14
+ it('single row uncheck removes currently selected row', () => {
15
+ const selected = new Set(['2']);
16
+ const next = applyRowSelection({
17
+ currentSelectedIds: selected,
18
+ rowId: '2',
19
+ isChecked: false,
20
+ selectionMode: 'single',
21
+ });
22
+ expect(Array.from(next)).toEqual([]);
23
+ });
24
+ it('multiple row selection toggles add/remove correctly', () => {
25
+ const selected = new Set(['1']);
26
+ const afterAdd = applyRowSelection({
27
+ currentSelectedIds: selected,
28
+ rowId: '2',
29
+ isChecked: true,
30
+ selectionMode: 'multiple',
31
+ });
32
+ const afterRemove = applyRowSelection({
33
+ currentSelectedIds: afterAdd,
34
+ rowId: '1',
35
+ isChecked: false,
36
+ selectionMode: 'multiple',
37
+ });
38
+ expect(Array.from(afterAdd).sort((a, b) => a.localeCompare(b))).toEqual([
39
+ '1',
40
+ '2',
41
+ ]);
42
+ expect(Array.from(afterRemove)).toEqual(['2']);
43
+ });
44
+ it('ignores synthetic select-all events for checked/unchecked/indeterminate noise', () => {
45
+ expect(shouldIgnoreSelectAllEvent({
46
+ isChecked: true,
47
+ selectAllState: 'checked',
48
+ })).toBe(true);
49
+ expect(shouldIgnoreSelectAllEvent({
50
+ isChecked: false,
51
+ selectAllState: 'unchecked',
52
+ })).toBe(true);
53
+ expect(shouldIgnoreSelectAllEvent({
54
+ isChecked: false,
55
+ selectAllState: 'indeterminate',
56
+ })).toBe(true);
57
+ });
58
+ it('select-all then deselect-all clears visible rows', () => {
59
+ const selected = new Set(['1']);
60
+ const visibleRowIds = ['1', '2'];
61
+ const afterSelectAll = applySelectAllSelection({
62
+ currentSelectedIds: selected,
63
+ visibleRowIds,
64
+ isChecked: true,
65
+ });
66
+ const afterDeselectAll = applySelectAllSelection({
67
+ currentSelectedIds: afterSelectAll,
68
+ visibleRowIds,
69
+ isChecked: false,
70
+ });
71
+ expect(Array.from(afterSelectAll).sort((a, b) => a.localeCompare(b))).toEqual(['1', '2']);
72
+ expect(Array.from(afterDeselectAll)).toEqual([]);
73
+ });
74
+ });
@@ -12,6 +12,8 @@ import { NvCalendar as NvCalendarElement, defineCustomElement as defineNvCalenda
12
12
  import { NvCol as NvColElement, defineCustomElement as defineNvCol } from "@nova-design-system/nova-webcomponents/dist/components/nv-col.js";
13
13
  import { NvDatagrid as NvDatagridElement, defineCustomElement as defineNvDatagrid } from "@nova-design-system/nova-webcomponents/dist/components/nv-datagrid.js";
14
14
  import { NvDatagridcolumn as NvDatagridcolumnElement, defineCustomElement as defineNvDatagridcolumn } from "@nova-design-system/nova-webcomponents/dist/components/nv-datagridcolumn.js";
15
+ import { NvDatetest as NvDatetestElement, defineCustomElement as defineNvDatetest } from "@nova-design-system/nova-webcomponents/dist/components/nv-datetest.js";
16
+ import { NvDatetimetest as NvDatetimetestElement, defineCustomElement as defineNvDatetimetest } from "@nova-design-system/nova-webcomponents/dist/components/nv-datetimetest.js";
15
17
  import { NvDialog as NvDialogElement, defineCustomElement as defineNvDialog } from "@nova-design-system/nova-webcomponents/dist/components/nv-dialog.js";
16
18
  import { NvDialogfooter as NvDialogfooterElement, defineCustomElement as defineNvDialogfooter } from "@nova-design-system/nova-webcomponents/dist/components/nv-dialogfooter.js";
17
19
  import { NvDialogheader as NvDialogheaderElement, defineCustomElement as defineNvDialogheader } from "@nova-design-system/nova-webcomponents/dist/components/nv-dialogheader.js";
@@ -58,6 +60,7 @@ import { NvSplit as NvSplitElement, defineCustomElement as defineNvSplit } from
58
60
  import { NvStack as NvStackElement, defineCustomElement as defineNvStack } from "@nova-design-system/nova-webcomponents/dist/components/nv-stack.js";
59
61
  import { NvTable as NvTableElement, defineCustomElement as defineNvTable } from "@nova-design-system/nova-webcomponents/dist/components/nv-table.js";
60
62
  import { NvTableheader as NvTableheaderElement, defineCustomElement as defineNvTableheader } from "@nova-design-system/nova-webcomponents/dist/components/nv-tableheader.js";
63
+ import { NvTimetest as NvTimetestElement, defineCustomElement as defineNvTimetest } from "@nova-design-system/nova-webcomponents/dist/components/nv-timetest.js";
61
64
  import { NvToggle as NvToggleElement, defineCustomElement as defineNvToggle } from "@nova-design-system/nova-webcomponents/dist/components/nv-toggle.js";
62
65
  import { NvTogglebutton as NvTogglebuttonElement, defineCustomElement as defineNvTogglebutton } from "@nova-design-system/nova-webcomponents/dist/components/nv-togglebutton.js";
63
66
  import { NvTogglebuttongroup as NvTogglebuttongroupElement, defineCustomElement as defineNvTogglebuttongroup } from "@nova-design-system/nova-webcomponents/dist/components/nv-togglebuttongroup.js";
@@ -171,6 +174,29 @@ export const NvDatagridcolumn = createComponent({
171
174
  events: {},
172
175
  defineCustomElement: defineNvDatagridcolumn
173
176
  });
177
+ export const NvDatetest = createComponent({
178
+ tagName: 'nv-datetest',
179
+ elementClass: NvDatetestElement,
180
+ react: React,
181
+ events: {
182
+ onValueChanged: 'valueChanged',
183
+ onLastChanged: 'lastChanged',
184
+ onHistoryChanged: 'historyChanged',
185
+ onValueDateChanged: 'valueDateChanged'
186
+ },
187
+ defineCustomElement: defineNvDatetest
188
+ });
189
+ export const NvDatetimetest = createComponent({
190
+ tagName: 'nv-datetimetest',
191
+ elementClass: NvDatetimetestElement,
192
+ react: React,
193
+ events: {
194
+ onValueChanged: 'valueChanged',
195
+ onLastChanged: 'lastChanged',
196
+ onHistoryChanged: 'historyChanged'
197
+ },
198
+ defineCustomElement: defineNvDatetimetest
199
+ });
174
200
  export const NvDialog = createComponent({
175
201
  tagName: 'nv-dialog',
176
202
  elementClass: NvDialogElement,
@@ -513,6 +539,18 @@ export const NvTableheader = createComponent({
513
539
  events: { onSortDirectionChanged: 'sortDirectionChanged' },
514
540
  defineCustomElement: defineNvTableheader
515
541
  });
542
+ export const NvTimetest = createComponent({
543
+ tagName: 'nv-timetest',
544
+ elementClass: NvTimetestElement,
545
+ react: React,
546
+ events: {
547
+ onValueChanged: 'valueChanged',
548
+ onLastChanged: 'lastChanged',
549
+ onHistoryChanged: 'historyChanged',
550
+ onValueDateChanged: 'valueDateChanged'
551
+ },
552
+ defineCustomElement: defineNvTimetest
553
+ });
516
554
  export const NvToggle = createComponent({
517
555
  tagName: 'nv-toggle',
518
556
  elementClass: NvToggleElement,