@nova-design-system/nova-vue 3.20.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.
package/README.md CHANGED
@@ -2,26 +2,6 @@
2
2
 
3
3
  **Nova Components Vue** provides an easy way to use [Nova’s native Web Components](https://www.npmjs.com/package/@nova-design-system/nova-webcomponents) within your Vue applications.
4
4
 
5
- - [Nova Components Vue](#nova-components-vue)
6
- - [Key Features](#key-features)
7
- - [Installation](#installation)
8
- - [Setting up Tailwind](#setting-up-tailwind)
9
- - [About Tailwind and the Nova Plugin](#about-tailwind-and-the-nova-plugin)
10
- - [1. Install Tailwind CSS and the Vite Plugin](#1-install-tailwind-css-and-the-vite-plugin)
11
- - [2. Configure the Vite Plugin](#2-configure-the-vite-plugin)
12
- - [3. Create `tailwind.config.ts`](#3-create-tailwindconfigts)
13
- - [4. Configure Tailwind and Nova Plugin in `main.css`](#4-configure-tailwind-and-nova-plugin-in-maincss)
14
- - [5. Register NovaComponents and include the Nova Tokens](#5-register-novacomponents-and-include-the-nova-tokens)
15
- - [6. Use Nova Components with Tailwind Utilities](#6-use-nova-components-with-tailwind-utilities)
16
- - [7. Setup the Nova Font](#7-setup-the-nova-font)
17
- - [Creating Your Own Style Components with Tailwind](#creating-your-own-style-components-with-tailwind)
18
- - [Nova Font Pro Integration](#nova-font-pro-integration)
19
- - [Option 1: Import in Global CSS (Recommended)](#option-1-import-in-global-css-recommended)
20
- - [Option 2: HTML Integration](#option-2-html-integration)
21
-
22
-
23
- ---
24
-
25
5
  ## Key Features
26
6
 
27
7
  - **Lightweight Integration**: Leverage Nova Web Components with minimal configuration in Vue.
@@ -1,4 +1,5 @@
1
1
  import { type VNode, type PropType } from 'vue';
2
+ import { type SortingState } from '@tanstack/vue-table';
2
3
  /**
3
4
  * Creates a typed NvDatatable component for a specific row type. This is the
4
5
  * standard approach for generic components in Vue.
@@ -21,6 +22,10 @@ export declare function createNvDatatable<T>(): import("vue").DefineComponent<im
21
22
  type: PropType<NvDatatablePaginationConfig>;
22
23
  default: any;
23
24
  };
25
+ sorting: {
26
+ type: PropType<NvDatatableSortingConfig>;
27
+ default: any;
28
+ };
24
29
  renderPagination: {
25
30
  type: PropType<(api: NvDatatableRenderPaginationAPI) => VNode>;
26
31
  default: any;
@@ -46,6 +51,10 @@ export declare function createNvDatatable<T>(): import("vue").DefineComponent<im
46
51
  type: PropType<NvDatatablePaginationConfig>;
47
52
  default: any;
48
53
  };
54
+ sorting: {
55
+ type: PropType<NvDatatableSortingConfig>;
56
+ default: any;
57
+ };
49
58
  renderPagination: {
50
59
  type: PropType<(api: NvDatatableRenderPaginationAPI) => VNode>;
51
60
  default: any;
@@ -58,6 +67,7 @@ export declare function createNvDatatable<T>(): import("vue").DefineComponent<im
58
67
  columns: NvDatatableColumn<T, keyof T, T[keyof T]>[];
59
68
  rows: T[];
60
69
  pagination: NvDatatablePaginationConfig;
70
+ sorting: NvDatatableSortingConfig;
61
71
  renderPagination: (api: NvDatatableRenderPaginationAPI) => VNode;
62
72
  stickyHeader: boolean;
63
73
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -152,6 +162,16 @@ export interface NvDatatableColumn<Row, K extends keyof Row = keyof Row, F = Row
152
162
  valueFormatter?: (params: NvTableValueFormatterParams<Row, Row[K], K>) => F;
153
163
  /** Custom cell renderer */
154
164
  renderCell?: (params: NvTableRenderCellParams<Row, F, K>) => VNode | string | number;
165
+ /** Enable/disable sorting for this column */
166
+ sortable?: boolean;
167
+ /** Custom sorting function or built-in function name */
168
+ sortingFn?: ((rowA: any, rowB: any, columnId: string) => number) | string;
169
+ /** Start with descending sort for this column */
170
+ sortDescFirst?: boolean;
171
+ /** Invert the sort order (useful for rankings) */
172
+ invertSorting?: boolean;
173
+ /** Where to place undefined values in sort */
174
+ sortUndefined?: 'first' | 'last' | false | -1 | 1;
155
175
  }
156
176
  /**
157
177
  * Parameters for custom cell rendering function.
@@ -241,3 +261,27 @@ export interface NvDatatableRenderPaginationAPI {
241
261
  /** Whether more items are available (only for infinite scroll) */
242
262
  hasMore?: boolean;
243
263
  }
264
+ /**
265
+ * Sorting configuration for NvDatatable.
266
+ * Supports both client-side and server-side sorting.
267
+ */
268
+ export interface NvDatatableSortingConfig {
269
+ /** Sorting mode */
270
+ mode: 'client' | 'server';
271
+ /** Enable multi-column sorting with Shift+Click */
272
+ enableMultiSort?: boolean;
273
+ /** Allow cycling through to "no sort" state */
274
+ enableSortingRemoval?: boolean;
275
+ /** Maximum number of columns for multi-sort */
276
+ maxMultiSortColCount?: number;
277
+ /** Start with descending sort as first toggle state */
278
+ sortDescFirst?: boolean;
279
+ /** Controlled sort state (for server-side sorting) */
280
+ sortState?: SortingState;
281
+ /** Callback when sorting changes (for server-side sorting) */
282
+ onSortingChange?: (sorting: SortingState) => void;
283
+ }
284
+ /**
285
+ * Sorting state type - array of sort descriptors
286
+ */
287
+ export type NvDataTableSortingState = SortingState;
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable jsdoc/require-jsdoc */
2
2
  import { defineComponent, computed, h, ref, watch, watchEffect, onUnmounted, } from 'vue';
3
- import { useVueTable, getCoreRowModel, getPaginationRowModel, } from '@tanstack/vue-table';
4
- import { NvTable } from '../generated/components';
3
+ import { useVueTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel, } from '@tanstack/vue-table';
4
+ import { NvTable, NvTableheader } from '../generated/components';
5
5
  /**
6
6
  * Creates a typed NvDatatable component for a specific row type. This is the
7
7
  * standard approach for generic components in Vue.
@@ -27,6 +27,10 @@ export function createNvDatatable() {
27
27
  type: Object,
28
28
  default: undefined,
29
29
  },
30
+ sorting: {
31
+ type: Object,
32
+ default: undefined,
33
+ },
30
34
  renderPagination: {
31
35
  type: Function,
32
36
  default: undefined,
@@ -42,69 +46,75 @@ export function createNvDatatable() {
42
46
  pageIndex: 0,
43
47
  pageSize: props.pagination?.initialPageSize || 10,
44
48
  });
49
+ // Sorting state (controlled or uncontrolled)
50
+ const sortingState = ref(props.sorting?.sortState || []);
45
51
  // Ref for observing last row (infinite scroll)
46
52
  const lastRowRef = ref(null);
47
53
  const tableColumns = computed(() => props.columns
48
54
  .filter((col) => !col.hidden)
49
- .map((col) => ({
50
- accessorKey: col.field,
51
- accessorFn: col.valueFormatter
52
- ? (row) => {
53
- const rawValue = row[col.field];
54
- return col.valueFormatter({
55
- value: rawValue,
56
- row,
57
- field: col.field,
58
- });
59
- }
60
- : undefined,
61
- header: col.headerName || String(col.field),
62
- size: col.width,
63
- enableResizing: col.resizable ?? true,
64
- cell: (context) => {
65
- const value = context.getValue();
66
- const row = context.row.original;
67
- const rowIndex = context.row.index;
68
- const field = col.field;
69
- // Priority: slot > renderCell > default
70
- // Sanitize field name to ensure valid HTML attribute name
71
- // Replace invalid characters (anything not a-z, A-Z, 0-9, -, _) with underscore
72
- const sanitizedField = String(field).replace(/[^a-zA-Z0-9_-]/g, '_');
73
- const slotName = `cell-${sanitizedField}`;
74
- if (slots[slotName]) {
75
- return slots[slotName]({ value, row, field, rowIndex });
76
- }
77
- // Use custom renderCell if provided
78
- if (col.renderCell) {
79
- return col.renderCell({
80
- value,
81
- row,
82
- field,
83
- rowIndex,
84
- });
85
- }
86
- // Default rendering
87
- return value;
88
- },
89
- })));
90
- // Create table instance based on pagination mode
91
- // Client-side pagination needs direct config, server-side needs reactive config
92
- let table;
93
- if (!props.pagination || props.pagination.mode === 'infinite') {
94
- // No pagination or infinite scroll - simple config
95
- table = useVueTable({
96
- get data() {
97
- return computed(() => props.rows);
98
- },
99
- get columns() {
100
- return tableColumns.value;
55
+ .map((col) => {
56
+ const columnDef = {
57
+ accessorKey: col.field,
58
+ accessorFn: col.valueFormatter
59
+ ? (row) => {
60
+ const rawValue = row[col.field];
61
+ return col.valueFormatter({
62
+ value: rawValue,
63
+ row,
64
+ field: col.field,
65
+ });
66
+ }
67
+ : undefined,
68
+ header: col.headerName || String(col.field),
69
+ size: col.width,
70
+ enableResizing: col.resizable ?? true,
71
+ // Sorting configuration
72
+ enableSorting: props.sorting ? col.sortable ?? true : false,
73
+ cell: (context) => {
74
+ const value = context.getValue();
75
+ const row = context.row.original;
76
+ const rowIndex = context.row.index;
77
+ const field = col.field;
78
+ // Priority: slot > renderCell > default
79
+ // Sanitize field name to ensure valid HTML attribute name
80
+ // Replace invalid characters (anything not a-z, A-Z, 0-9, -, _) with underscore
81
+ const sanitizedField = String(field).replace(/[^a-zA-Z0-9_-]/g, '_');
82
+ const slotName = `cell-${sanitizedField}`;
83
+ if (slots[slotName]) {
84
+ return slots[slotName]({ value, row, field, rowIndex });
85
+ }
86
+ // Use custom renderCell if provided
87
+ if (col.renderCell) {
88
+ return col.renderCell({
89
+ value,
90
+ row,
91
+ field,
92
+ rowIndex,
93
+ });
94
+ }
95
+ // Default rendering
96
+ return value;
101
97
  },
102
- getCoreRowModel: getCoreRowModel(),
103
- });
104
- }
105
- else if (props.pagination.mode === 'client') {
106
- // Client-side pagination - table manages its own state
107
- table = useVueTable({
98
+ };
99
+ // Add optional sorting properties only if defined
100
+ if (col.sortingFn !== undefined) {
101
+ // @ts-expect-error - TanStack typing is strict but this works at runtime
102
+ columnDef.sortingFn = col.sortingFn;
103
+ }
104
+ if (col.sortDescFirst !== undefined) {
105
+ columnDef.sortDescFirst = col.sortDescFirst;
106
+ }
107
+ if (col.invertSorting !== undefined) {
108
+ columnDef.invertSorting = col.invertSorting;
109
+ }
110
+ if (col.sortUndefined !== undefined) {
111
+ columnDef.sortUndefined = col.sortUndefined;
112
+ }
113
+ return columnDef;
114
+ }));
115
+ // Determine base table configuration with sorting
116
+ const getBaseTableConfig = () => {
117
+ const baseConfig = {
108
118
  get data() {
109
119
  return computed(() => props.rows);
110
120
  },
@@ -112,54 +122,103 @@ export function createNvDatatable() {
112
122
  return tableColumns.value;
113
123
  },
114
124
  getCoreRowModel: getCoreRowModel(),
115
- getPaginationRowModel: getPaginationRowModel(),
116
- initialState: {
117
- pagination: {
118
- pageIndex: 0,
119
- pageSize: props.pagination.initialPageSize || 10,
125
+ // Sorting configuration
126
+ ...(props.sorting && {
127
+ get state() {
128
+ return {
129
+ sorting: props.sorting.mode === 'server' && props.sorting.sortState
130
+ ? props.sorting.sortState
131
+ : sortingState.value,
132
+ };
120
133
  },
121
- },
122
- });
123
- }
124
- else {
125
- // Server-side pagination - manual pagination with reactive state
126
- table = useVueTable({
127
- get data() {
128
- return computed(() => props.rows);
129
- },
130
- get columns() {
131
- return tableColumns.value;
132
- },
133
- getCoreRowModel: getCoreRowModel(),
134
- manualPagination: true,
135
- get pageCount() {
136
- if (!props.pagination || props.pagination.mode !== 'server') {
134
+ onSortingChange: (updaterOrValue) => {
135
+ const currentSort = props.sorting.mode === 'server' && props.sorting.sortState
136
+ ? props.sorting.sortState
137
+ : sortingState.value;
138
+ const newSort = typeof updaterOrValue === 'function'
139
+ ? updaterOrValue(currentSort)
140
+ : updaterOrValue;
141
+ // Always update internal state for reactivity
142
+ sortingState.value = newSort;
143
+ // For server-side sorting, also call the callback
144
+ if (props.sorting?.mode === 'server' &&
145
+ props.sorting.onSortingChange) {
146
+ props.sorting.onSortingChange(newSort);
147
+ }
148
+ },
149
+ manualSorting: props.sorting.mode === 'server',
150
+ enableSorting: true,
151
+ enableMultiSort: props.sorting.enableMultiSort ?? false,
152
+ enableSortingRemoval: props.sorting.enableSortingRemoval ?? true,
153
+ maxMultiSortColCount: props.sorting.maxMultiSortColCount,
154
+ sortDescFirst: props.sorting.sortDescFirst ?? false,
155
+ // When multi-sort is enabled, treat all clicks as multi-sort events
156
+ isMultiSortEvent: props.sorting.enableMultiSort
157
+ ? () => true
158
+ : undefined,
159
+ getSortedRowModel: props.sorting.mode === 'client' ? getSortedRowModel() : undefined,
160
+ }),
161
+ };
162
+ return baseConfig;
163
+ };
164
+ // Create reactive table instance based on pagination mode
165
+ const table = computed(() => {
166
+ const baseConfig = getBaseTableConfig();
167
+ if (!props.pagination || props.pagination.mode === 'infinite') {
168
+ // No pagination or infinite scroll - simple config
169
+ return useVueTable(baseConfig);
170
+ }
171
+ else if (props.pagination.mode === 'client') {
172
+ // Client-side pagination - table manages its own state
173
+ return useVueTable({
174
+ ...baseConfig,
175
+ getPaginationRowModel: getPaginationRowModel(),
176
+ initialState: {
177
+ pagination: {
178
+ pageIndex: 0,
179
+ pageSize: props.pagination.initialPageSize || 10,
180
+ },
181
+ },
182
+ });
183
+ }
184
+ else {
185
+ // Server-side pagination - manual pagination with reactive state
186
+ return useVueTable({
187
+ ...baseConfig,
188
+ manualPagination: true,
189
+ get pageCount() {
190
+ if (!props.pagination || props.pagination.mode !== 'server') {
191
+ return -1;
192
+ }
193
+ const pageSize = paginationState.value.pageSize;
194
+ if (props.pagination.totalPageCount !== undefined) {
195
+ return props.pagination.totalPageCount;
196
+ }
197
+ else if (props.pagination.totalRowCount !== undefined) {
198
+ return Math.ceil(props.pagination.totalRowCount / pageSize);
199
+ }
137
200
  return -1;
138
- }
139
- const pageSize = paginationState.value.pageSize;
140
- if (props.pagination.totalPageCount !== undefined) {
141
- return props.pagination.totalPageCount;
142
- }
143
- else if (props.pagination.totalRowCount !== undefined) {
144
- return Math.ceil(props.pagination.totalRowCount / pageSize);
145
- }
146
- return -1;
147
- },
148
- get state() {
149
- return {
150
- pagination: paginationState.value,
151
- };
152
- },
153
- onPaginationChange: (updaterOrValue) => {
154
- if (typeof updaterOrValue === 'function') {
155
- paginationState.value = updaterOrValue(paginationState.value);
156
- }
157
- else {
158
- paginationState.value = updaterOrValue;
159
- }
160
- },
161
- });
162
- }
201
+ },
202
+ get state() {
203
+ const sortState = baseConfig.state
204
+ ? baseConfig.state.sorting
205
+ : undefined;
206
+ return {
207
+ pagination: paginationState.value,
208
+ ...(sortState !== undefined && { sorting: sortState }),
209
+ };
210
+ },
211
+ onPaginationChange: (updaterOrValue) => {
212
+ if (typeof updaterOrValue === 'function') {
213
+ paginationState.value = updaterOrValue(paginationState.value);
214
+ }
215
+ else {
216
+ paginationState.value = updaterOrValue;
217
+ }
218
+ },
219
+ });
220
+ }
221
+ });
163
222
  // Handle pagination changes for server mode
164
223
  watch(paginationState, (newState) => {
165
224
  if (props.pagination?.mode === 'server' &&
@@ -215,8 +274,8 @@ export function createNvDatatable() {
215
274
  if (!props.pagination) {
216
275
  return null;
217
276
  }
218
- const tablePaginationState = table.getState().pagination;
219
- const pageCount = table.getPageCount();
277
+ const tablePaginationState = table.value.getState().pagination;
278
+ const pageCount = table.value.getPageCount();
220
279
  const rowCount = props.pagination.mode === 'server'
221
280
  ? props.pagination.totalRowCount || props.rows.length
222
281
  : props.rows.length;
@@ -225,14 +284,14 @@ export function createNvDatatable() {
225
284
  pageSize: tablePaginationState.pageSize,
226
285
  pageCount,
227
286
  rowCount,
228
- firstPage: () => table.setPageIndex(0),
229
- previousPage: () => table.previousPage(),
230
- nextPage: () => table.nextPage(),
231
- lastPage: () => table.setPageIndex(pageCount - 1),
232
- setPageIndex: (index) => table.setPageIndex(index),
233
- setPageSize: (size) => table.setPageSize(size),
234
- canPreviousPage: table.getCanPreviousPage(),
235
- canNextPage: table.getCanNextPage(),
287
+ firstPage: () => table.value.setPageIndex(0),
288
+ previousPage: () => table.value.previousPage(),
289
+ nextPage: () => table.value.nextPage(),
290
+ lastPage: () => table.value.setPageIndex(pageCount - 1),
291
+ setPageIndex: (index) => table.value.setPageIndex(index),
292
+ setPageSize: (size) => table.value.setPageSize(size),
293
+ canPreviousPage: table.value.getCanPreviousPage(),
294
+ canNextPage: table.value.getCanNextPage(),
236
295
  isLoading: props.pagination.mode === 'infinite'
237
296
  ? props.pagination.isLoading
238
297
  : undefined,
@@ -242,7 +301,7 @@ export function createNvDatatable() {
242
301
  };
243
302
  });
244
303
  return () => {
245
- const tableRows = table.getRowModel().rows;
304
+ const tableRows = table.value.getRowModel().rows;
246
305
  const isInfiniteScroll = props.pagination?.mode === 'infinite';
247
306
  const tableElement = h(NvTable, attrs, {
248
307
  default: () => [
@@ -250,25 +309,44 @@ export function createNvDatatable() {
250
309
  h('thead', {
251
310
  'data-sticky-top': props.stickyHeader ? 'true' : undefined,
252
311
  }, [
253
- ...table.getHeaderGroups().map((headerGroup) => h('tr', { key: headerGroup.id }, [
254
- ...headerGroup.headers.map((header) => h('th', {
255
- key: header.id,
256
- 'data-testid': `datatable-header-${header.id}`,
257
- style: {
258
- width: header.column.columnDef.size
259
- ? `${header.column.columnDef.size}px`
260
- : undefined,
261
- },
262
- 'data-no-resize': header.column.columnDef
263
- .enableResizing
312
+ ...table.value.getHeaderGroups().map((headerGroup) => h('tr', { key: headerGroup.id }, [
313
+ ...headerGroup.headers.map((header) => {
314
+ const canSort = header.column.getCanSort();
315
+ const sortDirection = header.column.getIsSorted();
316
+ return h('th', {
317
+ key: header.id,
318
+ 'data-testid': `datatable-header-${header.id}`,
319
+ style: {
320
+ width: header.column.columnDef.size
321
+ ? `${header.column.columnDef.size}px`
322
+ : undefined,
323
+ },
324
+ 'data-no-resize': header.column.columnDef
325
+ .enableResizing
326
+ ? null
327
+ : true,
328
+ }, header.isPlaceholder
264
329
  ? null
265
- : true,
266
- }, header.isPlaceholder
267
- ? null
268
- : typeof header.column.columnDef.header ===
269
- 'function'
270
- ? header.column.columnDef.header(header.getContext())
271
- : header.column.columnDef.header)),
330
+ : canSort
331
+ ? h(NvTableheader, {
332
+ sortable: true,
333
+ sortDirection: sortDirection || 'none',
334
+ onSortDirectionChanged: (event) => {
335
+ // Call the TanStack handler
336
+ const handler = header.column.getToggleSortingHandler();
337
+ handler?.(event);
338
+ },
339
+ }, {
340
+ default: () => typeof header.column.columnDef.header ===
341
+ 'function'
342
+ ? header.column.columnDef.header(header.getContext())
343
+ : header.column.columnDef.header,
344
+ })
345
+ : typeof header.column.columnDef.header ===
346
+ 'function'
347
+ ? header.column.columnDef.header(header.getContext())
348
+ : header.column.columnDef.header);
349
+ }),
272
350
  ])),
273
351
  ]),
274
352
  h('tbody', {}, [
@@ -276,12 +354,12 @@ export function createNvDatatable() {
276
354
  const isLastRow = isInfiniteScroll && index === tableRows.length - 1;
277
355
  return h('tr', {
278
356
  key: row.id,
279
- 'data-testid': `datatable-row-${row.id}`,
357
+ 'data-testid': `datatable-row-${index}`,
280
358
  ref: isLastRow ? lastRowRef : undefined,
281
359
  }, [
282
360
  ...row.getVisibleCells().map((cell) => h('td', {
283
361
  key: cell.id,
284
- 'data-testid': `datatable-cell-${cell.id}`,
362
+ 'data-testid': `datatable-cell-${cell.column.id}`,
285
363
  }, typeof cell.column.columnDef.cell === 'function'
286
364
  ? cell.column.columnDef.cell(cell.getContext())
287
365
  : cell.getValue())),
@@ -62,6 +62,7 @@ export declare const NvRow: import("vue").DefineSetupFnComponent<JSX.NvRow & imp
62
62
  export declare const NvSplit: import("vue").DefineSetupFnComponent<JSX.NvSplit & import("./vue-component-lib/utils").InputProps<number[]>, {}, {}, JSX.NvSplit & import("./vue-component-lib/utils").InputProps<number[]> & {}, import("vue").PublicProps>;
63
63
  export declare const NvStack: import("vue").DefineSetupFnComponent<JSX.NvStack & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvStack & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
64
64
  export declare const NvTable: import("vue").DefineSetupFnComponent<JSX.NvTable & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvTable & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
65
+ export declare const NvTableheader: import("vue").DefineSetupFnComponent<JSX.NvTableheader & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvTableheader & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
65
66
  export declare const NvToggle: import("vue").DefineSetupFnComponent<JSX.NvToggle & import("./vue-component-lib/utils").InputProps<boolean>, {}, {}, JSX.NvToggle & import("./vue-component-lib/utils").InputProps<boolean> & {}, import("vue").PublicProps>;
66
67
  export declare const NvTogglebutton: import("vue").DefineSetupFnComponent<JSX.NvTogglebutton & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvTogglebutton & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
67
68
  export declare const NvTogglebuttongroup: import("vue").DefineSetupFnComponent<JSX.NvTogglebuttongroup & import("./vue-component-lib/utils").InputProps<string[]>, {}, {}, JSX.NvTogglebuttongroup & import("./vue-component-lib/utils").InputProps<string[]> & {}, import("vue").PublicProps>;
@@ -75,7 +75,8 @@ export const NvButton = /*@__PURE__*/ defineContainer('nv-button', undefined, [
75
75
  'disabled',
76
76
  'fluid',
77
77
  'type',
78
- 'form'
78
+ 'form',
79
+ 'disableTabindex'
79
80
  ]);
80
81
  export const NvButtongroup = /*@__PURE__*/ defineContainer('nv-buttongroup', undefined, [
81
82
  'size',
@@ -515,7 +516,8 @@ export const NvIconbutton = /*@__PURE__*/ defineContainer('nv-iconbutton', undef
515
516
  'active',
516
517
  'name',
517
518
  'type',
518
- 'shape'
519
+ 'shape',
520
+ 'disableTabindex'
519
521
  ]);
520
522
  export const NvLoader = /*@__PURE__*/ defineContainer('nv-loader', undefined, [
521
523
  'size',
@@ -584,6 +586,11 @@ export const NvStack = /*@__PURE__*/ defineContainer('nv-stack', undefined, [
584
586
  'vertical'
585
587
  ]);
586
588
  export const NvTable = /*@__PURE__*/ defineContainer('nv-table', undefined);
589
+ export const NvTableheader = /*@__PURE__*/ defineContainer('nv-tableheader', undefined, [
590
+ 'sortable',
591
+ 'sortDirection',
592
+ 'sortDirectionChanged'
593
+ ]);
587
594
  export const NvToggle = /*@__PURE__*/ defineContainer('nv-toggle', undefined, [
588
595
  'inputId',
589
596
  'name',
@@ -33,6 +33,8 @@ export interface NotificationOptions {
33
33
  actions?: NotificationAction[];
34
34
  /** Custom components for the notification actions. */
35
35
  actionSlot?: Component;
36
+ /** Duration in milliseconds before auto-dismissing. 0 = sticky (no auto-dismiss). Default: 0 */
37
+ duration?: number;
36
38
  }
37
39
  /**
38
40
  * A notification with all required fields populated.
@@ -25,6 +25,7 @@ class NotificationManager {
25
25
  notifications = ref([]);
26
26
  options;
27
27
  containerApp = null;
28
+ timers = new Map();
28
29
  constructor(options = {}) {
29
30
  this.options = {
30
31
  position: options.position || 'top-right',
@@ -116,6 +117,7 @@ class NotificationManager {
116
117
  actions: options.actions ?? [],
117
118
  actionSlot: options.actionSlot,
118
119
  icon: options.icon,
120
+ duration: options.duration ?? 0,
119
121
  createdAt: Date.now(),
120
122
  };
121
123
  // Remove oldest notifications if we exceed max
@@ -130,6 +132,13 @@ class NotificationManager {
130
132
  const ref = this.elRefs.get(id);
131
133
  const el = unwrapNotificationEl(ref);
132
134
  el?.show();
135
+ // Set up auto-dismiss timer if duration > 0
136
+ if (notification.duration && notification.duration > 0) {
137
+ const timer = setTimeout(() => {
138
+ this.dismiss(id);
139
+ }, notification.duration);
140
+ this.timers.set(id, timer);
141
+ }
133
142
  }, 0);
134
143
  return id;
135
144
  };
@@ -140,6 +149,12 @@ class NotificationManager {
140
149
  * @param {string} id - The notification ID to dismiss
141
150
  */
142
151
  dismiss = (id) => {
152
+ // Clear timer if exists
153
+ const timer = this.timers.get(id);
154
+ if (timer) {
155
+ clearTimeout(timer);
156
+ this.timers.delete(id);
157
+ }
143
158
  const ref = this.elRefs.get(id);
144
159
  const el = unwrapNotificationEl(ref);
145
160
  el?.dismiss();
@@ -150,6 +165,12 @@ class NotificationManager {
150
165
  * @param {string} id - The notification ID to dismiss
151
166
  */
152
167
  remove = (id) => {
168
+ // Clear timer if exists
169
+ const timer = this.timers.get(id);
170
+ if (timer) {
171
+ clearTimeout(timer);
172
+ this.timers.delete(id);
173
+ }
153
174
  this.notifications.value = this.notifications.value.filter((notification) => notification.id !== id);
154
175
  this.elRefs.delete(id);
155
176
  };
@@ -157,6 +178,9 @@ class NotificationManager {
157
178
  * Clear all active notifications.
158
179
  */
159
180
  removeAll = () => {
181
+ // Clear all timers
182
+ this.timers.forEach((timer) => clearTimeout(timer));
183
+ this.timers.clear();
160
184
  this.notifications.value = [];
161
185
  };
162
186
  /**
@@ -175,6 +199,9 @@ class NotificationManager {
175
199
  * Destroy the notification manager and clean up resources.
176
200
  */
177
201
  destroy() {
202
+ // Clear all timers
203
+ this.timers.forEach((timer) => clearTimeout(timer));
204
+ this.timers.clear();
178
205
  if (this.containerApp) {
179
206
  this.containerApp.unmount();
180
207
  this.containerApp = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nova-design-system/nova-vue",
3
- "version": "3.20.0",
3
+ "version": "3.21.0",
4
4
  "description": "Nova is a design system created by Elia Group to empower creators to efficiently build solutions that people love to use.",
5
5
  "author": "Elia Group",
6
6
  "homepage": "https://nova.eliagroup.io",