@nova-design-system/nova-angular-19 3.19.0 → 3.21.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.
@@ -4,7 +4,7 @@ import { __decorate } from 'tslib';
4
4
  import { fromEvent, BehaviorSubject } from 'rxjs';
5
5
  import * as i2 from '@angular/common';
6
6
  import { CommonModule } from '@angular/common';
7
- import { memo, createTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/table-core';
7
+ import { memo, createTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel } from '@tanstack/table-core';
8
8
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
9
9
  export * from '@nova-design-system/nova-webcomponents/constants';
10
10
 
@@ -252,11 +252,11 @@ let NvButton = class NvButton {
252
252
  this.el = r.nativeElement;
253
253
  }
254
254
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NvButton, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
255
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: NvButton, isStandalone: false, selector: "nv-button", inputs: { active: "active", danger: "danger", disabled: "disabled", emphasis: "emphasis", fluid: "fluid", form: "form", loading: "loading", size: "size", type: "type" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
255
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: NvButton, isStandalone: false, selector: "nv-button", inputs: { active: "active", danger: "danger", disableTabindex: "disableTabindex", disabled: "disabled", emphasis: "emphasis", fluid: "fluid", form: "form", loading: "loading", size: "size", type: "type" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
256
256
  };
257
257
  NvButton = __decorate([
258
258
  ProxyCmp({
259
- inputs: ['active', 'danger', 'disabled', 'emphasis', 'fluid', 'form', 'loading', 'size', 'type']
259
+ inputs: ['active', 'danger', 'disableTabindex', 'disabled', 'emphasis', 'fluid', 'form', 'loading', 'size', 'type']
260
260
  })
261
261
  ], NvButton);
262
262
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NvButton, decorators: [{
@@ -266,7 +266,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
266
266
  changeDetection: ChangeDetectionStrategy.OnPush,
267
267
  template: '<ng-content></ng-content>',
268
268
  // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
269
- inputs: ['active', 'danger', 'disabled', 'emphasis', 'fluid', 'form', 'loading', 'size', 'type'],
269
+ inputs: ['active', 'danger', 'disableTabindex', 'disabled', 'emphasis', 'fluid', 'form', 'loading', 'size', 'type'],
270
270
  standalone: false
271
271
  }]
272
272
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }] });
@@ -902,11 +902,11 @@ let NvIconbutton = class NvIconbutton {
902
902
  this.el = r.nativeElement;
903
903
  }
904
904
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NvIconbutton, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
905
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: NvIconbutton, isStandalone: false, selector: "nv-iconbutton", inputs: { active: "active", disabled: "disabled", emphasis: "emphasis", loading: "loading", name: "name", shape: "shape", size: "size", type: "type" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
905
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: NvIconbutton, isStandalone: false, selector: "nv-iconbutton", inputs: { active: "active", disableTabindex: "disableTabindex", disabled: "disabled", emphasis: "emphasis", loading: "loading", name: "name", shape: "shape", size: "size", type: "type" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
906
906
  };
907
907
  NvIconbutton = __decorate([
908
908
  ProxyCmp({
909
- inputs: ['active', 'disabled', 'emphasis', 'loading', 'name', 'shape', 'size', 'type']
909
+ inputs: ['active', 'disableTabindex', 'disabled', 'emphasis', 'loading', 'name', 'shape', 'size', 'type']
910
910
  })
911
911
  ], NvIconbutton);
912
912
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NvIconbutton, decorators: [{
@@ -916,7 +916,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
916
916
  changeDetection: ChangeDetectionStrategy.OnPush,
917
917
  template: '<ng-content></ng-content>',
918
918
  // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
919
- inputs: ['active', 'disabled', 'emphasis', 'loading', 'name', 'shape', 'size', 'type'],
919
+ inputs: ['active', 'disableTabindex', 'disabled', 'emphasis', 'loading', 'name', 'shape', 'size', 'type'],
920
920
  standalone: false
921
921
  }]
922
922
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }] });
@@ -1175,6 +1175,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
1175
1175
  standalone: false
1176
1176
  }]
