shirkasoft-ui-components 1.3.8 → 1.3.9
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 +37 -12
- package/fesm2022/shirkasoft-ui-components-button.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-button.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-confirm-dialog.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-confirm-dialog.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-date-picker.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-date-picker.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-loading.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-loading.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-modal.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-modal.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-notification.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-notification.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-price-input.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-price-input.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-select.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-select.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-table.mjs +198 -12
- package/fesm2022/shirkasoft-ui-components-table.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-tabs.mjs +7 -12
- package/fesm2022/shirkasoft-ui-components-tabs.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-tag.mjs +3 -3
- package/fesm2022/shirkasoft-ui-components-tag.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-text-field.mjs +2 -2
- package/fesm2022/shirkasoft-ui-components-text-field.mjs.map +1 -1
- package/fesm2022/shirkasoft-ui-components-toggle.mjs +61 -24
- package/fesm2022/shirkasoft-ui-components-toggle.mjs.map +1 -1
- package/package.json +1 -1
- package/src/theme.css +80 -0
- package/types/shirkasoft-ui-components-table.d.ts +86 -9
- package/types/shirkasoft-ui-components-toggle.d.ts +7 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ChangeDetectorRef, DestroyRef, input, output, signal, linkedSignal, computed, effect, untracked, HostListener, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { inject, ChangeDetectorRef, DestroyRef, input, model, output, signal, linkedSignal, computed, afterEveryRender, effect, untracked, HostListener, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
@@ -18,6 +18,8 @@ import { getIcon } from 'shirkasoft-ui-components/utils';
|
|
|
18
18
|
* Authors: Roberto Valladares Martin (Ansem13), Julio César García García (Sanjuli14).
|
|
19
19
|
* Belongs to Shirkasoft.
|
|
20
20
|
*/
|
|
21
|
+
/** Counter used to generate unique element ids per table instance. */
|
|
22
|
+
let nextTableId = 0;
|
|
21
23
|
/**
|
|
22
24
|
* Data table with sorting, column filters, global search, pagination,
|
|
23
25
|
* row/header actions, tag cells and optional server-side mode.
|
|
@@ -28,6 +30,8 @@ class TableComponent {
|
|
|
28
30
|
return col.width || this.DEFAULT_COLUMN_WIDTH;
|
|
29
31
|
}
|
|
30
32
|
constructor() {
|
|
33
|
+
/** Unique id prefix so several tables on the same page don't share element ids. */
|
|
34
|
+
this.uid = `shk-table-${nextTableId++}`;
|
|
31
35
|
this.fb = inject(FormBuilder);
|
|
32
36
|
this.cdr = inject(ChangeDetectorRef);
|
|
33
37
|
this.transloco = inject(TranslocoService);
|
|
@@ -89,6 +93,19 @@ class TableComponent {
|
|
|
89
93
|
this.filters = input([], ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
|
|
90
94
|
/** Currently active filter chip value. */
|
|
91
95
|
this.activeFilter = input('', ...(ngDevMode ? [{ debugName: "activeFilter" }] : /* istanbul ignore next */ []));
|
|
96
|
+
/** Whether to render the leading checkbox column for multi-row selection. */
|
|
97
|
+
this.selectable = input(false, ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
|
|
98
|
+
/**
|
|
99
|
+
* Field used to identify rows in the selection (keeps it stable across
|
|
100
|
+
* pages and data reloads). Falls back to the row reference when missing.
|
|
101
|
+
*/
|
|
102
|
+
this.dataKey = input('id', ...(ngDevMode ? [{ debugName: "dataKey" }] : /* istanbul ignore next */ []));
|
|
103
|
+
/** Optional predicate to disable the checkbox of specific rows. */
|
|
104
|
+
this.isRowSelectable = input(undefined, ...(ngDevMode ? [{ debugName: "isRowSelectable" }] : /* istanbul ignore next */ []));
|
|
105
|
+
/** Actions shown in the header while at least one row is selected. */
|
|
106
|
+
this.bulkActions = input([], ...(ngDevMode ? [{ debugName: "bulkActions" }] : /* istanbul ignore next */ []));
|
|
107
|
+
/** Selected rows. Supports two-way binding via `[(selection)]`. */
|
|
108
|
+
this.selection = model([], ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
92
109
|
/** Emits when the user clicks the refresh button. */
|
|
93
110
|
this.refresh = output();
|
|
94
111
|
/** Emits page changes (server-side mode) with first/rows/page info. */
|
|
@@ -167,6 +184,31 @@ class TableComponent {
|
|
|
167
184
|
}
|
|
168
185
|
return [...columnsArray];
|
|
169
186
|
}, ...(ngDevMode ? [{ debugName: "columnsWithActions" }] : /* istanbul ignore next */ []));
|
|
187
|
+
/** Keys of the selected rows, for O(1) lookups in the template. */
|
|
188
|
+
this.selectedKeys = computed(() => new Set(this.selection().map((row) => this.getSelectionKey(row))), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
189
|
+
/** Whether non-empty data has been received (see the selection pruning effect). */
|
|
190
|
+
this.hasReceivedData = false;
|
|
191
|
+
/** Selectable rows of the current page. */
|
|
192
|
+
this.selectablePageRows = computed(() => this.effectiveDisplayedData().filter((row) => this.canSelectRow(row)), ...(ngDevMode ? [{ debugName: "selectablePageRows" }] : /* istanbul ignore next */ []));
|
|
193
|
+
/** Whether every selectable row of the current page is selected. */
|
|
194
|
+
this.allPageSelected = computed(() => {
|
|
195
|
+
const rows = this.selectablePageRows();
|
|
196
|
+
const keys = this.selectedKeys();
|
|
197
|
+
return rows.length > 0 && rows.every((row) => keys.has(this.getSelectionKey(row)));
|
|
198
|
+
}, ...(ngDevMode ? [{ debugName: "allPageSelected" }] : /* istanbul ignore next */ []));
|
|
199
|
+
/** Whether only part of the current page is selected (header checkbox indeterminate). */
|
|
200
|
+
this.somePageSelected = computed(() => {
|
|
201
|
+
const keys = this.selectedKeys();
|
|
202
|
+
return (!this.allPageSelected() &&
|
|
203
|
+
this.selectablePageRows().some((row) => keys.has(this.getSelectionKey(row))));
|
|
204
|
+
}, ...(ngDevMode ? [{ debugName: "somePageSelected" }] : /* istanbul ignore next */ []));
|
|
205
|
+
/** Visible bulk actions for the current selection. */
|
|
206
|
+
this.visibleBulkActions = computed(() => {
|
|
207
|
+
const selected = this.selection();
|
|
208
|
+
return this.bulkActions().filter((a) => (a.isVisible ? a.isVisible(selected) : true));
|
|
209
|
+
}, ...(ngDevMode ? [{ debugName: "visibleBulkActions" }] : /* istanbul ignore next */ []));
|
|
210
|
+
/** Number of rendered columns, including the selection column. */
|
|
211
|
+
this.totalColspan = computed(() => this.columnsWithActions().length + (this.selectable() ? 1 : 0), ...(ngDevMode ? [{ debugName: "totalColspan" }] : /* istanbul ignore next */ []));
|
|
170
212
|
/** Total records: server-provided in server mode, locally computed otherwise. */
|
|
171
213
|
this.effectiveTotalRecords = computed(() => {
|
|
172
214
|
return this.serverSide() ? this.totalRecords() : this.localTotalRecords();
|
|
@@ -197,6 +239,13 @@ class TableComponent {
|
|
|
197
239
|
const to = Math.min(this.first() + this.rowsPerPageLocal(), total);
|
|
198
240
|
return this.transloco.translate('table.pageReport', { from, to, total });
|
|
199
241
|
}, ...(ngDevMode ? [{ debugName: "pageReport" }] : /* istanbul ignore next */ []));
|
|
242
|
+
/** Width (px) of the column filter popup, used to keep it inside the viewport. */
|
|
243
|
+
this.FILTER_POPUP_WIDTH = 200;
|
|
244
|
+
/** Viewport position of the open column filter popup. */
|
|
245
|
+
this.filterPopupPosition = signal({ top: 0, left: 0 }, ...(ngDevMode ? [{ debugName: "filterPopupPosition" }] : /* istanbul ignore next */ []));
|
|
246
|
+
/** Filter button the open popup is anchored to, and its scroll/resize listener cleanup. */
|
|
247
|
+
this.filterAnchor = null;
|
|
248
|
+
this.filterListenersCleanup = null;
|
|
200
249
|
/** Maximum number of row actions shown directly before collapsing into a dropdown. */
|
|
201
250
|
this.MAX_VISIBLE_ACTIONS = 4;
|
|
202
251
|
/** Tracks which row's "more actions" dropdown is currently open (keyed by row identity). */
|
|
@@ -204,6 +253,15 @@ class TableComponent {
|
|
|
204
253
|
/** Computes the fixed position of the dropdown so it's not clipped by overflow. */
|
|
205
254
|
this.dropdownPosition = signal({ top: 0, left: 0 }, ...(ngDevMode ? [{ debugName: "dropdownPosition" }] : /* istanbul ignore next */ []));
|
|
206
255
|
this.columnFiltersForm = this.fb.group({});
|
|
256
|
+
this.destroyRef.onDestroy(() => this.filterListenersCleanup?.());
|
|
257
|
+
// Close the filter popup if its header cell was removed (e.g. the column
|
|
258
|
+
// disappeared). Re-renders of the same columns keep the cell (tracked by
|
|
259
|
+
// field), so new `columns` references alone don't close it.
|
|
260
|
+
afterEveryRender(() => {
|
|
261
|
+
if (this.filterAnchor && !this.filterAnchor.isConnected) {
|
|
262
|
+
this.closeFilters();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
207
265
|
// Debounced column-filter changes (server-side mode only).
|
|
208
266
|
this.filterSubject
|
|
209
267
|
.pipe(debounceTime(300), takeUntilDestroyed(this.destroyRef))
|
|
@@ -229,6 +287,21 @@ class TableComponent {
|
|
|
229
287
|
}
|
|
230
288
|
});
|
|
231
289
|
});
|
|
290
|
+
// Drop selected rows that disappeared from the data (client mode). Skipped
|
|
291
|
+
// while loading and until the first non-empty data arrives, so a temporary
|
|
292
|
+
// `[]` (initial value, async load, reload) doesn't wipe the selection.
|
|
293
|
+
effect(() => {
|
|
294
|
+
const newData = this.data();
|
|
295
|
+
const loading = this.loading();
|
|
296
|
+
untracked(() => {
|
|
297
|
+
if (this.serverSide() || loading)
|
|
298
|
+
return;
|
|
299
|
+
if (newData.length > 0)
|
|
300
|
+
this.hasReceivedData = true;
|
|
301
|
+
if (this.hasReceivedData)
|
|
302
|
+
this.pruneSelection(newData);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
232
305
|
// Rebuild the column filter form whenever the columns change.
|
|
233
306
|
effect(() => {
|
|
234
307
|
const cols = this.columns();
|
|
@@ -366,11 +439,11 @@ class TableComponent {
|
|
|
366
439
|
}
|
|
367
440
|
else if (col.filterType === 'exact') {
|
|
368
441
|
const searchTerm = String(filterValue).toLowerCase();
|
|
369
|
-
filtered = filtered.filter((item) => String(item[col.field]).toLowerCase() === searchTerm);
|
|
442
|
+
filtered = filtered.filter((item) => String(item[col.field] ?? '').toLowerCase() === searchTerm);
|
|
370
443
|
}
|
|
371
444
|
else {
|
|
372
445
|
const searchTerm = String(filterValue).toLowerCase();
|
|
373
|
-
filtered = filtered.filter((item) => String(item[col.field]).toLowerCase().includes(searchTerm));
|
|
446
|
+
filtered = filtered.filter((item) => String(item[col.field] ?? '').toLowerCase().includes(searchTerm));
|
|
374
447
|
}
|
|
375
448
|
}
|
|
376
449
|
}
|
|
@@ -388,6 +461,71 @@ class TableComponent {
|
|
|
388
461
|
const displayed = filtered.slice(firstIndex, firstIndex + rows);
|
|
389
462
|
this.displayedData.set(displayed);
|
|
390
463
|
}
|
|
464
|
+
/** Identity used for a row inside the selection. */
|
|
465
|
+
getSelectionKey(row) {
|
|
466
|
+
const key = this.dataKey();
|
|
467
|
+
return key && row?.[key] !== undefined ? row[key] : row;
|
|
468
|
+
}
|
|
469
|
+
/** Whether the row's checkbox is enabled. */
|
|
470
|
+
canSelectRow(row) {
|
|
471
|
+
const predicate = this.isRowSelectable();
|
|
472
|
+
return predicate ? predicate(row) : true;
|
|
473
|
+
}
|
|
474
|
+
/** Whether the row is currently selected. */
|
|
475
|
+
isRowSelected(row) {
|
|
476
|
+
return this.selectedKeys().has(this.getSelectionKey(row));
|
|
477
|
+
}
|
|
478
|
+
/** Selects/deselects a single row. */
|
|
479
|
+
toggleRowSelection(row) {
|
|
480
|
+
if (!this.canSelectRow(row))
|
|
481
|
+
return;
|
|
482
|
+
const key = this.getSelectionKey(row);
|
|
483
|
+
if (this.isRowSelected(row)) {
|
|
484
|
+
this.selection.set(this.selection().filter((r) => this.getSelectionKey(r) !== key));
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
this.selection.set([...this.selection(), row]);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Header checkbox: selects every selectable row of the current page or,
|
|
492
|
+
* when all of them are already selected, removes them from the selection.
|
|
493
|
+
* Rows selected on other pages are kept.
|
|
494
|
+
*/
|
|
495
|
+
toggleSelectAllPage() {
|
|
496
|
+
const pageRows = this.selectablePageRows();
|
|
497
|
+
const pageKeys = new Set(pageRows.map((row) => this.getSelectionKey(row)));
|
|
498
|
+
const rest = this.selection().filter((r) => !pageKeys.has(this.getSelectionKey(r)));
|
|
499
|
+
this.selection.set(this.allPageSelected() ? rest : [...rest, ...pageRows]);
|
|
500
|
+
}
|
|
501
|
+
/** Clears the whole selection. */
|
|
502
|
+
clearSelection() {
|
|
503
|
+
this.selection.set([]);
|
|
504
|
+
}
|
|
505
|
+
/** Runs a bulk action with the current selection. */
|
|
506
|
+
runBulkAction(action) {
|
|
507
|
+
action.onClick([...this.selection()]);
|
|
508
|
+
}
|
|
509
|
+
/** Whether a bulk action is disabled for the current selection. */
|
|
510
|
+
isBulkActionDisabled(action) {
|
|
511
|
+
return action.isDisabled ? action.isDisabled(this.selection()) : false;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Drops selected rows that no longer exist in the data (client mode, e.g.
|
|
515
|
+
* after a bulk delete) and refreshes kept rows with their new references.
|
|
516
|
+
*/
|
|
517
|
+
pruneSelection(data) {
|
|
518
|
+
const current = this.selection();
|
|
519
|
+
if (current.length === 0)
|
|
520
|
+
return;
|
|
521
|
+
const byKey = new Map(data.map((row) => [this.getSelectionKey(row), row]));
|
|
522
|
+
const next = current
|
|
523
|
+
.map((row) => byKey.get(this.getSelectionKey(row)))
|
|
524
|
+
.filter((row) => row !== undefined);
|
|
525
|
+
if (next.length !== current.length || next.some((row, i) => row !== current[i])) {
|
|
526
|
+
this.selection.set(next);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
391
529
|
/** Emits the filter chip clicked in the template. */
|
|
392
530
|
onFilterClick(value) {
|
|
393
531
|
this.filterClick.emit(value);
|
|
@@ -447,15 +585,52 @@ class TableComponent {
|
|
|
447
585
|
const column = this.columns().find((col) => col.field === field);
|
|
448
586
|
const value = column?.filterType === 'select' ? null : '';
|
|
449
587
|
this.columnFiltersForm.get(field)?.setValue(value);
|
|
450
|
-
|
|
451
|
-
|
|
588
|
+
this.closeFilters();
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Toggles a column filter popup. Only one popup is open at a time. The
|
|
592
|
+
* popup uses `position: fixed` anchored to the filter button, so the
|
|
593
|
+
* header cell (`overflow: hidden`) and the scroll wrapper can't clip it.
|
|
594
|
+
*/
|
|
595
|
+
toggleFilter(field, event) {
|
|
596
|
+
const wasOpen = !!this.showFilterInput()[field];
|
|
597
|
+
this.closeFilters();
|
|
598
|
+
if (wasOpen)
|
|
599
|
+
return;
|
|
600
|
+
this.filterAnchor = event?.currentTarget ?? null;
|
|
601
|
+
this.updateFilterPopupPosition();
|
|
602
|
+
this.showFilterInput.set({ ...this.showFilterInput(), [field]: true });
|
|
603
|
+
const reposition = () => this.updateFilterPopupPosition();
|
|
604
|
+
window.addEventListener('scroll', reposition, true);
|
|
605
|
+
window.addEventListener('resize', reposition);
|
|
606
|
+
this.filterListenersCleanup = () => {
|
|
607
|
+
window.removeEventListener('scroll', reposition, true);
|
|
608
|
+
window.removeEventListener('resize', reposition);
|
|
609
|
+
};
|
|
610
|
+
setTimeout(() => document.getElementById(`${this.uid}-filter-${field}`)?.focus());
|
|
452
611
|
this.cdr.markForCheck();
|
|
453
612
|
}
|
|
454
|
-
/**
|
|
455
|
-
|
|
613
|
+
/** Closes every column filter popup (values are kept). */
|
|
614
|
+
closeFilters() {
|
|
615
|
+
this.filterListenersCleanup?.();
|
|
616
|
+
this.filterListenersCleanup = null;
|
|
617
|
+
this.filterAnchor = null;
|
|
456
618
|
const visibility = this.showFilterInput();
|
|
457
|
-
|
|
458
|
-
|
|
619
|
+
if (Object.values(visibility).some((v) => v)) {
|
|
620
|
+
const closed = {};
|
|
621
|
+
Object.keys(visibility).forEach((key) => (closed[key] = false));
|
|
622
|
+
this.showFilterInput.set(closed);
|
|
623
|
+
this.cdr.markForCheck();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/** Places the filter popup under its button, clamped to the viewport. */
|
|
627
|
+
updateFilterPopupPosition() {
|
|
628
|
+
if (!this.filterAnchor)
|
|
629
|
+
return;
|
|
630
|
+
const rect = this.filterAnchor.getBoundingClientRect();
|
|
631
|
+
const maxLeft = window.innerWidth - this.FILTER_POPUP_WIDTH - 8;
|
|
632
|
+
const left = Math.max(8, Math.min(rect.right - this.FILTER_POPUP_WIDTH, maxLeft));
|
|
633
|
+
this.filterPopupPosition.set({ top: rect.bottom + 6, left });
|
|
459
634
|
}
|
|
460
635
|
/**
|
|
461
636
|
* Whether a column filter is non-empty. Avoids treating the number 0 as
|
|
@@ -672,6 +847,9 @@ class TableComponent {
|
|
|
672
847
|
onHeaderKeydown(event, col) {
|
|
673
848
|
if (col.field === 'actions' || !col.sortable)
|
|
674
849
|
return;
|
|
850
|
+
// Ignore keys typed inside the filter popup (e.g. spaces in the input).
|
|
851
|
+
if (event.target !== event.currentTarget)
|
|
852
|
+
return;
|
|
675
853
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
676
854
|
event.preventDefault();
|
|
677
855
|
this.toggleSort(col.field);
|
|
@@ -742,9 +920,14 @@ class TableComponent {
|
|
|
742
920
|
if (hasOpen) {
|
|
743
921
|
this.actionsDropdownOpen.set({});
|
|
744
922
|
}
|
|
923
|
+
this.closeFilters();
|
|
924
|
+
}
|
|
925
|
+
/** Closes the column filter popup with Escape. */
|
|
926
|
+
onEscape() {
|
|
927
|
+
this.closeFilters();
|
|
745
928
|
}
|
|
746
929
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
747
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: TableComponent, isStandalone: true, selector: "shk-table", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPage: { classPropertyName: "rowsPerPage", publicName: "rowsPerPage", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showActionRow: { classPropertyName: "showActionRow", publicName: "showActionRow", isSignal: true, isRequired: false, transformFunction: null }, customTemplates: { classPropertyName: "customTemplates", publicName: "customTemplates", isSignal: true, isRequired: false, transformFunction: null }, headerActions: { classPropertyName: "headerActions", publicName: "headerActions", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, hasShadow: { classPropertyName: "hasShadow", publicName: "hasShadow", isSignal: true, isRequired: false, transformFunction: null }, defaultSortField: { classPropertyName: "defaultSortField", publicName: "defaultSortField", isSignal: true, isRequired: false, transformFunction: null }, defaultSortOrder: { classPropertyName: "defaultSortOrder", publicName: "defaultSortOrder", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, activeFilter: { classPropertyName: "activeFilter", publicName: "activeFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { refresh: "refresh", pageChange: "pageChange", filterChange: "filterChange", searchChange: "searchChange", filterClick: "filterClick" }, host: { listeners: { "document:click": "onDocumentClick()" } }, ngImport: i0, template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <label [for]=\"'shk-table-search'\" class=\"sr-only\">{{\n searchPlaceholder() || ('common.search_placeholder' | transloco)\n }}</label>\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg\n [lucideIcon]=\"getIcon('search')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <input\n id=\"shk-table-search\"\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n @if (filters().length > 0) {\n <div\n class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\"\n role=\"group\"\n >\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n [attr.aria-pressed]=\"f.value === activeFilter()\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"\n f.value === activeFilter()\n ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]'\n : ''\n \"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isHeaderActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n <div class=\"card p-0 overflow-hidden table-card\" [class.shadow-lg]=\"hasShadow()\">\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table>\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n scope=\"col\"\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n (keydown)=\"onHeaderKeydown($event, col)\"\n [tabindex]=\"col.field !== 'actions' && col.sortable ? 0 : -1\"\n [attr.role]=\"col.field !== 'actions' && col.sortable ? 'button' : null\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.field === 'actions' ? col.width : null\"\n [style.min-width]=\"col.field === 'actions' ? col.width : null\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n class=\"relative px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)] focus:outline-none focus-visible:ring-1 focus-visible:[--tw-ring-color:var(--shk-primary-500)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <shk-tooltip [text]=\"'table.tooltips.sort' | transloco\">\n @if (sortField() === col.field) {\n <svg\n [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"\n ></svg>\n } @else {\n <svg\n [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n }\n </shk-tooltip>\n }\n @if (col.filter) {\n <shk-tooltip [text]=\"'table.tooltips.filter' | transloco\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n [attr.aria-label]=\"'table.tooltips.filter' | transloco\"\n [attr.aria-expanded]=\"showFilterInput()[col.field] || false\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"hasFilterValue(col.field)\"\n ></svg>\n </button>\n </shk-tooltip>\n }\n </div>\n </div>\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"absolute left-0 top-full z-50 mt-1 p-2 min-w-[200px] bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-lg shadow-lg\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <input\n [id]=\"'shk-filter-' + col.field\"\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n \"\n />\n }\n @if (col.filterType === 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <select\n [id]=\"'shk-filter-' + col.field\"\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">{{ 'table.filterAll' | transloco }}</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (hasFilterValue(col.field) && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div\n class=\"flex items-center justify-center\"\n role=\"status\"\n [attr.aria-label]=\"'common.loading' | transloco\"\n >\n <svg\n [lucideIcon]=\"getIcon('loader')\"\n class=\"w-6 h-6 animate-spin _shk-text-primary\"\n ></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div\n class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\"\n >\n <svg\n [lucideIcon]=\"getIcon('inbox')\"\n class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <p\n class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n >\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track trackByRow(rowData, $index)) {\n <tr\n class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n @for (col of columnsWithActions(); track col.field) {\n <td\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [class]=\"\n 'px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] ' +\n (col.field === 'actions' ? 'whitespace-nowrap' : 'whitespace-normal break-words')\n \"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'visible'\"\n [style.text-overflow]=\"'clip'\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <shk-tag\n [value]=\"getTagValue(col, rowData)\"\n [severity]=\"getTagSeverity(col, rowData)\"\n [rounded]=\"getTagRounded(col, rowData)\"\n [icon]=\"getTagIcon(col, rowData)\"\n [style]=\"getTagStyle(col, rowData)\"\n />\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of getVisibleRowActions(rowData); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <shk-tooltip\n [text]=\"getRowActionLabel(action, rowData)\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"\n 'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' +\n getRowActionClass(action, rowData)\n \"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [attr.aria-label]=\"getRowActionLabel(action, rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n </div>\n }\n }\n @if (getHiddenRowActions(rowData).length > 0) {\n <div class=\"relative\">\n <shk-tooltip\n [text]=\"'common.more_options' | transloco\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleActionsDropdown(rowData, $event)\"\n class=\"table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md\"\n [attr.aria-label]=\"'common.more_options' | transloco\"\n [attr.aria-expanded]=\"isActionsDropdownOpen(rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon('ellipsis-vertical')\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n @if (isActionsDropdownOpen(rowData)) {\n <div\n class=\"fixed z-[9999] min-w-[160px] bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md shadow-lg py-1\"\n [style.top]=\"dropdownPosition().top + 'px'\"\n [style.left]=\"dropdownPosition().left + 'px'\"\n >\n @for (action of getHiddenRowActions(rowData); track $index) {\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData); toggleActionsDropdown(rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [class]=\"\n 'w-full flex items-center gap-2 px-3 py-2 text-xs text-left _shk-text-surface-700 dark:[color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] disabled:opacity-40 disabled:cursor-not-allowed transition-colors ' +\n getRowActionClass(action, rowData)\n \"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-3.5 h-3.5 shrink-0\"\n ></svg>\n <span>{{ getRowActionLabel(action, rowData) }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ rowData[field] }}\n </ng-template>\n </div>\n <div\n class=\"table-pagination flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n {{ pageReport() }}\n </div>\n <div\n class=\"flex items-center gap-0.5\"\n role=\"navigation\"\n >\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span\n class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\"\n >{{ currentPage() }} / {{ totalPages() }}</span\n >\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\"\n >\n <label [for]=\"'shk-rows-per-page'\">{{ 'table.rows' | transloco }}</label>\n <select\n id=\"shk-rows-per-page\"\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table td{white-space:normal;word-break:break-word}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;overflow-y:hidden;border-radius:inherit;-webkit-overflow-scrolling:touch;container-type:inline-size}.table-card,.table-pagination{container-type:inline-size}@container (max-width: 640px){.table-pagination{flex-direction:column;align-items:center;gap:.5rem}.table-pagination>div{justify-content:center}}.table-scroll-wrapper table{table-layout:fixed;width:100%}@container (max-width: 768px){.table-scroll-wrapper{scroll-snap-type:x mandatory}.table-scroll-wrapper table{table-layout:auto;width:100%}.table-scroll-wrapper th:not([data-shk-actions]),.table-scroll-wrapper td:not([data-shk-actions]){min-width:var(--shk-col-width, 140px);max-width:var(--shk-col-width, 140px)}.table-scroll-wrapper th,.table-scroll-wrapper td{scroll-snap-align:start}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "component", type: TagComponent, selector: "shk-tag", inputs: ["value", "severity", "rounded", "icon", "style", "styleClass"] }, { kind: "component", type: TooltipComponent, selector: "shk-tooltip", inputs: ["text", "position", "mode", "offset"] }, { kind: "pipe", type: i3.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
930
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: TableComponent, isStandalone: true, selector: "shk-table", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPage: { classPropertyName: "rowsPerPage", publicName: "rowsPerPage", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showActionRow: { classPropertyName: "showActionRow", publicName: "showActionRow", isSignal: true, isRequired: false, transformFunction: null }, customTemplates: { classPropertyName: "customTemplates", publicName: "customTemplates", isSignal: true, isRequired: false, transformFunction: null }, headerActions: { classPropertyName: "headerActions", publicName: "headerActions", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, hasShadow: { classPropertyName: "hasShadow", publicName: "hasShadow", isSignal: true, isRequired: false, transformFunction: null }, defaultSortField: { classPropertyName: "defaultSortField", publicName: "defaultSortField", isSignal: true, isRequired: false, transformFunction: null }, defaultSortOrder: { classPropertyName: "defaultSortOrder", publicName: "defaultSortOrder", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, activeFilter: { classPropertyName: "activeFilter", publicName: "activeFilter", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, isRowSelectable: { classPropertyName: "isRowSelectable", publicName: "isRowSelectable", isSignal: true, isRequired: false, transformFunction: null }, bulkActions: { classPropertyName: "bulkActions", publicName: "bulkActions", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange", refresh: "refresh", pageChange: "pageChange", filterChange: "filterChange", searchChange: "searchChange", filterClick: "filterClick" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n @if (\n showSearch() ||\n filters().length > 0 ||\n headerActions().length > 0 ||\n (selectable() && bulkActions().length > 0 && selection().length > 0)\n ) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <label [for]=\"uid + '-search'\" class=\"sr-only\">{{\n searchPlaceholder() || ('common.search_placeholder' | transloco)\n }}</label>\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg\n [lucideIcon]=\"getIcon('search')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <input\n [id]=\"uid + '-search'\"\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n @if (filters().length > 0) {\n <div\n class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\"\n role=\"group\"\n >\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n [attr.aria-pressed]=\"f.value === activeFilter()\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"\n f.value === activeFilter()\n ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]'\n : ''\n \"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n @if (selectable() && selection().length > 0 && bulkActions().length > 0) {\n <div\n class=\"flex items-center gap-1.5 shrink-0\"\n role=\"toolbar\"\n [attr.aria-label]=\"'table.selected' | transloco: { count: selection().length }\"\n >\n <span\n class=\"text-xs font-medium _shk-text-surface-600 dark:[color:var(--shk-surface-400)] whitespace-nowrap\"\n aria-live=\"polite\"\n >{{ 'table.selected' | transloco: { count: selection().length } }}</span\n >\n <button\n type=\"button\"\n (click)=\"clearSelection()\"\n [attr.aria-label]=\"'table.clearSelection' | transloco\"\n class=\"table-action-btn rounded-md _shk-text-surface-500 dark:[color:var(--shk-surface-400)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)]\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n @for (action of visibleBulkActions(); track $index) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isBulkActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"runBulkAction(action)\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n }\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isHeaderActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n <div class=\"card p-0 overflow-hidden table-card\" [class.shadow-lg]=\"hasShadow()\">\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table>\n <thead>\n <tr>\n @if (selectable()) {\n <th\n scope=\"col\"\n data-shk-select\n class=\"w-10 px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-center _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)]\"\n >\n <input\n type=\"checkbox\"\n class=\"shk-table-checkbox\"\n [checked]=\"allPageSelected()\"\n [indeterminate]=\"somePageSelected()\"\n [disabled]=\"loading() || selectablePageRows().length === 0\"\n (change)=\"toggleSelectAllPage()\"\n [attr.aria-label]=\"'table.selectAll' | transloco\"\n />\n </th>\n }\n @for (col of columnsWithActions(); track col.field) {\n <th\n scope=\"col\"\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n (keydown)=\"onHeaderKeydown($event, col)\"\n [tabindex]=\"col.field !== 'actions' && col.sortable ? 0 : -1\"\n [attr.role]=\"col.field !== 'actions' && col.sortable ? 'button' : null\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.field === 'actions' ? col.width : null\"\n [style.min-width]=\"col.field === 'actions' ? col.width : null\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n class=\"relative px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)] focus:outline-none focus-visible:ring-1 focus-visible:[--tw-ring-color:var(--shk-primary-500)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1 shrink-0\">\n @if (col.sortable) {\n <shk-tooltip [text]=\"'table.tooltips.sort' | transloco\">\n @if (sortField() === col.field) {\n <svg\n [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"\n ></svg>\n } @else {\n <svg\n [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n }\n </shk-tooltip>\n }\n @if (col.filter) {\n <shk-tooltip [text]=\"'table.tooltips.filter' | transloco\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field, $event)\"\n [attr.aria-label]=\"'table.tooltips.filter' | transloco\"\n [attr.aria-expanded]=\"showFilterInput()[col.field] || false\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"hasFilterValue(col.field)\"\n ></svg>\n </button>\n </shk-tooltip>\n }\n </div>\n </div>\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"fixed z-[9999] p-2 w-[200px] normal-case tracking-normal font-normal bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-lg shadow-lg\"\n [style.top]=\"filterPopupPosition().top + 'px'\"\n [style.left]=\"filterPopupPosition().left + 'px'\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (keydown.escape)=\"closeFilters()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <label [for]=\"uid + '-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <input\n [id]=\"uid + '-filter-' + col.field\"\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n \"\n />\n }\n @if (col.filterType === 'select') {\n <label [for]=\"uid + '-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <select\n [id]=\"uid + '-filter-' + col.field\"\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">{{ 'table.filterAll' | transloco }}</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (hasFilterValue(col.field) && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"totalColspan()\" class=\"text-center py-12\">\n <div\n class=\"flex items-center justify-center\"\n role=\"status\"\n [attr.aria-label]=\"'common.loading' | transloco\"\n >\n <svg\n [lucideIcon]=\"getIcon('loader')\"\n class=\"w-6 h-6 animate-spin _shk-text-primary\"\n ></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"totalColspan()\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div\n class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\"\n >\n <svg\n [lucideIcon]=\"getIcon('inbox')\"\n class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <p\n class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n >\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track trackByRow(rowData, $index)) {\n <tr\n [attr.aria-selected]=\"selectable() ? isRowSelected(rowData) : null\"\n [class.shk-row-selected]=\"selectable() && isRowSelected(rowData)\"\n class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n @if (selectable()) {\n <td data-shk-select class=\"w-10 px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-center\">\n <input\n type=\"checkbox\"\n class=\"shk-table-checkbox\"\n [checked]=\"isRowSelected(rowData)\"\n [disabled]=\"!canSelectRow(rowData)\"\n (change)=\"toggleRowSelection(rowData)\"\n [attr.aria-label]=\"'table.selectRow' | transloco: { index: first() + $index + 1 }\"\n />\n </td>\n }\n @for (col of columnsWithActions(); track col.field) {\n <td\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [class]=\"\n 'px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] ' +\n (col.field === 'actions' ? 'whitespace-nowrap' : 'whitespace-normal break-words')\n \"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'visible'\"\n [style.text-overflow]=\"'clip'\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <shk-tag\n [value]=\"getTagValue(col, rowData)\"\n [severity]=\"getTagSeverity(col, rowData)\"\n [rounded]=\"getTagRounded(col, rowData)\"\n [icon]=\"getTagIcon(col, rowData)\"\n [style]=\"getTagStyle(col, rowData)\"\n />\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of getVisibleRowActions(rowData); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <shk-tooltip\n [text]=\"getRowActionLabel(action, rowData)\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"\n 'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' +\n getRowActionClass(action, rowData)\n \"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [attr.aria-label]=\"getRowActionLabel(action, rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n </div>\n }\n }\n @if (getHiddenRowActions(rowData).length > 0) {\n <div class=\"relative\">\n <shk-tooltip\n [text]=\"'common.more_options' | transloco\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleActionsDropdown(rowData, $event)\"\n class=\"table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md\"\n [attr.aria-label]=\"'common.more_options' | transloco\"\n [attr.aria-expanded]=\"isActionsDropdownOpen(rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon('ellipsis-vertical')\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n @if (isActionsDropdownOpen(rowData)) {\n <div\n class=\"fixed z-[9999] min-w-[160px] bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md shadow-lg py-1\"\n [style.top]=\"dropdownPosition().top + 'px'\"\n [style.left]=\"dropdownPosition().left + 'px'\"\n >\n @for (action of getHiddenRowActions(rowData); track $index) {\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData); toggleActionsDropdown(rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [class]=\"\n 'w-full flex items-center gap-2 px-3 py-2 text-xs text-left _shk-text-surface-700 dark:[color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] disabled:opacity-40 disabled:cursor-not-allowed transition-colors ' +\n getRowActionClass(action, rowData)\n \"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-3.5 h-3.5 shrink-0\"\n ></svg>\n <span>{{ getRowActionLabel(action, rowData) }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ rowData[field] }}\n </ng-template>\n </div>\n <div\n class=\"table-pagination flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n {{ pageReport() }}\n </div>\n <div\n class=\"flex items-center gap-0.5\"\n role=\"navigation\"\n >\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span\n class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\"\n >{{ currentPage() }} / {{ totalPages() }}</span\n >\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\"\n >\n <label [for]=\"uid + '-rows-per-page'\">{{ 'table.rows' | transloco }}</label>\n <select\n [id]=\"uid + '-rows-per-page'\"\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap}table td{white-space:normal;overflow-wrap:break-word}.shk-page-btn{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border-radius:.375rem;transition:background-color .15s ease,color .15s ease}.shk-page-btn:hover:not(:disabled){background-color:var(--shk-surface-100)}:host-context(.dark) .shk-page-btn:hover:not(:disabled){background-color:var(--shk-surface-200)}.shk-page-btn:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--shk-primary-500) 45%,transparent)}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;overflow-y:hidden;border-radius:inherit;-webkit-overflow-scrolling:touch;container-type:inline-size}.table-card,.table-pagination{container-type:inline-size}@container (max-width: 640px){.table-pagination{flex-direction:column;align-items:center;gap:.5rem}.table-pagination>div{justify-content:center}}.table-scroll-wrapper table{table-layout:auto;width:100%}@container (max-width: 768px){.table-scroll-wrapper{scroll-snap-type:x mandatory}.table-scroll-wrapper table{table-layout:auto;width:100%}.table-scroll-wrapper th:not([data-shk-actions]):not([data-shk-select]){min-width:var(--shk-col-width, 140px)}.table-scroll-wrapper td:not([data-shk-actions]):not([data-shk-select]){min-width:var(--shk-col-width, 140px);max-width:var(--shk-col-width, 140px)}.table-scroll-wrapper th,.table-scroll-wrapper td{scroll-snap-align:start}}th[data-shk-select],td[data-shk-select]{width:2.5rem;min-width:2.5rem}.shk-table-checkbox{width:.875rem;height:.875rem;cursor:pointer;vertical-align:middle;accent-color:var(--shk-primary-500)}.shk-table-checkbox:disabled{opacity:.4;cursor:not-allowed}tr.shk-row-selected{background-color:color-mix(in srgb,var(--shk-primary-50) 60%,transparent)}tr.shk-row-selected:hover{background-color:color-mix(in srgb,var(--shk-primary-50) 90%,transparent)}:host-context(.dark) tr.shk-row-selected{background-color:color-mix(in srgb,var(--shk-primary-900) 20%,transparent)}:host-context(.dark) tr.shk-row-selected:hover{background-color:color-mix(in srgb,var(--shk-primary-900) 30%,transparent)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "component", type: TagComponent, selector: "shk-tag", inputs: ["value", "severity", "rounded", "icon", "style", "styleClass"] }, { kind: "component", type: TooltipComponent, selector: "shk-tooltip", inputs: ["text", "position", "mode", "offset"] }, { kind: "pipe", type: i3.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
748
931
|
}
|
|
749
932
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: TableComponent, decorators: [{
|
|
750
933
|
type: Component,
|
|
@@ -755,10 +938,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
|
|
|
755
938
|
LucideDynamicIcon,
|
|
756
939
|
TagComponent,
|
|
757
940
|
TooltipComponent,
|
|
758
|
-
], template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <label [for]=\"'shk-table-search'\" class=\"sr-only\">{{\n searchPlaceholder() || ('common.search_placeholder' | transloco)\n }}</label>\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg\n [lucideIcon]=\"getIcon('search')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <input\n id=\"shk-table-search\"\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n @if (filters().length > 0) {\n <div\n class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\"\n role=\"group\"\n >\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n [attr.aria-pressed]=\"f.value === activeFilter()\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"\n f.value === activeFilter()\n ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]'\n : ''\n \"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isHeaderActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n <div class=\"card p-0 overflow-hidden table-card\" [class.shadow-lg]=\"hasShadow()\">\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table>\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n scope=\"col\"\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n (keydown)=\"onHeaderKeydown($event, col)\"\n [tabindex]=\"col.field !== 'actions' && col.sortable ? 0 : -1\"\n [attr.role]=\"col.field !== 'actions' && col.sortable ? 'button' : null\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.field === 'actions' ? col.width : null\"\n [style.min-width]=\"col.field === 'actions' ? col.width : null\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n class=\"relative px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)] focus:outline-none focus-visible:ring-1 focus-visible:[--tw-ring-color:var(--shk-primary-500)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <shk-tooltip [text]=\"'table.tooltips.sort' | transloco\">\n @if (sortField() === col.field) {\n <svg\n [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"\n ></svg>\n } @else {\n <svg\n [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n }\n </shk-tooltip>\n }\n @if (col.filter) {\n <shk-tooltip [text]=\"'table.tooltips.filter' | transloco\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n [attr.aria-label]=\"'table.tooltips.filter' | transloco\"\n [attr.aria-expanded]=\"showFilterInput()[col.field] || false\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"hasFilterValue(col.field)\"\n ></svg>\n </button>\n </shk-tooltip>\n }\n </div>\n </div>\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"absolute left-0 top-full z-50 mt-1 p-2 min-w-[200px] bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-lg shadow-lg\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <input\n [id]=\"'shk-filter-' + col.field\"\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n \"\n />\n }\n @if (col.filterType === 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <select\n [id]=\"'shk-filter-' + col.field\"\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">{{ 'table.filterAll' | transloco }}</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (hasFilterValue(col.field) && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div\n class=\"flex items-center justify-center\"\n role=\"status\"\n [attr.aria-label]=\"'common.loading' | transloco\"\n >\n <svg\n [lucideIcon]=\"getIcon('loader')\"\n class=\"w-6 h-6 animate-spin _shk-text-primary\"\n ></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div\n class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\"\n >\n <svg\n [lucideIcon]=\"getIcon('inbox')\"\n class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <p\n class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n >\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track trackByRow(rowData, $index)) {\n <tr\n class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n @for (col of columnsWithActions(); track col.field) {\n <td\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [class]=\"\n 'px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] ' +\n (col.field === 'actions' ? 'whitespace-nowrap' : 'whitespace-normal break-words')\n \"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'visible'\"\n [style.text-overflow]=\"'clip'\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <shk-tag\n [value]=\"getTagValue(col, rowData)\"\n [severity]=\"getTagSeverity(col, rowData)\"\n [rounded]=\"getTagRounded(col, rowData)\"\n [icon]=\"getTagIcon(col, rowData)\"\n [style]=\"getTagStyle(col, rowData)\"\n />\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of getVisibleRowActions(rowData); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <shk-tooltip\n [text]=\"getRowActionLabel(action, rowData)\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"\n 'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' +\n getRowActionClass(action, rowData)\n \"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [attr.aria-label]=\"getRowActionLabel(action, rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n </div>\n }\n }\n @if (getHiddenRowActions(rowData).length > 0) {\n <div class=\"relative\">\n <shk-tooltip\n [text]=\"'common.more_options' | transloco\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleActionsDropdown(rowData, $event)\"\n class=\"table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md\"\n [attr.aria-label]=\"'common.more_options' | transloco\"\n [attr.aria-expanded]=\"isActionsDropdownOpen(rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon('ellipsis-vertical')\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n @if (isActionsDropdownOpen(rowData)) {\n <div\n class=\"fixed z-[9999] min-w-[160px] bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md shadow-lg py-1\"\n [style.top]=\"dropdownPosition().top + 'px'\"\n [style.left]=\"dropdownPosition().left + 'px'\"\n >\n @for (action of getHiddenRowActions(rowData); track $index) {\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData); toggleActionsDropdown(rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [class]=\"\n 'w-full flex items-center gap-2 px-3 py-2 text-xs text-left _shk-text-surface-700 dark:[color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] disabled:opacity-40 disabled:cursor-not-allowed transition-colors ' +\n getRowActionClass(action, rowData)\n \"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-3.5 h-3.5 shrink-0\"\n ></svg>\n <span>{{ getRowActionLabel(action, rowData) }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ rowData[field] }}\n </ng-template>\n </div>\n <div\n class=\"table-pagination flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n {{ pageReport() }}\n </div>\n <div\n class=\"flex items-center gap-0.5\"\n role=\"navigation\"\n >\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span\n class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\"\n >{{ currentPage() }} / {{ totalPages() }}</span\n >\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\"\n >\n <label [for]=\"'shk-rows-per-page'\">{{ 'table.rows' | transloco }}</label>\n <select\n id=\"shk-rows-per-page\"\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table td{white-space:normal;word-break:break-word}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;overflow-y:hidden;border-radius:inherit;-webkit-overflow-scrolling:touch;container-type:inline-size}.table-card,.table-pagination{container-type:inline-size}@container (max-width: 640px){.table-pagination{flex-direction:column;align-items:center;gap:.5rem}.table-pagination>div{justify-content:center}}.table-scroll-wrapper table{table-layout:fixed;width:100%}@container (max-width: 768px){.table-scroll-wrapper{scroll-snap-type:x mandatory}.table-scroll-wrapper table{table-layout:auto;width:100%}.table-scroll-wrapper th:not([data-shk-actions]),.table-scroll-wrapper td:not([data-shk-actions]){min-width:var(--shk-col-width, 140px);max-width:var(--shk-col-width, 140px)}.table-scroll-wrapper th,.table-scroll-wrapper td{scroll-snap-align:start}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
|
|
759
|
-
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rowsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPage", required: false }] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], showActionRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActionRow", required: false }] }], customTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "customTemplates", required: false }] }], headerActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerActions", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], hasShadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasShadow", required: false }] }], defaultSortField: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortField", required: false }] }], defaultSortOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortOrder", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }], activeFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeFilter", required: false }] }], refresh: [{ type: i0.Output, args: ["refresh"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], filterClick: [{ type: i0.Output, args: ["filterClick"] }], onDocumentClick: [{
|
|
941
|
+
], template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n @if (\n showSearch() ||\n filters().length > 0 ||\n headerActions().length > 0 ||\n (selectable() && bulkActions().length > 0 && selection().length > 0)\n ) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <label [for]=\"uid + '-search'\" class=\"sr-only\">{{\n searchPlaceholder() || ('common.search_placeholder' | transloco)\n }}</label>\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg\n [lucideIcon]=\"getIcon('search')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <input\n [id]=\"uid + '-search'\"\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n @if (filters().length > 0) {\n <div\n class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\"\n role=\"group\"\n >\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n [attr.aria-pressed]=\"f.value === activeFilter()\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"\n f.value === activeFilter()\n ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]'\n : ''\n \"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n @if (selectable() && selection().length > 0 && bulkActions().length > 0) {\n <div\n class=\"flex items-center gap-1.5 shrink-0\"\n role=\"toolbar\"\n [attr.aria-label]=\"'table.selected' | transloco: { count: selection().length }\"\n >\n <span\n class=\"text-xs font-medium _shk-text-surface-600 dark:[color:var(--shk-surface-400)] whitespace-nowrap\"\n aria-live=\"polite\"\n >{{ 'table.selected' | transloco: { count: selection().length } }}</span\n >\n <button\n type=\"button\"\n (click)=\"clearSelection()\"\n [attr.aria-label]=\"'table.clearSelection' | transloco\"\n class=\"table-action-btn rounded-md _shk-text-surface-500 dark:[color:var(--shk-surface-400)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)]\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n @for (action of visibleBulkActions(); track $index) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isBulkActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"runBulkAction(action)\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n }\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isHeaderActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n <div class=\"card p-0 overflow-hidden table-card\" [class.shadow-lg]=\"hasShadow()\">\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table>\n <thead>\n <tr>\n @if (selectable()) {\n <th\n scope=\"col\"\n data-shk-select\n class=\"w-10 px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-center _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)]\"\n >\n <input\n type=\"checkbox\"\n class=\"shk-table-checkbox\"\n [checked]=\"allPageSelected()\"\n [indeterminate]=\"somePageSelected()\"\n [disabled]=\"loading() || selectablePageRows().length === 0\"\n (change)=\"toggleSelectAllPage()\"\n [attr.aria-label]=\"'table.selectAll' | transloco\"\n />\n </th>\n }\n @for (col of columnsWithActions(); track col.field) {\n <th\n scope=\"col\"\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n (keydown)=\"onHeaderKeydown($event, col)\"\n [tabindex]=\"col.field !== 'actions' && col.sortable ? 0 : -1\"\n [attr.role]=\"col.field !== 'actions' && col.sortable ? 'button' : null\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.field === 'actions' ? col.width : null\"\n [style.min-width]=\"col.field === 'actions' ? col.width : null\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n class=\"relative px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)] focus:outline-none focus-visible:ring-1 focus-visible:[--tw-ring-color:var(--shk-primary-500)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1 shrink-0\">\n @if (col.sortable) {\n <shk-tooltip [text]=\"'table.tooltips.sort' | transloco\">\n @if (sortField() === col.field) {\n <svg\n [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"\n ></svg>\n } @else {\n <svg\n [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n }\n </shk-tooltip>\n }\n @if (col.filter) {\n <shk-tooltip [text]=\"'table.tooltips.filter' | transloco\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field, $event)\"\n [attr.aria-label]=\"'table.tooltips.filter' | transloco\"\n [attr.aria-expanded]=\"showFilterInput()[col.field] || false\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"hasFilterValue(col.field)\"\n ></svg>\n </button>\n </shk-tooltip>\n }\n </div>\n </div>\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"fixed z-[9999] p-2 w-[200px] normal-case tracking-normal font-normal bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-lg shadow-lg\"\n [style.top]=\"filterPopupPosition().top + 'px'\"\n [style.left]=\"filterPopupPosition().left + 'px'\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (keydown.escape)=\"closeFilters()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <label [for]=\"uid + '-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <input\n [id]=\"uid + '-filter-' + col.field\"\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n \"\n />\n }\n @if (col.filterType === 'select') {\n <label [for]=\"uid + '-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <select\n [id]=\"uid + '-filter-' + col.field\"\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">{{ 'table.filterAll' | transloco }}</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (hasFilterValue(col.field) && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"totalColspan()\" class=\"text-center py-12\">\n <div\n class=\"flex items-center justify-center\"\n role=\"status\"\n [attr.aria-label]=\"'common.loading' | transloco\"\n >\n <svg\n [lucideIcon]=\"getIcon('loader')\"\n class=\"w-6 h-6 animate-spin _shk-text-primary\"\n ></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"totalColspan()\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div\n class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\"\n >\n <svg\n [lucideIcon]=\"getIcon('inbox')\"\n class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <p\n class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n >\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track trackByRow(rowData, $index)) {\n <tr\n [attr.aria-selected]=\"selectable() ? isRowSelected(rowData) : null\"\n [class.shk-row-selected]=\"selectable() && isRowSelected(rowData)\"\n class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n @if (selectable()) {\n <td data-shk-select class=\"w-10 px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-center\">\n <input\n type=\"checkbox\"\n class=\"shk-table-checkbox\"\n [checked]=\"isRowSelected(rowData)\"\n [disabled]=\"!canSelectRow(rowData)\"\n (change)=\"toggleRowSelection(rowData)\"\n [attr.aria-label]=\"'table.selectRow' | transloco: { index: first() + $index + 1 }\"\n />\n </td>\n }\n @for (col of columnsWithActions(); track col.field) {\n <td\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [class]=\"\n 'px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] ' +\n (col.field === 'actions' ? 'whitespace-nowrap' : 'whitespace-normal break-words')\n \"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'visible'\"\n [style.text-overflow]=\"'clip'\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <shk-tag\n [value]=\"getTagValue(col, rowData)\"\n [severity]=\"getTagSeverity(col, rowData)\"\n [rounded]=\"getTagRounded(col, rowData)\"\n [icon]=\"getTagIcon(col, rowData)\"\n [style]=\"getTagStyle(col, rowData)\"\n />\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of getVisibleRowActions(rowData); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <shk-tooltip\n [text]=\"getRowActionLabel(action, rowData)\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"\n 'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' +\n getRowActionClass(action, rowData)\n \"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [attr.aria-label]=\"getRowActionLabel(action, rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n </div>\n }\n }\n @if (getHiddenRowActions(rowData).length > 0) {\n <div class=\"relative\">\n <shk-tooltip\n [text]=\"'common.more_options' | transloco\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleActionsDropdown(rowData, $event)\"\n class=\"table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md\"\n [attr.aria-label]=\"'common.more_options' | transloco\"\n [attr.aria-expanded]=\"isActionsDropdownOpen(rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon('ellipsis-vertical')\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n @if (isActionsDropdownOpen(rowData)) {\n <div\n class=\"fixed z-[9999] min-w-[160px] bg-white dark:[background-color:var(--shk-surface-100)] border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md shadow-lg py-1\"\n [style.top]=\"dropdownPosition().top + 'px'\"\n [style.left]=\"dropdownPosition().left + 'px'\"\n >\n @for (action of getHiddenRowActions(rowData); track $index) {\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData); toggleActionsDropdown(rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [class]=\"\n 'w-full flex items-center gap-2 px-3 py-2 text-xs text-left _shk-text-surface-700 dark:[color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] disabled:opacity-40 disabled:cursor-not-allowed transition-colors ' +\n getRowActionClass(action, rowData)\n \"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-3.5 h-3.5 shrink-0\"\n ></svg>\n <span>{{ getRowActionLabel(action, rowData) }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ rowData[field] }}\n </ng-template>\n </div>\n <div\n class=\"table-pagination flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n {{ pageReport() }}\n </div>\n <div\n class=\"flex items-center gap-0.5\"\n role=\"navigation\"\n >\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span\n class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\"\n >{{ currentPage() }} / {{ totalPages() }}</span\n >\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"shk-page-btn p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\"\n >\n <label [for]=\"uid + '-rows-per-page'\">{{ 'table.rows' | transloco }}</label>\n <select\n [id]=\"uid + '-rows-per-page'\"\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap}table td{white-space:normal;overflow-wrap:break-word}.shk-page-btn{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border-radius:.375rem;transition:background-color .15s ease,color .15s ease}.shk-page-btn:hover:not(:disabled){background-color:var(--shk-surface-100)}:host-context(.dark) .shk-page-btn:hover:not(:disabled){background-color:var(--shk-surface-200)}.shk-page-btn:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--shk-primary-500) 45%,transparent)}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;overflow-y:hidden;border-radius:inherit;-webkit-overflow-scrolling:touch;container-type:inline-size}.table-card,.table-pagination{container-type:inline-size}@container (max-width: 640px){.table-pagination{flex-direction:column;align-items:center;gap:.5rem}.table-pagination>div{justify-content:center}}.table-scroll-wrapper table{table-layout:auto;width:100%}@container (max-width: 768px){.table-scroll-wrapper{scroll-snap-type:x mandatory}.table-scroll-wrapper table{table-layout:auto;width:100%}.table-scroll-wrapper th:not([data-shk-actions]):not([data-shk-select]){min-width:var(--shk-col-width, 140px)}.table-scroll-wrapper td:not([data-shk-actions]):not([data-shk-select]){min-width:var(--shk-col-width, 140px);max-width:var(--shk-col-width, 140px)}.table-scroll-wrapper th,.table-scroll-wrapper td{scroll-snap-align:start}}th[data-shk-select],td[data-shk-select]{width:2.5rem;min-width:2.5rem}.shk-table-checkbox{width:.875rem;height:.875rem;cursor:pointer;vertical-align:middle;accent-color:var(--shk-primary-500)}.shk-table-checkbox:disabled{opacity:.4;cursor:not-allowed}tr.shk-row-selected{background-color:color-mix(in srgb,var(--shk-primary-50) 60%,transparent)}tr.shk-row-selected:hover{background-color:color-mix(in srgb,var(--shk-primary-50) 90%,transparent)}:host-context(.dark) tr.shk-row-selected{background-color:color-mix(in srgb,var(--shk-primary-900) 20%,transparent)}:host-context(.dark) tr.shk-row-selected:hover{background-color:color-mix(in srgb,var(--shk-primary-900) 30%,transparent)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
|
|
942
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rowsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPage", required: false }] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], showActionRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActionRow", required: false }] }], customTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "customTemplates", required: false }] }], headerActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerActions", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], hasShadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasShadow", required: false }] }], defaultSortField: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortField", required: false }] }], defaultSortOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortOrder", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }], activeFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeFilter", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], isRowSelectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isRowSelectable", required: false }] }], bulkActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "bulkActions", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], refresh: [{ type: i0.Output, args: ["refresh"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], filterClick: [{ type: i0.Output, args: ["filterClick"] }], onDocumentClick: [{
|
|
760
943
|
type: HostListener,
|
|
761
944
|
args: ['document:click']
|
|
945
|
+
}], onEscape: [{
|
|
946
|
+
type: HostListener,
|
|
947
|
+
args: ['document:keydown.escape']
|
|
762
948
|
}] } });
|
|
763
949
|
|
|
764
950
|
/**
|