ng-dynamic-datatable 0.0.0 → 0.0.1

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 (29) hide show
  1. package/README.md +109 -34
  2. package/fesm2022/ng-dynamic-datatable.mjs +469 -0
  3. package/fesm2022/ng-dynamic-datatable.mjs.map +1 -0
  4. package/index.d.ts +5 -0
  5. package/lib/dynamic-datatable.component.d.ts +79 -0
  6. package/lib/dynamic-datatable.types.d.ts +4 -0
  7. package/lib/models.d.ts +45 -0
  8. package/package.json +35 -38
  9. package/{projects/dynamic-datatable/src/public-api.ts → public-api.d.ts} +2 -6
  10. package/.editorconfig +0 -17
  11. package/.vscode/extensions.json +0 -4
  12. package/.vscode/launch.json +0 -20
  13. package/.vscode/tasks.json +0 -42
  14. package/angular.json +0 -44
  15. package/projects/dynamic-datatable/README.md +0 -134
  16. package/projects/dynamic-datatable/ng-package.json +0 -7
  17. package/projects/dynamic-datatable/package.json +0 -24
  18. package/projects/dynamic-datatable/src/lib/dynamic-datatable.component.html +0 -137
  19. package/projects/dynamic-datatable/src/lib/dynamic-datatable.component.scss +0 -237
  20. package/projects/dynamic-datatable/src/lib/dynamic-datatable.component.spec.ts +0 -23
  21. package/projects/dynamic-datatable/src/lib/dynamic-datatable.component.ts +0 -556
  22. package/projects/dynamic-datatable/src/lib/dynamic-datatable.service.spec.ts +0 -16
  23. package/projects/dynamic-datatable/src/lib/dynamic-datatable.service.ts +0 -9
  24. package/projects/dynamic-datatable/src/lib/dynamic-datatable.types.ts +0 -4
  25. package/projects/dynamic-datatable/src/lib/models.ts +0 -52
  26. package/projects/dynamic-datatable/tsconfig.lib.json +0 -15
  27. package/projects/dynamic-datatable/tsconfig.lib.prod.json +0 -11
  28. package/projects/dynamic-datatable/tsconfig.spec.json +0 -15
  29. package/tsconfig.json +0 -32
package/README.md CHANGED
@@ -1,59 +1,134 @@
1
- # NgDynamicDatatable
1
+ # dynamic-datatable
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.27.
3
+ Config-driven Angular DataTable library that accepts columns, JSON data, and feature configuration.
4
4
 
5
- ## Development server
5
+ ## Features
6
6
 
7
- To start a local development server, run:
7
+ - Dynamic columns and data inputs
8
+ - Sorting (column-wise)
9
+ - Searching across visible columns
10
+ - Pagination and rows-per-page selection
11
+ - Row selection and select-all
12
+ - Row action events
13
+ - CSV and Excel export
14
+
15
+ ## Install
8
16
 
9
17
  ```bash
10
- ng serve
18
+ npm install dynamic-datatable
11
19
  ```
12
20
 
13
- Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
14
-
15
- ## Code scaffolding
16
-
17
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
18
-
19
- ```bash
20
- ng generate component component-name
21
+ ## Usage
22
+
23
+ Import the standalone component directly in your consuming component:
24
+
25
+ ```ts
26
+ import { Component } from '@angular/core';
27
+ import {
28
+ DynamicDatatableComponent,
29
+ DynamicDataTableConfig,
30
+ DataTableColumn
31
+ } from 'dynamic-datatable';
32
+
33
+ @Component({
34
+ selector: 'app-users',
35
+ standalone: true,
36
+ imports: [DynamicDatatableComponent],
37
+ template: `
38
+ <lib-dynamic-datatable
39
+ [config]="config"
40
+ [columns]="columns"
41
+ [data]="rows"
42
+ (actionSelected)="onAction($event)"
43
+ (selectionChange)="onSelection($event)"
44
+ />
45
+ `
46
+ })
47
+ export class UsersComponent {
48
+ rows = [
49
+ { id: 1, name: 'Aarav Sharma', email: 'aarav.sharma@company.in', salary: '19366.53', status: 'Resigned' },
50
+ { id: 2, name: 'Ananya Patel', email: 'ananya.patel@company.in', salary: '10745.32', status: 'Applied' }
51
+ ];
52
+
53
+ columns: DataTableColumn[] = [
54
+ { key: 'name', label: 'Name', sortable: true, width: '220px' },
55
+ { key: 'email', label: 'Email', sortable: true, width: '260px' },
56
+ { key: 'salary', label: 'Salary', sortable: true, width: '120px' },
57
+ {
58
+ key: 'status',
59
+ label: 'Status',
60
+ sortable: true,
61
+ render: (row) => `<span>${row['status']}</span>`
62
+ }
63
+ ];
64
+
65
+ config: DynamicDataTableConfig = {
66
+ title: 'Users',
67
+ features: {
68
+ export: true,
69
+ sorting: true,
70
+ searching: true,
71
+ pagination: true,
72
+ rowsPerPage: true,
73
+ actions: true,
74
+ checkboxSelection: true
75
+ },
76
+ rowsPerPageOptions: [5, 10, 25],
77
+ defaultRowsPerPage: 10,
78
+ exportOptions: { fileName: 'users-export', formats: ['csv', 'excel'] },
79
+ actions: [
80
+ { label: 'Details', id: 'details' },
81
+ { label: 'Delete', id: 'delete', danger: true }
82
+ ]
83
+ };
84
+
85
+ onAction(event: unknown): void {
86
+ console.log(event);
87
+ }
88
+
89
+ onSelection(rows: unknown[]): void {
90
+ console.log(rows);
91
+ }
92
+ }
21
93
  ```
22
94
 
23
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
95
+ ## Component API
24
96
 
25
- ```bash
26
- ng generate --help
27
- ```
97
+ Inputs:
28
98
 
29
- ## Building
99
+ - `config: DynamicDataTableConfig`
100
+ - `columns: DataTableColumn[]`
101
+ - `data: Record<string, unknown>[]`
30
102
 
31
- To build the project run:
103
+ Outputs:
32
104
 
33
- ```bash
34
- ng build
35
- ```
105
+ - `actionSelected`
106
+ - `selectionChange`
107
+ - `sortChange`
108
+ - `pageChange`
109
+ - `exportTriggered`
36
110
 
37
- This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
111
+ Public methods:
38
112
 
39
- ## Running unit tests
113
+ - `refresh()`
114
+ - `updateData(newData)`
115
+ - `updateConfig(newConfig)`
116
+ - `getSelectedRows()`
117
+ - `clearSelection()`
40
118
 
41
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
119
+ ## Build
42
120
 
43
121
  ```bash