1177
1177
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }] });
1178
+ let NvTableheader = class NvTableheader {
1179
+ constructor(c, r, z) {
1180
+ this.z = z;
1181
+ c.detach();
1182
+ this.el = r.nativeElement;
1183
+ proxyOutputs(this, this.el, ['sortDirectionChanged']);
1184
+ }
1185
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NvTableheader, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
1186
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: NvTableheader, isStandalone: false, selector: "nv-tableheader", inputs: { sortDirection: "sortDirection", sortable: "sortable" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1187
+ };
1188
+ NvTableheader = __decorate([
1189
+ ProxyCmp({
1190
+ inputs: ['sortDirection', 'sortable']
1191
+ })
1192
+ ], NvTableheader);
1193
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NvTableheader, decorators: [{
1194
+ type: Component,
1195
+ args: [{
1196
+ selector: 'nv-tableheader',
1197
+ changeDetection: ChangeDetectionStrategy.OnPush,
1198
+ template: '<ng-content></ng-content>',
1199
+ // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
1200
+ inputs: ['sortDirection', 'sortable'],
1201
+ standalone: false
1202
+ }]
1203
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }] });
1178
1204
  let NvToggle = class NvToggle {
1179
1205
  constructor(c, r, z) {
1180
1206
  this.z = z;
@@ -1324,6 +1350,7 @@ const DIRECTIVES = [
1324
1350
  NvSplit,
1325
1351
  NvStack,
1326
1352
  NvTable,
1353
+ NvTableheader,
1327
1354
  NvToggle,
1328
1355
  NvTogglebutton,
1329
1356
  NvTogglebuttongroup,
@@ -2092,14 +2119,47 @@ function createAngularTable(options) {
2092
2119
 
2093
2120
  /* eslint-disable jsdoc/require-jsdoc */
2094
2121
  /* eslint-disable jsdoc/require-returns */
2122
+ /* eslint-disable jsdoc/require-param */
2095
2123
  /**
2096
- * Nova Datatable built on TanStack Table (Angular).
2124
+ * A powerful, flexible datatable component built on TanStack Table.
2125
+ * Supports custom cell rendering, column configuration, pagination, and full TypeScript typing.
2097
2126
  */
2098
2127
  class NvDatatable {
2099
2128
  /** Public getter for table instance. */
2100
2129
  table() {
2101
2130
  return this.tableInstance();
2102
2131
  }
2132
+ /**
2133
+ * Handle sort direction change from table header
2134
+ * @param {Event} event The sort direction change event or direction string
2135
+ * @param {object} header The table header object
2136
+ */
2137
+ handleSortDirectionChanged(event, header) {
2138
+ // Extract direction from event or use directly if string
2139
+ let direction;
2140
+ if (typeof event === 'string') {
2141
+ direction = event;
2142
+ }
2143
+ else if ('detail' in event && typeof event.detail === 'string') {
2144
+ direction = event.detail;
2145
+ }
2146
+ else {
2147
+ // Fallback - shouldn't happen but handle gracefully
2148
+ console.warn('Unexpected event type in handleSortDirectionChanged:', event);
2149
+ return;
2150
+ }
2151
+ const sortingConfig = this.sorting();
2152
+ // Check if multi-sort is enabled
2153
+ const isMultiSort = sortingConfig?.enableMultiSort ?? false;
2154
+ if (direction === 'none') {
2155
+ // Clear this column's sort
2156
+ header.column.clearSorting();
2157
+ }
2158
+ else {
2159
+ // Toggle sort - TanStack will handle the logic internally
2160
+ header.column.toggleSorting(direction === 'desc', isMultiSort);
2161
+ }
2162
+ }
2103
2163
  constructor() {
2104
2164
  /** Column definitions */
2105
2165
  this.columns = input([]);
@@ -2107,6 +2167,8 @@ class NvDatatable {
2107
2167
  this.rows = input([]);
2108
2168
  /** Optional pagination configuration */
2109
2169
  this.pagination = input(undefined);
2170
+ /** Optional sorting configuration */
2171
+ this.sorting = input(undefined);
2110
2172
  /** Should the header stick to the top of the table when scrolling? */
2111
2173
  this.stickyHeader = input(false);
2112
2174
  /** Signal to track cell templates array */
@@ -2124,6 +2186,8 @@ class NvDatatable {
2124
2186
  pageIndex: 0,
2125
2187
  pageSize: this.pagination()?.initialPageSize || 10,
2126
2188
  });
2189
+ /** Sorting state for controlled sorting (server mode) */
2190
+ this.sortingState = signal(this.sorting()?.sortState || []);
2127
2191
  /** Reference to table rows for infinite scroll observer */
2128
2192
  this.tableRows = viewChild('tableRow');
2129
2193
  /** Intersection observer for infinite scroll */
@@ -2135,45 +2199,114 @@ class NvDatatable {
2135
2199
  const templateMap = this.cellTemplateMap();
2136
2200
  return this.columns()
2137
2201
  .filter((col) => !col.hidden)
2138
- .map((col) => ({
2139
- accessorKey: col.field,
2140
- header: col.headerName || String(col.field),
2141
- size: col.width,
2142
- enableResizing: col.resizable ?? true,
2143
- cell: (context) => {
2202
+ .map((col) => {
2203
+ // Shared cell renderer
2204
+ const cellRenderer = (context) => {
2144
2205
  const fieldName = String(col.field);
2145
2206
  const cellTemplate = templateMap.get(fieldName);
2146
2207
  // Priority: template > renderCell > default
2147
2208
  if (cellTemplate) {
2148
- // Return TemplateRef directly - FlexRender will handle it
2209
+ // Return TemplateRef directly - getValue() returns formatted value
2149
2210
  return cellTemplate;
2150
2211
  }
2151
2212
  // Fall back to renderCell if provided
2152
2213
  if (col.renderCell) {
2153
2214
  return col.renderCell(context);
2154
2215
  }
2155
- // Default: just return the value
2216
+ // Default: just return the value (formatted if valueFormatter was used)
2156
2217
  return context.getValue();
2157
- },
2158
- }));
2218
+ };
2219
+ const columnDef = {
2220
+ accessorKey: col.field,
2221
+ accessorFn: col.valueFormatter
2222
+ ? (row) => {
2223
+ const rawValue = row[col.field];
2224
+ return col.valueFormatter({
2225
+ value: rawValue,
2226
+ row,
2227
+ field: col.field,
2228
+ });
2229
+ }
2230
+ : undefined,
2231
+ header: col.headerName || String(col.field),
2232
+ size: col.width,
2233
+ enableResizing: col.resizable ?? true,
2234
+ // Sorting configuration
2235
+ enableSorting: this.sorting() ? col.sortable ?? true : false,
2236
+ cell: cellRenderer,
2237
+ };
2238
+ // Add optional sorting properties only if defined
2239
+ if (col.sortingFn !== undefined) {
2240
+ // @ts-expect-error - TanStack typing is strict but this works at runtime
2241
+ columnDef.sortingFn = col.sortingFn;
2242
+ }
2243
+ if (col.sortDescFirst !== undefined) {
2244
+ columnDef.sortDescFirst = col.sortDescFirst;
2245
+ }
2246
+ if (col.invertSorting !== undefined) {
2247
+ columnDef.invertSorting = col.invertSorting;
2248
+ }
2249
+ if (col.sortUndefined !== undefined) {
2250
+ columnDef.sortUndefined = col.sortUndefined;
2251
+ }
2252
+ return columnDef;
2253
+ });
2159
2254
  });
2160
2255
  /** TanStack table instance with Signals */
2161
2256
  this.tableInstance = createAngularTable(() => {
2162
2257
  const paginationConfig = this.pagination();
2163
- if (!paginationConfig || paginationConfig.mode === 'infinite') {
2164
- // No pagination or infinite scroll - simple config
2165
- return {
2258
+ const sortingConfig = this.sorting();
2259
+ // Determine base table configuration with sorting
2260
+ const getBaseTableConfig = () => {
2261
+ const baseConfig = {
2166
2262
  data: this.rows(),
2167
2263
  columns: this.tableColumns(),
2168
2264
  getCoreRowModel: getCoreRowModel(),
2265
+ // Sorting configuration
2266
+ ...(sortingConfig && {
2267
+ state: {
2268
+ sorting: sortingConfig.mode === 'server' && sortingConfig.sortState
2269
+ ? sortingConfig.sortState
2270
+ : this.sortingState(),
2271
+ },
2272
+ onSortingChange: (updaterOrValue) => {
2273
+ const currentSort = sortingConfig.mode === 'server' && sortingConfig.sortState
2274
+ ? sortingConfig.sortState
2275
+ : this.sortingState();
2276
+ const newSort = typeof updaterOrValue === 'function'
2277
+ ? updaterOrValue(currentSort)
2278
+ : updaterOrValue;
2279
+ // Always update internal state for reactivity
2280
+ this.sortingState.set(newSort);
2281
+ // For server-side sorting, also call the callback
2282
+ if (sortingConfig?.mode === 'server' &&
2283
+ sortingConfig.onSortingChange) {
2284
+ sortingConfig.onSortingChange(newSort);
2285
+ }
2286
+ },
2287
+ manualSorting: sortingConfig.mode === 'server',
2288
+ enableSorting: true,
2289
+ enableMultiSort: sortingConfig.enableMultiSort ?? false,
2290
+ enableSortingRemoval: sortingConfig.enableSortingRemoval ?? true,
2291
+ maxMultiSortColCount: sortingConfig.maxMultiSortColCount,
2292
+ sortDescFirst: sortingConfig.sortDescFirst ?? false,
2293
+ // When multi-sort is enabled, treat all clicks as multi-sort events
2294
+ isMultiSortEvent: sortingConfig.enableMultiSort
2295
+ ? () => true
2296
+ : undefined,
2297
+ getSortedRowModel: sortingConfig.mode === 'client' ? getSortedRowModel() : undefined,
2298
+ }),
2169
2299
  };
2300
+ return baseConfig;
2301
+ };
2302
+ if (!paginationConfig || paginationConfig.mode === 'infinite') {
2303
+ // No pagination or infinite scroll - simple config
2304
+ return getBaseTableConfig();
2170
2305
  }
2171
2306
  else if (paginationConfig.mode === 'client') {
2172
2307
  // Client-side pagination
2173
2308
  return {
2174
- data: this.rows(),
2175
- columns: this.tableColumns(),
2176
- getCoreRowModel: getCoreRowModel(),
2309
+ ...getBaseTableConfig(),
2177
2310
  getPaginationRowModel: getPaginationRowModel(),
2178
2311
  initialState: {
2179
2312
  pagination: {
@@ -2184,12 +2317,11 @@ class NvDatatable {
2184
2317
  };
2185
2318
  }
2186
2319
  else {
2187
- // Server-side pagination
2320
+ // Server-side pagination - manual pagination with reactive state
2321
+ const baseConfig = getBaseTableConfig();
2188
2322
  const pageSize = this.paginationState().pageSize;
2189
2323
  return {
2190
- data: this.rows(),
2191
- columns: this.tableColumns(),
2192
- getCoreRowModel: getCoreRowModel(),
2324
+ ...baseConfig,
2193
2325
  manualPagination: true,
2194
2326
  pageCount: paginationConfig.totalPageCount !== undefined
2195
2327
  ? paginationConfig.totalPageCount
@@ -2198,13 +2330,19 @@ class NvDatatable {
2198
2330
  : -1,
2199
2331
  state: {
2200
2332
  pagination: this.paginationState(),
2333
+ ...(baseConfig.state && { sorting: baseConfig.state.sorting }),
2201
2334
  },
2202
2335
  onPaginationChange: (updaterOrValue) => {
2203
- if (typeof updaterOrValue === 'function') {
2204
- this.paginationState.set(updaterOrValue(this.paginationState()));
2205
- }
2206
- else {
2207
- this.paginationState.set(updaterOrValue);
2336
+ const newState = typeof updaterOrValue === 'function'
2337
+ ? updaterOrValue(this.paginationState())
2338
+ : updaterOrValue;
2339
+ this.paginationState.set(newState);
2340
+ // Call user's callback for server-side pagination
2341
+ if (paginationConfig.onPaginationChange) {
2342
+ paginationConfig.onPaginationChange({
2343
+ pageIndex: newState.pageIndex,
2344
+ pageSize: newState.pageSize,
2345
+ });
2208
2346
  }
2209
2347
  },
2210
2348
  };
@@ -2243,18 +2381,6 @@ class NvDatatable {
2243
2381
  : undefined,
2244
2382
  };
2245
2383
  });
2246
- // Watch pagination state changes for server mode
2247
- effect(() => {
2248
- const paginationConfig = this.pagination();
2249
- const state = this.paginationState();
2250
- if (paginationConfig?.mode === 'server' &&
2251
- paginationConfig.onPaginationChange) {
2252
- paginationConfig.onPaginationChange({
2253
- pageIndex: state.pageIndex,
2254
- pageSize: state.pageSize,
2255
- });
2256
- }
2257
- }, { allowSignalWrites: true });
2258
2384
  // Set up intersection observer for infinite scroll
2259
2385
  effect(() => {
2260
2386
  const paginationConfig = this.pagination();
@@ -2313,7 +2439,7 @@ class NvDatatable {
2313
2439
  }
2314
2440
  }
2315
2441
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NvDatatable, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2316
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.5", type: NvDatatable, isStandalone: true, selector: "nv-datatable", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "paginationTemplate", first: true, predicate: ["paginationTemplate"], descendants: true, read: TemplateRef }, { propertyName: "cellTemplates", predicate: NvDatatableCellDirective }], viewQueries: [{ propertyName: "tableRows", first: true, predicate: ["tableRow"], descendants: true, isSignal: true }], ngImport: i0, template: `
2442
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.5", type: NvDatatable, isStandalone: true, selector: "nv-datatable", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: false, transformFunction: null }, sorting: { classPropertyName: "sorting", publicName: "sorting", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "paginationTemplate", first: true, predicate: ["paginationTemplate"], descendants: true, read: TemplateRef }, { propertyName: "cellTemplates", predicate: NvDatatableCellDirective }], viewQueries: [{ propertyName: "tableRows", first: true, predicate: ["tableRow"], descendants: true, isSignal: true }], ngImport: i0, template: `
2317
2443
  <nv-table>
2318
2444
  <table>
2319
2445
  <thead [attr.data-sticky-top]="stickyHeader() ? 'true' : null">
@@ -2332,9 +2458,19 @@ class NvDatatable {
2332
2458
  !header.column.columnDef.enableResizing ? 'true' : null
2333
2459
  "
2334
2460
  >
2335
- @if (!header.isPlaceholder) {
2461
+ @if (!header.isPlaceholder) { @if (header.column.getCanSort()) {
2462
+ <nv-tableheader
2463
+ [sortable]="true"
2464
+ [sortDirection]="header.column.getIsSorted() || 'none'"
2465
+ (sortDirectionChanged)="
2466
+ handleSortDirectionChanged($event, header)
2467
+ "
2468
+ >
2469
+ {{ header.column.columnDef.header }}
2470
+ </nv-tableheader>
2471
+ } @else {
2336
2472
  {{ header.column.columnDef.header }}
2337
- }
2473
+ } }
2338
2474
  </th>
2339
2475
  }
2340
2476
  </tr>
@@ -2345,7 +2481,7 @@ class NvDatatable {
2345
2481
  @for (row of table().getRowModel().rows; track row.id; let i = $index)
2346
2482
  {
2347
2483
  <tr
2348
- [attr.data-testid]="'datatable-row-' + row.id"
2484
+ [attr.data-testid]="'datatable-row-' + i"
2349
2485
  #tableRow
2350
2486
  [attr.data-is-last]="
2351
2487
  isInfiniteScroll() && i === table().getRowModel().rows.length - 1
@@ -2354,7 +2490,7 @@ class NvDatatable {
2354
2490
  "
2355
2491
  >
2356
2492
  @for (cell of row.getVisibleCells(); track cell.id) {
2357
- <td [attr.data-testid]="'datatable-cell-' + cell.id">
2493
+ <td [attr.data-testid]="'datatable-cell-' + cell.column.id">
2358
2494
  <ng-container
2359
2495
  *flexRender="
2360
2496
  cell.column.columnDef.cell;
@@ -2409,9 +2545,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
2409
2545
  !header.column.columnDef.enableResizing ? 'true' : null
2410
2546
  "
2411
2547
  >
2412
- @if (!header.isPlaceholder) {
2548
+ @if (!header.isPlaceholder) { @if (header.column.getCanSort()) {
2549
+ <nv-tableheader
2550
+ [sortable]="true"
2551
+ [sortDirection]="header.column.getIsSorted() || 'none'"
2552
+ (sortDirectionChanged)="
2553
+ handleSortDirectionChanged($event, header)
2554
+ "
2555
+ >
2556
+ {{ header.column.columnDef.header }}
2557
+ </nv-tableheader>
2558
+ } @else {
2413
2559
  {{ header.column.columnDef.header }}
2414
- }
2560
+ } }
2415
2561
  </th>
2416
2562
  }
2417
2563
  </tr>
@@ -2422,7 +2568,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
2422
2568
  @for (row of table().getRowModel().rows; track row.id; let i = $index)
2423
2569
  {
2424
2570
  <tr
2425
- [attr.data-testid]="'datatable-row-' + row.id"
2571
+ [attr.data-testid]="'datatable-row-' + i"
2426
2572
  #tableRow
2427
2573
  [attr.data-is-last]="
2428
2574
  isInfiniteScroll() && i === table().getRowModel().rows.length - 1
@@ -2431,7 +2577,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
2431
2577
  "
2432
2578
  >
2433
2579
  @for (cell of row.getVisibleCells(); track cell.id) {
2434
- <td [attr.data-testid]="'datatable-cell-' + cell.id">
2580
+ <td [attr.data-testid]="'datatable-cell-' + cell.column.id">
2435
2581
  <ng-container
2436
2582
  *flexRender="
2437
2583
  cell.column.columnDef.cell;
@@ -2467,6 +2613,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
2467
2613
  type: ContentChildren,
2468
2614
  args: [NvDatatableCellDirective]
2469
2615
  }] } });
2616
+ /********************************* UTILS **************************************/
2617
+ /**
2618
+ * Creates a strongly-typed column factory for a given row type.
2619
+ *
2620
+ * @template Row The row data type (e.g., `Product`)
2621
+ *
2622
+ * @returns {function} A function that accepts a column definition and infers:
2623
+ * - `K` as the field key (`keyof Row`)
2624
+ * - `F` as the return type of `valueFormatter` (defaults to `Row[K]`)
2625
+ *
2626
+ * @example
2627
+ * ```ts
2628
+ * // Define your row type
2629
+ * interface Product {
2630
+ * name: string;
2631
+ * price: number;
2632
+ * }
2633
+ *
2634
+ * const col = makeColumn<Product>();
2635
+ */
2636
+ function makeColumn() {
2637
+ return function define(col) {
2638
+ return col;
2639
+ };
2640
+ }
2470
2641
 
2471
2642
  class ValueAccessor {
2472
2643
  constructor(el) {
@@ -3340,7 +3511,7 @@ const VALUE_ACCESSORS = [
3340
3511
 
3341
3512
  class NovaComponentsModule {
3342
3513
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NovaComponentsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
3343
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.5", ngImport: i0, type: NovaComponentsModule, declarations: [NvAccordion, NvAccordionItem, NvAlert, NvAvatar, NvBadge, NvBreadcrumb, NvBreadcrumbs, NvButton, NvButtongroup, NvCalendar, NvCol, NvDatagrid, NvDatagridcolumn, NvDialog, NvDialogfooter, NvDialogheader, NvFieldcheckbox, NvFielddate, NvFielddaterange, NvFielddropdown, NvFielddropdownitem, NvFielddropdownitemcheck, NvFieldmultiselect, NvFieldnumber, NvFieldpassword, NvFieldradio, NvFieldselect, NvFieldslider, NvFieldtext, NvFieldtextarea, NvFieldtime, NvIcon, NvIconbutton, NvLoader, NvMenu, NvMenuitem, NvNotification, NvNotificationcontainer, NvPopover, NvRow, NvSplit, NvStack, NvTable, NvToggle, NvTogglebutton, NvTogglebuttongroup, NvTooltip], imports: [NvDatatable], exports: [NvAccordion, NvAccordionItem, NvAlert, NvAvatar, NvBadge, NvBreadcrumb, NvBreadcrumbs, NvButton, NvButtongroup, NvCalendar, NvCol, NvDatagrid, NvDatagridcolumn, NvDialog, NvDialogfooter, NvDialogheader, NvFieldcheckbox, NvFielddate, NvFielddaterange, NvFielddropdown, NvFielddropdownitem, NvFielddropdownitemcheck, NvFieldmultiselect, NvFieldnumber, NvFieldpassword, NvFieldradio, NvFieldselect, NvFieldslider, NvFieldtext, NvFieldtextarea, NvFieldtime, NvIcon, NvIconbutton, NvLoader, NvMenu, NvMenuitem, NvNotification, NvNotificationcontainer, NvPopover, NvRow, NvSplit, NvStack, NvTable, NvToggle, NvTogglebutton, NvTogglebuttongroup, NvTooltip, NvDatatable] }); }
3514
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.5", ngImport: i0, type: NovaComponentsModule, declarations: [NvAccordion, NvAccordionItem, NvAlert, NvAvatar, NvBadge, NvBreadcrumb, NvBreadcrumbs, NvButton, NvButtongroup, NvCalendar, NvCol, NvDatagrid, NvDatagridcolumn, NvDialog, NvDialogfooter, NvDialogheader, NvFieldcheckbox, NvFielddate, NvFielddaterange, NvFielddropdown, NvFielddropdownitem, NvFielddropdownitemcheck, NvFieldmultiselect, NvFieldnumber, NvFieldpassword, NvFieldradio, NvFieldselect, NvFieldslider, NvFieldtext, NvFieldtextarea, NvFieldtime, NvIcon, NvIconbutton, NvLoader, NvMenu, NvMenuitem, NvNotification, NvNotificationcontainer, NvPopover, NvRow, NvSplit, NvStack, NvTable, NvTableheader, NvToggle, NvTogglebutton, NvTogglebuttongroup, NvTooltip], imports: [NvDatatable], exports: [NvAccordion, NvAccordionItem, NvAlert, NvAvatar, NvBadge, NvBreadcrumb, NvBreadcrumbs, NvButton, NvButtongroup, NvCalendar, NvCol, NvDatagrid, NvDatagridcolumn, NvDialog, NvDialogfooter, NvDialogheader, NvFieldcheckbox, NvFielddate, NvFielddaterange, NvFielddropdown, NvFielddropdownitem, NvFielddropdownitemcheck, NvFieldmultiselect, NvFieldnumber, NvFieldpassword, NvFieldradio, NvFieldselect, NvFieldslider, NvFieldtext, NvFieldtextarea, NvFieldtime, NvIcon, NvIconbutton, NvLoader, NvMenu, NvMenuitem, NvNotification, NvNotificationcontainer, NvPopover, NvRow, NvSplit, NvStack, NvTable, NvTableheader, NvToggle, NvTogglebutton, NvTogglebuttongroup, NvTooltip, NvDatatable] }); }
3344
3515
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NovaComponentsModule, imports: [NvDatatable] }); }
3345
3516
  }
3346
3517
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NovaComponentsModule, decorators: [{
@@ -3388,6 +3559,7 @@ class NotificationService {
3388
3559
  }
3389
3560
  constructor() {
3390
3561
  this._notifications = new BehaviorSubject([]);
3562
+ this.timers = new Map();
3391
3563
  /**
3392
3564
  * Observable stream of active notifications.
3393
3565
  */
@@ -3425,6 +3597,14 @@ class NotificationService {
3425
3597
  clearRefs() {
3426
3598
  this.elRefs.clear();
3427
3599
  }
3600
+ /**
3601
+ * Clean up all timers and references.
3602
+ */
3603
+ ngOnDestroy() {
3604
+ this.timers.forEach((timer) => clearTimeout(timer));
3605
+ this.timers.clear();
3606
+ this.clearRefs();
3607
+ }
3428
3608
  /**
3429
3609
  * Show a new notification.
3430
3610
  *
@@ -3443,6 +3623,7 @@ class NotificationService {
3443
3623
  icon: options.icon,
3444
3624
  actions: options.actions ?? [],
3445
3625
  actionSlot: options.actionSlot,
3626
+ duration: options.duration ?? 0,
3446
3627
  createdAt: Date.now(),
3447
3628
  };
3448
3629
  const currentNotifications = this._notifications.value;
@@ -3457,6 +3638,13 @@ class NotificationService {
3457
3638
  setTimeout(() => {
3458
3639
  const ref = this.elRefs.get(id);
3459
3640
  ref?.show();
3641
+ // Set up auto-dismiss timer if duration > 0
3642
+ if (notification.duration && notification.duration > 0) {
3643
+ const timer = setTimeout(() => {
3644
+ this.dismiss(id);
3645
+ }, notification.duration);
3646
+ this.timers.set(id, timer);
3647
+ }
3460
3648
  }, 0);
3461
3649
  return id;
3462
3650
  }
@@ -3467,6 +3655,12 @@ class NotificationService {
3467
3655
  * @param {string} id The notification ID to dismiss
3468
3656
  */
3469
3657
  dismiss(id) {
3658
+ // Clear timer if exists
3659
+ const timer = this.timers.get(id);
3660
+ if (timer) {
3661
+ clearTimeout(timer);
3662
+ this.timers.delete(id);
3663
+ }
3470
3664
  this.elRefs.get(id)?.dismiss?.();
3471
3665
  }
3472
3666
  /**
@@ -3475,6 +3669,12 @@ class NotificationService {
3475
3669
  * @param {string} id The notification ID to dismiss
3476
3670
  */
3477
3671
  remove(id) {
3672
+ // Clear timer if exists
3673
+ const timer = this.timers.get(id);
3674
+ if (timer) {
3675
+ clearTimeout(timer);
3676
+ this.timers.delete(id);
3677
+ }
3478
3678
  const currentNotifications = this._notifications.value;
3479
3679
  const filteredNotifications = currentNotifications.filter((notification) => notification.id !== id);
3480
3680
  this._notifications.next(filteredNotifications);
@@ -3483,6 +3683,9 @@ class NotificationService {
3483
3683
  * Immediately remove all active notifications.
3484
3684
  */
3485
3685
  removeAll() {
3686
+ // Clear all timers
3687
+ this.timers.forEach((timer) => clearTimeout(timer));
3688
+ this.timers.clear();
3486
3689
  this._notifications.next([]);
3487
3690
  }
3488
3691
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -3681,5 +3884,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImpor
3681
3884
  * Generated bundle index. Do not edit.
3682
3885
  */
3683
3886
 
3684
- export { NotificationService, NotificationServiceComponent, NovaComponentsModule, NovaComponentsValueAccessorModule, NvAccordion, NvAccordionItem, NvAccordionValueAccessor, NvAlert, NvAlertValueAccessor, NvAvatar, NvBadge, NvBreadcrumb, NvBreadcrumbs, NvButton, NvButtongroup, NvCalendar, NvCalendarValueAccessor, NvCol, NvDatagrid, NvDatagridValueAccessor, NvDatagridcolumn, NvDatatable, NvDatatableCellDirective, NvDialog, NvDialogValueAccessor, NvDialogfooter, NvDialogheader, NvFieldcheckbox, NvFieldcheckboxValueAccessor, NvFielddate, NvFielddateValueAccessor, NvFielddaterange, NvFielddaterangeValueAccessor, NvFielddropdown, NvFielddropdownValueAccessor, NvFielddropdownitem, NvFielddropdownitemcheck, NvFieldmultiselect, NvFieldmultiselectValueAccessor, NvFieldnumber, NvFieldnumberValueAccessor, NvFieldpassword, NvFieldpasswordValueAccessor, NvFieldradio, NvFieldradioValueAccessor, NvFieldselect, NvFieldselectValueAccessor, NvFieldslider, NvFieldsliderValueAccessor, NvFieldtext, NvFieldtextValueAccessor, NvFieldtextarea, NvFieldtextareaValueAccessor, NvFieldtime, NvFieldtimeValueAccessor, NvIcon, NvIconbutton, NvLoader, NvMenu, NvMenuitem, NvNotification, NvNotificationValueAccessor, NvNotificationcontainer, NvPopover, NvPopoverValueAccessor, NvRow, NvSplit, NvSplitValueAccessor, NvStack, NvTable, NvToggle, NvToggleValueAccessor, NvTogglebutton, NvTogglebuttongroup, NvTogglebuttongroupValueAccessor, NvTooltip, VALUE_ACCESSORS, flexRenderComponent as nvDatatableRenderComponent };
3887
+ export { NotificationService, NotificationServiceComponent, NovaComponentsModule, NovaComponentsValueAccessorModule, NvAccordion, NvAccordionItem, NvAccordionValueAccessor, NvAlert, NvAlertValueAccessor, NvAvatar, NvBadge, NvBreadcrumb, NvBreadcrumbs, NvButton, NvButtongroup, NvCalendar, NvCalendarValueAccessor, NvCol, NvDatagrid, NvDatagridValueAccessor, NvDatagridcolumn, NvDatatable, NvDatatableCellDirective, NvDialog, NvDialogValueAccessor, NvDialogfooter, NvDialogheader, NvFieldcheckbox, NvFieldcheckboxValueAccessor, NvFielddate, NvFielddateValueAccessor, NvFielddaterange, NvFielddaterangeValueAccessor, NvFielddropdown, NvFielddropdownValueAccessor, NvFielddropdownitem, NvFielddropdownitemcheck, NvFieldmultiselect, NvFieldmultiselectValueAccessor, NvFieldnumber, NvFieldnumberValueAccessor, NvFieldpassword, NvFieldpasswordValueAccessor, NvFieldradio, NvFieldradioValueAccessor, NvFieldselect, NvFieldselectValueAccessor, NvFieldslider, NvFieldsliderValueAccessor, NvFieldtext, NvFieldtextValueAccessor, NvFieldtextarea, NvFieldtextareaValueAccessor, NvFieldtime, NvFieldtimeValueAccessor, NvIcon, NvIconbutton, NvLoader, NvMenu, NvMenuitem, NvNotification, NvNotificationValueAccessor, NvNotificationcontainer, NvPopover, NvPopoverValueAccessor, NvRow, NvSplit, NvSplitValueAccessor, NvStack, NvTable, NvTableheader, NvToggle, NvToggleValueAccessor, NvTogglebutton, NvTogglebuttongroup, NvTogglebuttongroupValueAccessor, NvTooltip, VALUE_ACCESSORS, makeColumn, flexRenderComponent as nvDatatableRenderComponent };
3685
3888
  //# sourceMappingURL=nova-components.mjs.map