44
- ng test
122
+ ng build dynamic-datatable
45
123
  ```
46
124
 
47
- ## Running end-to-end tests
125
+ Artifacts are generated in `dist/dynamic-datatable`.
48
126
 
49
- For end-to-end (e2e) testing, run:
127
+ ## Publish to npm
50
128
 
51
129
  ```bash
52
- ng e2e
130
+ cd dist/dynamic-datatable
131
+ npm publish --access public
53
132
  ```
54
133
 
55
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
56
-
57
- ## Additional Resources
58
-
59
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
134
+ Before publishing, ensure the package name in `projects/dynamic-datatable/package.json` is unique on npm.
@@ -0,0 +1,469 @@
1
+ import * as i1 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { EventEmitter, Output, Input, Component } from '@angular/core';
5
+
6
+ const DEFAULT_CONFIG = {
7
+ title: 'Dynamic DataTable',
8
+ features: {
9
+ export: true,
10
+ sorting: true,
11
+ searching: true,
12
+ pagination: true,
13
+ rowsPerPage: true,
14
+ actions: true,
15
+ checkboxSelection: true
16
+ },
17
+ rowsPerPageOptions: [5, 10, 25, 50],
18
+ defaultRowsPerPage: 10,
19
+ exportOptions: {
20
+ fileName: 'datatable-export',
21
+ formats: ['csv', 'excel']
22
+ }
23
+ };
24
+ class DynamicDatatableComponent {
25
+ config = {};
26
+ columns = [];
27
+ data = [];
28
+ actionSelected = new EventEmitter();
29
+ selectionChange = new EventEmitter();
30
+ sortChange = new EventEmitter();
31
+ pageChange = new EventEmitter();
32
+ exportTriggered = new EventEmitter();
33
+ mergedConfig = {
34
+ title: DEFAULT_CONFIG.title,
35
+ features: { ...DEFAULT_CONFIG.features },
36
+ rowsPerPageOptions: [...DEFAULT_CONFIG.rowsPerPageOptions],
37
+ defaultRowsPerPage: DEFAULT_CONFIG.defaultRowsPerPage,
38
+ exportOptions: { ...DEFAULT_CONFIG.exportOptions },
39
+ columns: [],
40
+ actions: []
41
+ };
42
+ mergedColumns = [];
43
+ allRows = [];
44
+ filteredRows = [];
45
+ pagedRows = [];
46
+ searchTerm = '';
47
+ currentPage = 1;
48
+ rowsPerPage = DEFAULT_CONFIG.defaultRowsPerPage;
49
+ totalPages = 1;
50
+ sortState = { key: null, direction: 'asc' };
51
+ selectedRows = new Set();
52
+ openActionMenuRowId = null;
53
+ ngOnChanges(_changes) {
54
+ this.refresh();
55
+ }
56
+ refresh() {
57
+ this.applyConfig();
58
+ this.normalizeRows();
59
+ this.applyFiltersSortAndPagination();
60
+ }
61
+ updateData(newData) {
62
+ this.data = Array.isArray(newData) ? [...newData] : [];
63
+ this.refresh();
64
+ }
65
+ updateConfig(newConfig) {
66
+ this.config = { ...this.config, ...newConfig };
67
+ this.refresh();
68
+ }
69
+ getSelectedRows() {
70
+ return this.allRows.filter((row) => this.selectedRows.has(row.id)).map((row) => row.data);
71
+ }
72
+ clearSelection() {
73
+ this.selectedRows.clear();
74
+ this.selectionChange.emit([]);
75
+ }
76
+ isFeatureOn(feature) {
77
+ return Boolean(this.mergedConfig.features?.[feature]);
78
+ }
79
+ isExportFormatEnabled(format) {
80
+ const formats = this.mergedConfig.exportOptions?.formats ?? [];
81
+ return formats.includes(format);
82
+ }
83
+ onSearchInput(event) {
84
+ const input = event.target;
85
+ this.searchTerm = (input.value || '').trim().toLowerCase();
86
+ this.currentPage = 1;
87
+ this.applyFiltersSortAndPagination();
88
+ }
89
+ onRowsPerPageChange(event) {
90
+ const select = event.target;
91
+ const value = Number(select.value);
92
+ this.rowsPerPage = Number.isFinite(value) && value > 0 ? value : this.getInitialRowsPerPage();
93
+ this.currentPage = 1;
94
+ this.applyFiltersSortAndPagination();
95
+ }
96
+ toggleSort(column) {
97
+ if (!this.isFeatureOn('sorting') || !column.sortable) {
98
+ return;
99
+ }
100
+ const key = String(column.key);
101
+ if (this.sortState.key === key) {
102
+ this.sortState.direction = this.sortState.direction === 'asc' ? 'desc' : 'asc';
103
+ }
104
+ else {
105
+ this.sortState = { key, direction: 'asc' };
106
+ }
107
+ this.currentPage = 1;
108
+ this.sortChange.emit({ ...this.sortState });
109
+ this.applyFiltersSortAndPagination();
110
+ }
111
+ getSortIndicator(columnKey) {
112
+ if (this.sortState.key !== columnKey) {
113
+ return '↕';
114
+ }
115
+ return this.sortState.direction === 'asc' ? '↑' : '↓';
116
+ }
117
+ goToPage(page) {
118
+ if (!this.isFeatureOn('pagination')) {
119
+ return;
120
+ }
121
+ const bounded = Math.min(Math.max(page, 1), this.totalPages);
122
+ if (bounded === this.currentPage) {
123
+ return;
124
+ }
125
+ this.currentPage = bounded;
126
+ this.pageChange.emit(this.currentPage);
127
+ this.updatePagedRowsOnly();
128
+ }
129
+ getPaginationPages() {
130
+ return Array.from({ length: this.totalPages }, (_, idx) => idx + 1);
131
+ }
132
+ isRowSelected(rowId) {
133
+ return this.selectedRows.has(rowId);
134
+ }
135
+ toggleRowSelection(row, event) {
136
+ const input = event.target;
137
+ if (input.checked) {
138
+ this.selectedRows.add(row.id);
139
+ }
140
+ else {
141
+ this.selectedRows.delete(row.id);
142
+ }
143
+ this.emitSelection();
144
+ }
145
+ toggleSelectAllCurrentPage(event) {
146
+ const input = event.target;
147
+ this.pagedRows.forEach((row) => {
148
+ if (input.checked) {
149
+ this.selectedRows.add(row.id);
150
+ }
151
+ else {
152
+ this.selectedRows.delete(row.id);
153
+ }
154
+ });
155
+ this.emitSelection();
156
+ }
157
+ isCurrentPageFullySelected() {
158
+ if (!this.pagedRows.length) {
159
+ return false;
160
+ }
161
+ return this.pagedRows.every((row) => this.selectedRows.has(row.id));
162
+ }
163
+ toggleActionMenu(rowId, event) {
164
+ event.stopPropagation();
165
+ this.openActionMenuRowId = this.openActionMenuRowId === rowId ? null : rowId;
166
+ }
167
+ closeActionMenu() {
168
+ this.openActionMenuRowId = null;
169
+ }
170
+ isActionMenuOpen(rowId) {
171
+ return this.openActionMenuRowId === rowId;
172
+ }
173
+ onActionItemClick(actionIndex, row) {
174
+ if (!Number.isInteger(actionIndex) || actionIndex < 0) {
175
+ return;
176
+ }
177
+ const action = this.mergedConfig.actions?.[actionIndex];
178
+ if (!action) {
179
+ return;
180
+ }
181
+ const rowIndex = this.filteredRows.findIndex((item) => item.id === row.id);
182
+ this.actionSelected.emit({ action, row: row.data, rowIndex });
183
+ this.closeActionMenu();
184
+ }
185
+ exportCSV() {
186
+ const rows = this.getRowsForExport();
187
+ this.exportTriggered.emit({ format: 'csv', rows });
188
+ const headers = this.mergedColumns.map((column) => this.escapeCsv(column.label));
189
+ const lines = rows.map((row) => {
190
+ return this.mergedColumns
191
+ .map((column) => this.escapeCsv(this.getCellTextValue(row, column)))
192
+ .join(',');
193
+ });
194
+ const csv = [headers.join(','), ...lines].join('\n');
195
+ this.downloadFile(csv, `${this.getExportFileName()}.csv`, 'text/csv;charset=utf-8;');
196
+ }
197
+ exportExcel() {
198
+ const rows = this.getRowsForExport();
199
+ this.exportTriggered.emit({ format: 'excel', rows });
200
+ const headerCells = this.mergedColumns.map((column) => `<th>${this.escapeHtml(column.label)}</th>`).join('');
201
+ const bodyRows = rows
202
+ .map((row) => {
203
+ const cells = this.mergedColumns
204
+ .map((column) => `<td>${this.escapeHtml(this.getCellTextValue(row, column))}</td>`)
205
+ .join('');
206
+ return `<tr>${cells}</tr>`;
207
+ })
208
+ .join('');
209
+ const html = `<html><head><meta charset="UTF-8"></head><body><table border="1"><thead><tr>${headerCells}</tr></thead><tbody>${bodyRows}</tbody></table></body></html>`;
210
+ this.downloadFile(html, `${this.getExportFileName()}.xls`, 'application/vnd.ms-excel;charset=utf-8;');
211
+ }
212
+ getRenderedCell(row, column) {
213
+ if (typeof column.render === 'function') {
214
+ return column.render(row);
215
+ }
216
+ const key = String(column.key);
217
+ const value = row[key];
218
+ return this.escapeHtml(value == null ? '' : String(value));
219
+ }
220
+ toColumnWidth(width) {
221
+ if (typeof width === 'number') {
222
+ return `${width}px`;
223
+ }
224
+ if (typeof width === 'string' && width.trim()) {
225
+ return width.trim();
226
+ }
227
+ return 'auto';
228
+ }
229
+ getColumnCount() {
230
+ let count = this.mergedColumns.length;
231
+ if (this.isFeatureOn('checkboxSelection')) {
232
+ count += 1;
233
+ }
234
+ if (this.isFeatureOn('actions')) {
235
+ count += 1;
236
+ }
237
+ return count;
238
+ }
239
+ getStartEntry() {
240
+ if (!this.filteredRows.length) {
241
+ return 0;
242
+ }
243
+ if (!this.isFeatureOn('pagination')) {
244
+ return 1;
245
+ }
246
+ return (this.currentPage - 1) * this.rowsPerPage + 1;
247
+ }
248
+ getEndEntry() {
249
+ if (!this.filteredRows.length) {
250
+ return 0;
251
+ }
252
+ if (!this.isFeatureOn('pagination')) {
253
+ return this.filteredRows.length;
254
+ }
255
+ return Math.min(this.currentPage * this.rowsPerPage, this.filteredRows.length);
256
+ }
257
+ applyConfig() {
258
+ const features = {
259
+ ...DEFAULT_CONFIG.features,
260
+ ...(this.config.features ?? {})
261
+ };
262
+ const exportOptions = {
263
+ ...DEFAULT_CONFIG.exportOptions,
264
+ ...(this.config.exportOptions ?? {})
265
+ };
266
+ const rowsPerPageOptions = (this.config.rowsPerPageOptions ?? DEFAULT_CONFIG.rowsPerPageOptions).filter((item) => Number.isFinite(item) && item > 0);
267
+ this.mergedConfig = {
268
+ title: this.config.title ?? DEFAULT_CONFIG.title,
269
+ features,
270
+ rowsPerPageOptions: rowsPerPageOptions.length ? rowsPerPageOptions : [...DEFAULT_CONFIG.rowsPerPageOptions],
271
+ defaultRowsPerPage: this.config.defaultRowsPerPage ?? DEFAULT_CONFIG.defaultRowsPerPage,
272
+ exportOptions,
273
+ actions: [...(this.config.actions ?? [])],
274
+ columns: []
275
+ };
276
+ this.mergedColumns = [...(this.columns.length ? this.columns : (this.config.columns ?? []))];
277
+ this.rowsPerPage = this.getInitialRowsPerPage();
278
+ }
279
+ normalizeRows() {
280
+ this.allRows = (Array.isArray(this.data) ? this.data : []).map((row, index) => {
281
+ const rowId = row['id'];
282
+ const id = rowId == null ? `row-${index + 1}` : String(rowId);
283
+ return { id, data: row };
284
+ });
285
+ const validIds = new Set(this.allRows.map((row) => row.id));
286
+ for (const selectedId of Array.from(this.selectedRows)) {
287
+ if (!validIds.has(selectedId)) {
288
+ this.selectedRows.delete(selectedId);
289
+ }
290
+ }
291
+ }
292
+ applyFiltersSortAndPagination() {
293
+ if (!this.isFeatureOn('searching') || !this.searchTerm) {
294
+ this.filteredRows = [...this.allRows];
295
+ }
296
+ else {
297
+ this.filteredRows = this.allRows.filter((row) => {
298
+ const searchSpace = this.mergedColumns
299
+ .map((column) => this.getCellTextValue(row.data, column).toLowerCase())
300
+ .join(' ');
301
+ return searchSpace.includes(this.searchTerm);
302
+ });
303
+ }
304
+ if (this.isFeatureOn('sorting') && this.sortState.key) {
305
+ const column = this.mergedColumns.find((item) => String(item.key) === this.sortState.key);
306
+ if (column?.sortable) {
307
+ this.filteredRows.sort((a, b) => {
308
+ const aVal = this.getCellTextValue(a.data, column);
309
+ const bVal = this.getCellTextValue(b.data, column);
310
+ const compare = this.compareValues(aVal, bVal);
311
+ return this.sortState.direction === 'asc' ? compare : -compare;
312
+ });
313
+ }
314
+ }
315
+ this.totalPages = this.isFeatureOn('pagination')
316
+ ? Math.max(1, Math.ceil(this.filteredRows.length / this.rowsPerPage))
317
+ : 1;
318
+ if (this.currentPage > this.totalPages) {
319
+ this.currentPage = this.totalPages;
320
+ }
321
+ this.updatePagedRowsOnly();
322
+ this.emitSelection();
323
+ }
324
+ updatePagedRowsOnly() {
325
+ if (!this.isFeatureOn('pagination')) {
326
+ this.pagedRows = [...this.filteredRows];
327
+ return;
328
+ }
329
+ const start = (this.currentPage - 1) * this.rowsPerPage;
330
+ this.pagedRows = this.filteredRows.slice(start, start + this.rowsPerPage);
331
+ }
332
+ getCellTextValue(row, column) {
333
+ const key = String(column.key);
334
+ if (row[key] != null) {
335
+ return String(row[key]);
336
+ }
337
+ const rendered = this.getRenderedCell(row, column);
338
+ return this.stripHtml(rendered);
339
+ }
340
+ compareValues(a, b) {
341
+ const aText = a.trim();
342
+ const bText = b.trim();
343
+ const aNum = this.parseStrictNumber(aText);
344
+ const bNum = this.parseStrictNumber(bText);
345
+ if (aNum !== null && bNum !== null) {
346
+ return aNum - bNum;
347
+ }
348
+ const aDate = this.parseDateValue(aText);
349
+ const bDate = this.parseDateValue(bText);
350
+ if (aDate !== null && bDate !== null) {
351
+ return aDate - bDate;
352
+ }
353
+ return aText.localeCompare(bText, undefined, { numeric: true, sensitivity: 'base' });
354
+ }
355
+ parseStrictNumber(value) {
356
+ const normalized = value.replace(/[$,\s]/g, '');
357
+ if (!/^[-+]?\d*\.?\d+$/.test(normalized)) {
358
+ return null;
359
+ }
360
+ const numberValue = Number(normalized);
361
+ return Number.isNaN(numberValue) ? null : numberValue;
362
+ }
363
+ parseDateValue(value) {
364
+ const usDate = value.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
365
+ if (usDate) {
366
+ const month = Number(usDate[1]);
367
+ const day = Number(usDate[2]);
368
+ const year = Number(usDate[3]);
369
+ const utc = Date.UTC(year, month - 1, day);
370
+ return Number.isNaN(utc) ? null : utc;
371
+ }
372
+ const isoDate = value.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
373
+ if (isoDate) {
374
+ const year = Number(isoDate[1]);
375
+ const month = Number(isoDate[2]);
376
+ const day = Number(isoDate[3]);
377
+ const utc = Date.UTC(year, month - 1, day);
378
+ return Number.isNaN(utc) ? null : utc;
379
+ }
380
+ return null;
381
+ }
382
+ getRowsForExport() {
383
+ const selected = this.getSelectedRows();
384
+ if (selected.length) {
385
+ return selected;
386
+ }
387
+ return this.filteredRows.map((item) => item.data);
388
+ }
389
+ getExportFileName() {
390
+ return this.mergedConfig.exportOptions?.fileName || 'datatable-export';
391
+ }
392
+ downloadFile(content, fileName, mimeType) {
393
+ if (typeof document === 'undefined') {
394
+ return;
395
+ }
396
+ const blob = new Blob([content], { type: mimeType });
397
+ const url = URL.createObjectURL(blob);
398
+ const anchor = document.createElement('a');
399
+ anchor.href = url;
400
+ anchor.download = fileName;
401
+ document.body.appendChild(anchor);
402
+ anchor.click();
403
+ document.body.removeChild(anchor);
404
+ URL.revokeObjectURL(url);
405
+ }
406
+ escapeCsv(value) {
407
+ if (/[",\n]/.test(value)) {
408
+ return `"${value.replace(/"/g, '""')}"`;
409
+ }
410
+ return value;
411
+ }
412
+ stripHtml(input) {
413
+ if (typeof document === 'undefined') {
414
+ return input;
415
+ }
416
+ const temp = document.createElement('div');
417
+ temp.innerHTML = input;
418
+ return temp.textContent || temp.innerText || '';
419
+ }
420
+ escapeHtml(value) {
421
+ return String(value)
422
+ .replace(/&/g, '&amp;')
423
+ .replace(/</g, '&lt;')
424
+ .replace(/>/g, '&gt;')
425
+ .replace(/"/g, '&quot;')
426
+ .replace(/'/g, '&#039;');
427
+ }
428
+ getInitialRowsPerPage() {
429
+ const options = this.mergedConfig.rowsPerPageOptions ?? DEFAULT_CONFIG.rowsPerPageOptions;
430
+ const preferred = Number(this.mergedConfig.defaultRowsPerPage ?? DEFAULT_CONFIG.defaultRowsPerPage);
431
+ return options.includes(preferred) ? preferred : options[0] ?? DEFAULT_CONFIG.defaultRowsPerPage;
432
+ }
433
+ emitSelection() {
434
+ this.selectionChange.emit(this.getSelectedRows());
435
+ }
436
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: DynamicDatatableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
437
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: DynamicDatatableComponent, isStandalone: true, selector: "lib-dynamic-datatable", inputs: { config: "config", columns: "columns", data: "data" }, outputs: { actionSelected: "actionSelected", selectionChange: "selectionChange", sortChange: "sortChange", pageChange: "pageChange", exportTriggered: "exportTriggered" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"ndt-card\" (click)=\"closeActionMenu()\">\r\n <div class=\"ndt-header\">\r\n <h3 class=\"ndt-title\">{{ mergedConfig.title }}</h3>\r\n </div>\r\n\r\n <div class=\"ndt-toolbar\">\r\n <div class=\"ndt-left-tools\">\r\n <ng-container *ngIf=\"isFeatureOn('export')\">\r\n <button class=\"ndt-btn\" type=\"button\" (click)=\"exportCSV()\" *ngIf=\"isExportFormatEnabled('csv')\">\r\n Export CSV\r\n </button>\r\n <button class=\"ndt-btn\" type=\"button\" (click)=\"exportExcel()\" *ngIf=\"isExportFormatEnabled('excel')\">\r\n Export Excel\r\n </button>\r\n </ng-container>\r\n\r\n <label class=\"ndt-inline-label\" *ngIf=\"isFeatureOn('rowsPerPage') && isFeatureOn('pagination')\">\r\n Rows per page:\r\n <select [value]=\"rowsPerPage\" (change)=\"onRowsPerPageChange($event)\">\r\n <option *ngFor=\"let option of mergedConfig.rowsPerPageOptions || []\" [value]=\"option\">{{ option }}</option>\r\n </select>\r\n </label>\r\n </div>\r\n\r\n <div class=\"ndt-right-tools\" *ngIf=\"isFeatureOn('searching')\">\r\n <input\r\n type=\"search\"\r\n placeholder=\"Search records\"\r\n [value]=\"searchTerm\"\r\n (input)=\"onSearchInput($event)\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"ndt-table-wrap\">\r\n <table class=\"ndt-table\">\r\n <colgroup>\r\n <col *ngIf=\"isFeatureOn('checkboxSelection')\" style=\"width: 44px\" />\r\n <col *ngFor=\"let column of mergedColumns\" [style.width]=\"toColumnWidth(column.width)\" />\r\n <col *ngIf=\"isFeatureOn('actions')\" style=\"width: 78px\" />\r\n </colgroup>\r\n\r\n <thead>\r\n <tr>\r\n <th *ngIf=\"isFeatureOn('checkboxSelection')\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isCurrentPageFullySelected()\"\r\n (change)=\"toggleSelectAllCurrentPage($event)\"\r\n />\r\n </th>\r\n <th\r\n *ngFor=\"let column of mergedColumns\"\r\n [class.ndt-sortable]=\"isFeatureOn('sorting') && !!column.sortable\"\r\n (click)=\"toggleSort(column)\"\r\n >\r\n <span class=\"ndt-th-content\">\r\n <span class=\"ndt-th-label\">{{ column.label }}</span>\r\n <span class=\"ndt-sort-indicator\" *ngIf=\"isFeatureOn('sorting') && column.sortable\">\r\n {{ getSortIndicator(column.key.toString()) }}\r\n </span>\r\n </span>\r\n </th>\r\n <th *ngIf=\"isFeatureOn('actions')\">Actions</th>\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n <tr *ngIf=\"!pagedRows.length\">\r\n <td [attr.colspan]=\"getColumnCount()\" class=\"ndt-empty\">No records found.</td>\r\n </tr>\r\n\r\n <tr *ngFor=\"let row of pagedRows; let rowIndex = index\" [class.ndt-selected]=\"isRowSelected(row.id)\">\r\n <td *ngIf=\"isFeatureOn('checkboxSelection')\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isRowSelected(row.id)\"\r\n (change)=\"toggleRowSelection(row, $event)\"\r\n />\r\n </td>\r\n\r\n <td *ngFor=\"let column of mergedColumns\" [innerHTML]=\"getRenderedCell(row.data, column)\"></td>\r\n\r\n <td *ngIf=\"isFeatureOn('actions')\" class=\"ndt-actions-cell\">\r\n <div class=\"ndt-dropdown\" (click)=\"$event.stopPropagation()\">\r\n <button\r\n type=\"button\"\r\n class=\"ndt-actions-btn\"\r\n (click)=\"toggleActionMenu(row.id, $event)\"\r\n [attr.aria-expanded]=\"isActionMenuOpen(row.id)\"\r\n >\r\n <span class=\"ndt-dots\">...</span>\r\n </button>\r\n\r\n <ul class=\"ndt-dropdown-menu\" *ngIf=\"isActionMenuOpen(row.id)\">\r\n <li *ngFor=\"let action of mergedConfig.actions || []; let i = index\">\r\n <button\r\n type=\"button\"\r\n class=\"ndt-dropdown-item\"\r\n [class.ndt-danger]=\"!!action.danger\"\r\n (click)=\"onActionItemClick(i, row)\"\r\n >\r\n <i *ngIf=\"action.icon\" [class]=\"action.icon\"></i>\r\n <span>{{ action.label }}</span>\r\n </button>\r\n </li>\r\n </ul>\r\n </div>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <div class=\"ndt-footer\">\r\n <div>\r\n {{ selectedRows.size }} selected\r\n </div>\r\n\r\n <div>\r\n Showing {{ getStartEntry() }} to {{ getEndEntry() }} of {{ filteredRows.length }} entries\r\n </div>\r\n\r\n <div class=\"ndt-pagination\" *ngIf=\"isFeatureOn('pagination') && totalPages > 1\">\r\n <button type=\"button\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage <= 1\">Prev</button>\r\n <button\r\n type=\"button\"\r\n *ngFor=\"let page of getPaginationPages()\"\r\n [class.active]=\"page === currentPage\"\r\n (click)=\"goToPage(page)\"\r\n >\r\n {{ page }}\r\n </button>\r\n <button type=\"button\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage >= totalPages\">Next</button>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;--ndt-primary: #4f46e5;--ndt-primary-soft: #eef2ff;--ndt-border: #dbe4ff;--ndt-text: #1f2a44;--ndt-muted: #6d7895}.ndt-card{border:1px solid var(--ndt-border);border-radius:14px;background:#fff;box-shadow:0 10px 30px #2d489114;overflow:hidden;color:var(--ndt-text);font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.ndt-header,.ndt-toolbar,.ndt-footer{padding:.9rem 1rem}.ndt-header{border-bottom:1px solid #edf1ff}.ndt-title{margin:0;font-size:1.1rem}.ndt-toolbar{display:flex;justify-content:space-between;gap:.75rem;flex-wrap:wrap}.ndt-left-tools,.ndt-right-tools{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.ndt-btn,.ndt-pagination button,.ndt-actions-cell select,.ndt-toolbar select,.ndt-toolbar input{border:1px solid #cfd9fb;background:#fff;color:#33446e;border-radius:8px;padding:.35rem .6rem;font-size:.85rem}.ndt-btn:hover,.ndt-pagination button:hover,.ndt-pagination button.active{border-color:var(--ndt-primary);background:var(--ndt-primary-soft);color:var(--ndt-primary)}.ndt-inline-label{display:inline-flex;align-items:center;gap:.4rem;color:var(--ndt-muted);font-size:.85rem}.ndt-table-wrap{overflow-x:auto;border-top:1px solid #edf1ff;border-bottom:1px solid #edf1ff}.ndt-table{width:100%;min-width:860px;border-collapse:collapse;table-layout:fixed}.ndt-table th,.ndt-table td{padding:.72rem;text-align:left;border-bottom:1px solid #edf1ff}.ndt-table th{color:#4a577f;font-size:.8rem;text-transform:uppercase;letter-spacing:.03em;background:#f8faff}.ndt-th-content{display:flex;align-items:center;justify-content:space-between;gap:.4rem}.ndt-th-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ndt-sortable{cursor:pointer}.ndt-sort-indicator{font-size:.75rem;color:#8a98c2}.ndt-table tbody tr:hover{background:#f5f8ff}.ndt-selected{background:#ebf1ff}.ndt-actions-cell{text-align:center;position:relative}.ndt-dropdown{position:relative;display:inline-block}.ndt-actions-btn{border:1px solid #cfd9fb;background:#fff;color:#33446e;border-radius:8px;width:34px;height:30px;padding:0;cursor:pointer}.ndt-actions-btn:hover{border-color:var(--ndt-primary);color:var(--ndt-primary);background:var(--ndt-primary-soft)}.ndt-dots{font-size:1rem;line-height:1}.ndt-dropdown-menu{position:absolute;right:0;top:calc(100% + .35rem);list-style:none;margin:0;padding:.35rem 0;min-width:150px;border:1px solid #dbe4ff;border-radius:10px;background:#fff;box-shadow:0 12px 24px #2d489124;z-index:20}.ndt-dropdown-item{width:100%;border:0;background:#fff;color:#33446e;display:flex;align-items:center;gap:.45rem;text-align:left;padding:.42rem .72rem;font-size:.84rem;cursor:pointer}.ndt-dropdown-item:hover{background:#f5f8ff;color:var(--ndt-primary)}.ndt-dropdown-item.ndt-danger{color:#dc3545}.ndt-dropdown-item.ndt-danger:hover{background:#fff1f3}.ndt-empty{text-align:center;color:var(--ndt-muted)}.ndt-footer{display:flex;justify-content:space-between;gap:.75rem;flex-wrap:wrap;color:#55658f;font-size:.86rem}.ndt-pagination{display:flex;align-items:center;gap:.35rem}@media(max-width:900px){.ndt-toolbar,.ndt-footer{flex-direction:column;align-items:flex-start}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
438
+ }
439
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: DynamicDatatableComponent, decorators: [{
440
+ type: Component,
441
+ args: [{ selector: 'lib-dynamic-datatable', standalone: true, imports: [CommonModule], template: "<div class=\"ndt-card\" (click)=\"closeActionMenu()\">\r\n <div class=\"ndt-header\">\r\n <h3 class=\"ndt-title\">{{ mergedConfig.title }}</h3>\r\n </div>\r\n\r\n <div class=\"ndt-toolbar\">\r\n <div class=\"ndt-left-tools\">\r\n <ng-container *ngIf=\"isFeatureOn('export')\">\r\n <button class=\"ndt-btn\" type=\"button\" (click)=\"exportCSV()\" *ngIf=\"isExportFormatEnabled('csv')\">\r\n Export CSV\r\n </button>\r\n <button class=\"ndt-btn\" type=\"button\" (click)=\"exportExcel()\" *ngIf=\"isExportFormatEnabled('excel')\">\r\n Export Excel\r\n </button>\r\n </ng-container>\r\n\r\n <label class=\"ndt-inline-label\" *ngIf=\"isFeatureOn('rowsPerPage') && isFeatureOn('pagination')\">\r\n Rows per page:\r\n <select [value]=\"rowsPerPage\" (change)=\"onRowsPerPageChange($event)\">\r\n <option *ngFor=\"let option of mergedConfig.rowsPerPageOptions || []\" [value]=\"option\">{{ option }}</option>\r\n </select>\r\n </label>\r\n </div>\r\n\r\n <div class=\"ndt-right-tools\" *ngIf=\"isFeatureOn('searching')\">\r\n <input\r\n type=\"search\"\r\n placeholder=\"Search records\"\r\n [value]=\"searchTerm\"\r\n (input)=\"onSearchInput($event)\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"ndt-table-wrap\">\r\n <table class=\"ndt-table\">\r\n <colgroup>\r\n <col *ngIf=\"isFeatureOn('checkboxSelection')\" style=\"width: 44px\" />\r\n <col *ngFor=\"let column of mergedColumns\" [style.width]=\"toColumnWidth(column.width)\" />\r\n <col *ngIf=\"isFeatureOn('actions')\" style=\"width: 78px\" />\r\n </colgroup>\r\n\r\n <thead>\r\n <tr>\r\n <th *ngIf=\"isFeatureOn('checkboxSelection')\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isCurrentPageFullySelected()\"\r\n (change)=\"toggleSelectAllCurrentPage($event)\"\r\n />\r\n </th>\r\n <th\r\n *ngFor=\"let column of mergedColumns\"\r\n [class.ndt-sortable]=\"isFeatureOn('sorting') && !!column.sortable\"\r\n (click)=\"toggleSort(column)\"\r\n >\r\n <span class=\"ndt-th-content\">\r\n <span class=\"ndt-th-label\">{{ column.label }}</span>\r\n <span class=\"ndt-sort-indicator\" *ngIf=\"isFeatureOn('sorting') && column.sortable\">\r\n {{ getSortIndicator(column.key.toString()) }}\r\n </span>\r\n </span>\r\n </th>\r\n <th *ngIf=\"isFeatureOn('actions')\">Actions</th>\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n <tr *ngIf=\"!pagedRows.length\">\r\n <td [attr.colspan]=\"getColumnCount()\" class=\"ndt-empty\">No records found.</td>\r\n </tr>\r\n\r\n <tr *ngFor=\"let row of pagedRows; let rowIndex = index\" [class.ndt-selected]=\"isRowSelected(row.id)\">\r\n <td *ngIf=\"isFeatureOn('checkboxSelection')\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isRowSelected(row.id)\"\r\n (change)=\"toggleRowSelection(row, $event)\"\r\n />\r\n </td>\r\n\r\n <td *ngFor=\"let column of mergedColumns\" [innerHTML]=\"getRenderedCell(row.data, column)\"></td>\r\n\r\n <td *ngIf=\"isFeatureOn('actions')\" class=\"ndt-actions-cell\">\r\n <div class=\"ndt-dropdown\" (click)=\"$event.stopPropagation()\">\r\n <button\r\n type=\"button\"\r\n class=\"ndt-actions-btn\"\r\n (click)=\"toggleActionMenu(row.id, $event)\"\r\n [attr.aria-expanded]=\"isActionMenuOpen(row.id)\"\r\n >\r\n <span class=\"ndt-dots\">...</span>\r\n </button>\r\n\r\n <ul class=\"ndt-dropdown-menu\" *ngIf=\"isActionMenuOpen(row.id)\">\r\n <li *ngFor=\"let action of mergedConfig.actions || []; let i = index\">\r\n <button\r\n type=\"button\"\r\n class=\"ndt-dropdown-item\"\r\n [class.ndt-danger]=\"!!action.danger\"\r\n (click)=\"onActionItemClick(i, row)\"\r\n >\r\n <i *ngIf=\"action.icon\" [class]=\"action.icon\"></i>\r\n <span>{{ action.label }}</span>\r\n </button>\r\n </li>\r\n </ul>\r\n </div>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <div class=\"ndt-footer\">\r\n <div>\r\n {{ selectedRows.size }} selected\r\n </div>\r\n\r\n <div>\r\n Showing {{ getStartEntry() }} to {{ getEndEntry() }} of {{ filteredRows.length }} entries\r\n </div>\r\n\r\n <div class=\"ndt-pagination\" *ngIf=\"isFeatureOn('pagination') && totalPages > 1\">\r\n <button type=\"button\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage <= 1\">Prev</button>\r\n <button\r\n type=\"button\"\r\n *ngFor=\"let page of getPaginationPages()\"\r\n [class.active]=\"page === currentPage\"\r\n (click)=\"goToPage(page)\"\r\n >\r\n {{ page }}\r\n </button>\r\n <button type=\"button\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage >= totalPages\">Next</button>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;--ndt-primary: #4f46e5;--ndt-primary-soft: #eef2ff;--ndt-border: #dbe4ff;--ndt-text: #1f2a44;--ndt-muted: #6d7895}.ndt-card{border:1px solid var(--ndt-border);border-radius:14px;background:#fff;box-shadow:0 10px 30px #2d489114;overflow:hidden;color:var(--ndt-text);font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.ndt-header,.ndt-toolbar,.ndt-footer{padding:.9rem 1rem}.ndt-header{border-bottom:1px solid #edf1ff}.ndt-title{margin:0;font-size:1.1rem}.ndt-toolbar{display:flex;justify-content:space-between;gap:.75rem;flex-wrap:wrap}.ndt-left-tools,.ndt-right-tools{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.ndt-btn,.ndt-pagination button,.ndt-actions-cell select,.ndt-toolbar select,.ndt-toolbar input{border:1px solid #cfd9fb;background:#fff;color:#33446e;border-radius:8px;padding:.35rem .6rem;font-size:.85rem}.ndt-btn:hover,.ndt-pagination button:hover,.ndt-pagination button.active{border-color:var(--ndt-primary);background:var(--ndt-primary-soft);color:var(--ndt-primary)}.ndt-inline-label{display:inline-flex;align-items:center;gap:.4rem;color:var(--ndt-muted);font-size:.85rem}.ndt-table-wrap{overflow-x:auto;border-top:1px solid #edf1ff;border-bottom:1px solid #edf1ff}.ndt-table{width:100%;min-width:860px;border-collapse:collapse;table-layout:fixed}.ndt-table th,.ndt-table td{padding:.72rem;text-align:left;border-bottom:1px solid #edf1ff}.ndt-table th{color:#4a577f;font-size:.8rem;text-transform:uppercase;letter-spacing:.03em;background:#f8faff}.ndt-th-content{display:flex;align-items:center;justify-content:space-between;gap:.4rem}.ndt-th-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ndt-sortable{cursor:pointer}.ndt-sort-indicator{font-size:.75rem;color:#8a98c2}.ndt-table tbody tr:hover{background:#f5f8ff}.ndt-selected{background:#ebf1ff}.ndt-actions-cell{text-align:center;position:relative}.ndt-dropdown{position:relative;display:inline-block}.ndt-actions-btn{border:1px solid #cfd9fb;background:#fff;color:#33446e;border-radius:8px;width:34px;height:30px;padding:0;cursor:pointer}.ndt-actions-btn:hover{border-color:var(--ndt-primary);color:var(--ndt-primary);background:var(--ndt-primary-soft)}.ndt-dots{font-size:1rem;line-height:1}.ndt-dropdown-menu{position:absolute;right:0;top:calc(100% + .35rem);list-style:none;margin:0;padding:.35rem 0;min-width:150px;border:1px solid #dbe4ff;border-radius:10px;background:#fff;box-shadow:0 12px 24px #2d489124;z-index:20}.ndt-dropdown-item{width:100%;border:0;background:#fff;color:#33446e;display:flex;align-items:center;gap:.45rem;text-align:left;padding:.42rem .72rem;font-size:.84rem;cursor:pointer}.ndt-dropdown-item:hover{background:#f5f8ff;color:var(--ndt-primary)}.ndt-dropdown-item.ndt-danger{color:#dc3545}.ndt-dropdown-item.ndt-danger:hover{background:#fff1f3}.ndt-empty{text-align:center;color:var(--ndt-muted)}.ndt-footer{display:flex;justify-content:space-between;gap:.75rem;flex-wrap:wrap;color:#55658f;font-size:.86rem}.ndt-pagination{display:flex;align-items:center;gap:.35rem}@media(max-width:900px){.ndt-toolbar,.ndt-footer{flex-direction:column;align-items:flex-start}}\n"] }]
442
+ }], propDecorators: { config: [{
443
+ type: Input
444
+ }], columns: [{
445
+ type: Input
446
+ }], data: [{
447
+ type: Input
448
+ }], actionSelected: [{
449
+ type: Output
450
+ }], selectionChange: [{
451
+ type: Output
452
+ }], sortChange: [{
453
+ type: Output
454
+ }], pageChange: [{
455
+ type: Output
456
+ }], exportTriggered: [{
457
+ type: Output
458
+ }] } });
459
+
460
+ /*
461
+ * Public API Surface of dynamic-datatable
462
+ */
463
+
464
+ /**
465
+ * Generated bundle index. Do not edit.
466
+ */
467
+
468
+ export { DynamicDatatableComponent };
469
+ //# sourceMappingURL=ng-dynamic-datatable.mjs.map