cloud-ide-element 1.1.24 → 1.1.25
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/fesm2022/cloud-ide-element.mjs +548 -34
- package/fesm2022/cloud-ide-element.mjs.map +1 -1
- package/index.d.ts +43 -1
- package/package.json +1 -1
|
@@ -8563,6 +8563,9 @@ class CideEleDataGridComponent {
|
|
|
8563
8563
|
hiddenColumns = signal([], ...(ngDevMode ? [{ debugName: "hiddenColumns" }] : [])); // Hidden column keys
|
|
8564
8564
|
showFilterPanel = signal(null, ...(ngDevMode ? [{ debugName: "showFilterPanel" }] : [])); // Column key for active filter panel
|
|
8565
8565
|
filterSearchTerm = signal('', ...(ngDevMode ? [{ debugName: "filterSearchTerm" }] : [])); // Search term for filter values
|
|
8566
|
+
showManageColumnsModal = signal(false, ...(ngDevMode ? [{ debugName: "showManageColumnsModal" }] : [])); // Show manage columns modal
|
|
8567
|
+
groupedColumns = signal([], ...(ngDevMode ? [{ debugName: "groupedColumns" }] : [])); // Columns to group by
|
|
8568
|
+
expandedGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedGroups" }] : [])); // Expanded group keys
|
|
8566
8569
|
// Export menu state
|
|
8567
8570
|
showExportMenu = signal(false, ...(ngDevMode ? [{ debugName: "showExportMenu" }] : [])); // Show/hide export format dropdown
|
|
8568
8571
|
showFilterDropdown = signal(false, ...(ngDevMode ? [{ debugName: "showFilterDropdown" }] : [])); // Show/hide filter dropdown
|
|
@@ -8583,6 +8586,8 @@ class CideEleDataGridComponent {
|
|
|
8583
8586
|
this.initializeOriginalOrder();
|
|
8584
8587
|
// Add global click listener to close menus when clicking outside
|
|
8585
8588
|
document.addEventListener('click', this.handleGlobalClick.bind(this));
|
|
8589
|
+
// Add ESC key listener to close manage columns modal
|
|
8590
|
+
document.addEventListener('keydown', this.handleKeyDown.bind(this));
|
|
8586
8591
|
}
|
|
8587
8592
|
ngOnDestroy() {
|
|
8588
8593
|
// Clean up search debounce timer
|
|
@@ -8591,16 +8596,28 @@ class CideEleDataGridComponent {
|
|
|
8591
8596
|
}
|
|
8592
8597
|
// Remove global click listener
|
|
8593
8598
|
document.removeEventListener('click', this.handleGlobalClick.bind(this));
|
|
8599
|
+
// Remove ESC key listener
|
|
8600
|
+
document.removeEventListener('keydown', this.handleKeyDown.bind(this));
|
|
8601
|
+
}
|
|
8602
|
+
handleKeyDown(event) {
|
|
8603
|
+
if (event.key === 'Escape' && this.showManageColumnsModal()) {
|
|
8604
|
+
this.closeManageColumnsModal();
|
|
8605
|
+
}
|
|
8594
8606
|
}
|
|
8595
8607
|
handleGlobalClick(event) {
|
|
8596
8608
|
// Close column menu and filter panel when clicking outside
|
|
8597
8609
|
const target = event.target;
|
|
8598
8610
|
const isInsideMenu = target.closest('.column-menu-dropdown');
|
|
8599
8611
|
const isMenuTrigger = target.closest('.column-menu-trigger');
|
|
8612
|
+
const isInsideFilterDropdown = target.closest('.filter-dropdown');
|
|
8613
|
+
const isFilterDropdownTrigger = target.closest('.filter-dropdown-trigger');
|
|
8600
8614
|
if (!isInsideMenu && !isMenuTrigger) {
|
|
8601
8615
|
this.closeColumnMenu();
|
|
8602
8616
|
this.closeFilterPanel();
|
|
8603
8617
|
}
|
|
8618
|
+
if (!isInsideFilterDropdown && !isFilterDropdownTrigger) {
|
|
8619
|
+
this.showFilterDropdown.set(false);
|
|
8620
|
+
}
|
|
8604
8621
|
}
|
|
8605
8622
|
ngOnChanges(changes) {
|
|
8606
8623
|
if (changes['config']) {
|
|
@@ -8843,15 +8860,83 @@ class CideEleDataGridComponent {
|
|
|
8843
8860
|
if (this.mergedConfig().tree?.enabled) {
|
|
8844
8861
|
filtered = this.flattenTreeForDisplay(filtered);
|
|
8845
8862
|
}
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
//
|
|
8863
|
+
// Apply column filters
|
|
8864
|
+
this.applyFiltersToData(filtered);
|
|
8865
|
+
// Apply sorting
|
|
8866
|
+
this.applySortsToData();
|
|
8867
|
+
}
|
|
8868
|
+
/**
|
|
8869
|
+
* Apply sorting to filtered data
|
|
8870
|
+
*/
|
|
8871
|
+
applySortsToData() {
|
|
8849
8872
|
if (this.serverSidePagination) {
|
|
8850
|
-
|
|
8873
|
+
// For server-side pagination, sorting is handled by the server
|
|
8874
|
+
return;
|
|
8851
8875
|
}
|
|
8852
|
-
|
|
8853
|
-
|
|
8876
|
+
const sorts = this.columnSorts();
|
|
8877
|
+
if (sorts.length === 0) {
|
|
8878
|
+
return;
|
|
8854
8879
|
}
|
|
8880
|
+
let sorted = [...this.filteredData()];
|
|
8881
|
+
sorts.forEach(sort => {
|
|
8882
|
+
const column = this.mergedConfig().columns.find(c => c.key === sort.columnKey);
|
|
8883
|
+
if (!column)
|
|
8884
|
+
return;
|
|
8885
|
+
// Use dataValueGetter if available, otherwise use valueGetter or column.key
|
|
8886
|
+
const valueGetter = column.dataValueGetter || column.valueGetter || column.key;
|
|
8887
|
+
sorted.sort((a, b) => {
|
|
8888
|
+
let valueA;
|
|
8889
|
+
let valueB;
|
|
8890
|
+
if (valueGetter.includes('.') || valueGetter !== column.key) {
|
|
8891
|
+
valueA = this.getNestedValue(a, valueGetter);
|
|
8892
|
+
valueB = this.getNestedValue(b, valueGetter);
|
|
8893
|
+
}
|
|
8894
|
+
else {
|
|
8895
|
+
valueA = a[column.key];
|
|
8896
|
+
valueB = b[column.key];
|
|
8897
|
+
}
|
|
8898
|
+
// Handle null/undefined values
|
|
8899
|
+
if (valueA === null || valueA === undefined)
|
|
8900
|
+
valueA = '';
|
|
8901
|
+
if (valueB === null || valueB === undefined)
|
|
8902
|
+
valueB = '';
|
|
8903
|
+
// Handle objects - extract display text for comparison
|
|
8904
|
+
if (typeof valueA === 'object' && valueA !== null && !Array.isArray(valueA) && !(valueA instanceof Date)) {
|
|
8905
|
+
const objA = valueA;
|
|
8906
|
+
const displayProps = ['name', 'title', 'label', 'text', 'display_text', 'user_fullname', 'sygms_title'];
|
|
8907
|
+
for (const prop of displayProps) {
|
|
8908
|
+
if (objA[prop] !== undefined && objA[prop] !== null) {
|
|
8909
|
+
valueA = objA[prop];
|
|
8910
|
+
break;
|
|
8911
|
+
}
|
|
8912
|
+
}
|
|
8913
|
+
}
|
|
8914
|
+
if (typeof valueB === 'object' && valueB !== null && !Array.isArray(valueB) && !(valueB instanceof Date)) {
|
|
8915
|
+
const objB = valueB;
|
|
8916
|
+
const displayProps = ['name', 'title', 'label', 'text', 'display_text', 'user_fullname', 'sygms_title'];
|
|
8917
|
+
for (const prop of displayProps) {
|
|
8918
|
+
if (objB[prop] !== undefined && objB[prop] !== null) {
|
|
8919
|
+
valueB = objB[prop];
|
|
8920
|
+
break;
|
|
8921
|
+
}
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
// Convert to strings for comparison
|
|
8925
|
+
const strA = String(valueA || '').toLowerCase();
|
|
8926
|
+
const strB = String(valueB || '').toLowerCase();
|
|
8927
|
+
// Compare
|
|
8928
|
+
let comparison = 0;
|
|
8929
|
+
if (strA < strB) {
|
|
8930
|
+
comparison = -1;
|
|
8931
|
+
}
|
|
8932
|
+
else if (strA > strB) {
|
|
8933
|
+
comparison = 1;
|
|
8934
|
+
}
|
|
8935
|
+
return sort.direction === 'asc' ? comparison : -comparison;
|
|
8936
|
+
});
|
|
8937
|
+
});
|
|
8938
|
+
this.filteredData.set(sorted);
|
|
8939
|
+
this.totalItems.set(sorted.length);
|
|
8855
8940
|
this.updatePaginationState();
|
|
8856
8941
|
}
|
|
8857
8942
|
/**
|
|
@@ -9856,6 +9941,7 @@ class CideEleDataGridComponent {
|
|
|
9856
9941
|
sorts.push({ columnKey: column.key, direction });
|
|
9857
9942
|
}
|
|
9858
9943
|
this.columnSorts.set([...sorts]);
|
|
9944
|
+
this.applySortsToData();
|
|
9859
9945
|
this.emitEvent('columnSort', { columnKey: column.key, direction }, column);
|
|
9860
9946
|
this.closeColumnMenu();
|
|
9861
9947
|
}
|
|
@@ -9887,9 +9973,70 @@ class CideEleDataGridComponent {
|
|
|
9887
9973
|
this.closeColumnMenu();
|
|
9888
9974
|
}
|
|
9889
9975
|
onColumnGroupBy(column) {
|
|
9976
|
+
const grouped = this.groupedColumns();
|
|
9977
|
+
const index = grouped.indexOf(column.key);
|
|
9978
|
+
if (index !== -1) {
|
|
9979
|
+
// Remove grouping
|
|
9980
|
+
grouped.splice(index, 1);
|
|
9981
|
+
this.groupedColumns.set([...grouped]);
|
|
9982
|
+
// Clear expanded groups for this column
|
|
9983
|
+
const expanded = this.expandedGroups();
|
|
9984
|
+
const newExpanded = new Set(expanded);
|
|
9985
|
+
expanded.forEach(key => {
|
|
9986
|
+
if (key.startsWith(`${column.key}:`)) {
|
|
9987
|
+
newExpanded.delete(key);
|
|
9988
|
+
}
|
|
9989
|
+
});
|
|
9990
|
+
this.expandedGroups.set(newExpanded);
|
|
9991
|
+
}
|
|
9992
|
+
else {
|
|
9993
|
+
// Add grouping
|
|
9994
|
+
this.groupedColumns.set([...grouped, column.key]);
|
|
9995
|
+
// Expand all groups by default
|
|
9996
|
+
this.expandAllGroupsForColumn(column.key);
|
|
9997
|
+
}
|
|
9890
9998
|
this.emitEvent('columnGroupBy', column.key, column);
|
|
9891
9999
|
this.closeColumnMenu();
|
|
9892
10000
|
}
|
|
10001
|
+
toggleGroupExpansion(groupKey) {
|
|
10002
|
+
const expanded = this.expandedGroups();
|
|
10003
|
+
const newExpanded = new Set(expanded);
|
|
10004
|
+
if (newExpanded.has(groupKey)) {
|
|
10005
|
+
newExpanded.delete(groupKey);
|
|
10006
|
+
}
|
|
10007
|
+
else {
|
|
10008
|
+
newExpanded.add(groupKey);
|
|
10009
|
+
}
|
|
10010
|
+
this.expandedGroups.set(newExpanded);
|
|
10011
|
+
}
|
|
10012
|
+
isGroupExpanded(groupKey) {
|
|
10013
|
+
return this.expandedGroups().has(groupKey);
|
|
10014
|
+
}
|
|
10015
|
+
expandAllGroupsForColumn(columnKey) {
|
|
10016
|
+
const data = this.filteredData();
|
|
10017
|
+
const column = this.mergedConfig().columns.find(c => c.key === columnKey);
|
|
10018
|
+
if (!column)
|
|
10019
|
+
return;
|
|
10020
|
+
const valueGetter = column.dataValueGetter || column.valueGetter || column.key;
|
|
10021
|
+
const groups = new Map();
|
|
10022
|
+
data.forEach(row => {
|
|
10023
|
+
let rawValue;
|
|
10024
|
+
if (valueGetter.includes('.') || valueGetter !== column.key) {
|
|
10025
|
+
rawValue = this.getNestedValue(row, valueGetter);
|
|
10026
|
+
}
|
|
10027
|
+
else {
|
|
10028
|
+
rawValue = row[column.key];
|
|
10029
|
+
}
|
|
10030
|
+
const groupKey = `${columnKey}:${this.getFilterValueKey(rawValue)}`;
|
|
10031
|
+
groups.set(groupKey, rawValue);
|
|
10032
|
+
});
|
|
10033
|
+
const expanded = this.expandedGroups();
|
|
10034
|
+
const newExpanded = new Set(expanded);
|
|
10035
|
+
groups.forEach((_, key) => {
|
|
10036
|
+
newExpanded.add(key);
|
|
10037
|
+
});
|
|
10038
|
+
this.expandedGroups.set(newExpanded);
|
|
10039
|
+
}
|
|
9893
10040
|
onColumnHide(column) {
|
|
9894
10041
|
const hidden = this.hiddenColumns();
|
|
9895
10042
|
if (!hidden.includes(column.key)) {
|
|
@@ -9914,6 +10061,8 @@ class CideEleDataGridComponent {
|
|
|
9914
10061
|
this.hiddenColumns.set([]);
|
|
9915
10062
|
this.columnFilters.set([]);
|
|
9916
10063
|
this.columnSorts.set([]);
|
|
10064
|
+
// Reapply filters and sorting (which will be empty now)
|
|
10065
|
+
this.applyFilters();
|
|
9917
10066
|
this.emitEvent('columnReset', null);
|
|
9918
10067
|
this.closeColumnMenu();
|
|
9919
10068
|
}
|
|
@@ -9922,15 +10071,45 @@ class CideEleDataGridComponent {
|
|
|
9922
10071
|
this.closeColumnMenu();
|
|
9923
10072
|
}
|
|
9924
10073
|
onManageColumns() {
|
|
9925
|
-
this.
|
|
10074
|
+
this.showManageColumnsModal.set(true);
|
|
9926
10075
|
this.closeColumnMenu();
|
|
10076
|
+
this.emitEvent('manageColumns', null);
|
|
10077
|
+
}
|
|
10078
|
+
closeManageColumnsModal() {
|
|
10079
|
+
this.showManageColumnsModal.set(false);
|
|
10080
|
+
}
|
|
10081
|
+
toggleColumnVisibility(columnKey) {
|
|
10082
|
+
const hidden = this.hiddenColumns();
|
|
10083
|
+
const index = hidden.indexOf(columnKey);
|
|
10084
|
+
if (index !== -1) {
|
|
10085
|
+
// Show column
|
|
10086
|
+
hidden.splice(index, 1);
|
|
10087
|
+
this.hiddenColumns.set([...hidden]);
|
|
10088
|
+
const column = this.mergedConfig().columns.find(c => c.key === columnKey);
|
|
10089
|
+
if (column) {
|
|
10090
|
+
this.emitEvent('columnShow', columnKey, column);
|
|
10091
|
+
}
|
|
10092
|
+
}
|
|
10093
|
+
else {
|
|
10094
|
+
// Hide column
|
|
10095
|
+
this.hiddenColumns.set([...hidden, columnKey]);
|
|
10096
|
+
const column = this.mergedConfig().columns.find(c => c.key === columnKey);
|
|
10097
|
+
if (column) {
|
|
10098
|
+
this.emitEvent('columnHide', columnKey, column);
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
isColumnHidden(columnKey) {
|
|
10103
|
+
return this.hiddenColumns().includes(columnKey);
|
|
9927
10104
|
}
|
|
9928
10105
|
getColumnFilterValue(columnKey) {
|
|
9929
10106
|
const filter = this.columnFilters().find(f => f.columnKey === columnKey);
|
|
9930
10107
|
return filter?.value;
|
|
9931
10108
|
}
|
|
9932
10109
|
getColumnSortDirection(columnKey) {
|
|
9933
|
-
|
|
10110
|
+
// Access the signal to ensure reactivity
|
|
10111
|
+
const sorts = this.columnSorts();
|
|
10112
|
+
const sort = sorts.find(s => s.columnKey === columnKey);
|
|
9934
10113
|
return sort?.direction || null;
|
|
9935
10114
|
}
|
|
9936
10115
|
isColumnFiltered(columnKey) {
|
|
@@ -9963,11 +10142,25 @@ class CideEleDataGridComponent {
|
|
|
9963
10142
|
const values = new Map();
|
|
9964
10143
|
// Count occurrences of each value
|
|
9965
10144
|
data.forEach(row => {
|
|
9966
|
-
|
|
9967
|
-
|
|
10145
|
+
// Use dataValueGetter if available (for filtering/export), otherwise use valueGetter or column.key
|
|
10146
|
+
const valueGetter = column.dataValueGetter || column.valueGetter || column.key;
|
|
10147
|
+
let rawValue = valueGetter.includes('.') || valueGetter !== column.key
|
|
10148
|
+
? this.getNestedValue(row, valueGetter)
|
|
9968
10149
|
: row[column.key];
|
|
9969
|
-
|
|
9970
|
-
|
|
10150
|
+
// For template/custom columns, if value is undefined/null, try to extract from row
|
|
10151
|
+
// This handles cases where column.key doesn't match actual data fields
|
|
10152
|
+
if ((rawValue === undefined || rawValue === null) && column.type === 'custom' && !column.dataValueGetter && !column.valueGetter) {
|
|
10153
|
+
rawValue = this.extractValueFromRowForTemplateColumn(row, column);
|
|
10154
|
+
}
|
|
10155
|
+
// Create a stable key for comparison (handle objects properly)
|
|
10156
|
+
const valueKey = this.getFilterValueKey(rawValue);
|
|
10157
|
+
const existing = values.get(valueKey);
|
|
10158
|
+
if (existing) {
|
|
10159
|
+
existing.count++;
|
|
10160
|
+
}
|
|
10161
|
+
else {
|
|
10162
|
+
values.set(valueKey, { value: rawValue, count: 1 });
|
|
10163
|
+
}
|
|
9971
10164
|
});
|
|
9972
10165
|
// Get active filter for this column
|
|
9973
10166
|
const activeFilter = this.columnFilters().find(f => f.columnKey === column.key);
|
|
@@ -9975,13 +10168,20 @@ class CideEleDataGridComponent {
|
|
|
9975
10168
|
? activeFilter.value
|
|
9976
10169
|
: [];
|
|
9977
10170
|
// Convert to array and sort
|
|
9978
|
-
const result = Array.from(values.
|
|
9979
|
-
.map((
|
|
9980
|
-
value,
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
10171
|
+
const result = Array.from(values.values())
|
|
10172
|
+
.map(({ value, count }) => {
|
|
10173
|
+
const label = this.formatFilterValue(value, column);
|
|
10174
|
+
// Check if this value is selected by comparing the value or its key
|
|
10175
|
+
const valueKey = this.getFilterValueKey(value);
|
|
10176
|
+
const isChecked = activeValues.length === 0 ||
|
|
10177
|
+
activeValues.some(av => this.getFilterValueKey(av) === valueKey);
|
|
10178
|
+
return {
|
|
10179
|
+
value,
|
|
10180
|
+
label,
|
|
10181
|
+
count,
|
|
10182
|
+
checked: isChecked
|
|
10183
|
+
};
|
|
10184
|
+
})
|
|
9985
10185
|
.sort((a, b) => {
|
|
9986
10186
|
// Sort by label
|
|
9987
10187
|
if (a.label < b.label)
|
|
@@ -9997,6 +10197,78 @@ class CideEleDataGridComponent {
|
|
|
9997
10197
|
}
|
|
9998
10198
|
return result;
|
|
9999
10199
|
}
|
|
10200
|
+
/**
|
|
10201
|
+
* Extract value from row for template columns when column.key doesn't exist
|
|
10202
|
+
* Tries common field patterns based on column key
|
|
10203
|
+
*/
|
|
10204
|
+
extractValueFromRowForTemplateColumn(row, column) {
|
|
10205
|
+
// Try common patterns based on column key
|
|
10206
|
+
const rowData = row;
|
|
10207
|
+
// If column key is 'details', try common detail/display fields
|
|
10208
|
+
if (column.key === 'details' || column.key.includes('detail')) {
|
|
10209
|
+
// Try display_text, name, title fields
|
|
10210
|
+
if (rowData['fdlds_display_text'])
|
|
10211
|
+
return rowData['fdlds_display_text'];
|
|
10212
|
+
if (rowData['display_text'])
|
|
10213
|
+
return rowData['display_text'];
|
|
10214
|
+
if (rowData['name'])
|
|
10215
|
+
return rowData['name'];
|
|
10216
|
+
if (rowData['title'])
|
|
10217
|
+
return rowData['title'];
|
|
10218
|
+
}
|
|
10219
|
+
// Try to find a field that matches the column key pattern
|
|
10220
|
+
// For example, if key is 'details', try 'detail', 'detail_text', etc.
|
|
10221
|
+
const keyVariations = [
|
|
10222
|
+
column.key,
|
|
10223
|
+
`${column.key}_text`,
|
|
10224
|
+
`${column.key}_name`,
|
|
10225
|
+
`${column.key}_title`,
|
|
10226
|
+
`${column.key}_display`,
|
|
10227
|
+
`fdlds_${column.key}`,
|
|
10228
|
+
`${column.key.replace('details', 'display_text')}`
|
|
10229
|
+
];
|
|
10230
|
+
for (const variation of keyVariations) {
|
|
10231
|
+
if (rowData[variation] !== undefined && rowData[variation] !== null) {
|
|
10232
|
+
return rowData[variation];
|
|
10233
|
+
}
|
|
10234
|
+
}
|
|
10235
|
+
// Try to find any string field in the row that might be a display value
|
|
10236
|
+
for (const key in rowData) {
|
|
10237
|
+
if (rowData.hasOwnProperty(key)) {
|
|
10238
|
+
const value = rowData[key];
|
|
10239
|
+
if (value !== null && value !== undefined && typeof value === 'string' && value.trim() !== '') {
|
|
10240
|
+
// Skip common ID fields
|
|
10241
|
+
if (!key.includes('_id') && !key.includes('id_')) {
|
|
10242
|
+
return value;
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
}
|
|
10246
|
+
}
|
|
10247
|
+
return undefined;
|
|
10248
|
+
}
|
|
10249
|
+
/**
|
|
10250
|
+
* Get a stable key for filter value comparison
|
|
10251
|
+
* Handles objects by using _id or JSON stringification
|
|
10252
|
+
*/
|
|
10253
|
+
getFilterValueKey(value) {
|
|
10254
|
+
if (value === null || value === undefined)
|
|
10255
|
+
return 'null';
|
|
10256
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
10257
|
+
const obj = value;
|
|
10258
|
+
// Use _id if available for object comparison
|
|
10259
|
+
if ('_id' in obj && obj['_id'] !== undefined) {
|
|
10260
|
+
return `obj_${String(obj['_id'])}`;
|
|
10261
|
+
}
|
|
10262
|
+
// Otherwise use JSON string for comparison
|
|
10263
|
+
try {
|
|
10264
|
+
return `obj_${JSON.stringify(value)}`;
|
|
10265
|
+
}
|
|
10266
|
+
catch {
|
|
10267
|
+
return `obj_${String(value)}`;
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
return String(value);
|
|
10271
|
+
}
|
|
10000
10272
|
formatFilterValue(value, column) {
|
|
10001
10273
|
if (value === null || value === undefined)
|
|
10002
10274
|
return '(Blank)';
|
|
@@ -10004,6 +10276,39 @@ class CideEleDataGridComponent {
|
|
|
10004
10276
|
return value ? 'Yes' : 'No';
|
|
10005
10277
|
if (value instanceof Date)
|
|
10006
10278
|
return value.toLocaleDateString();
|
|
10279
|
+
// Handle objects - try to extract display text
|
|
10280
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
10281
|
+
const obj = value;
|
|
10282
|
+
// Try common display property names in order of preference
|
|
10283
|
+
const displayProperties = [
|
|
10284
|
+
'name', 'title', 'label', 'text', 'display_text',
|
|
10285
|
+
'user_fullname', 'fullname', 'full_name',
|
|
10286
|
+
'sygms_title', 'sygmt_title',
|
|
10287
|
+
'user_name', 'username',
|
|
10288
|
+
'email', 'description'
|
|
10289
|
+
];
|
|
10290
|
+
for (const prop of displayProperties) {
|
|
10291
|
+
if (obj[prop] !== undefined && obj[prop] !== null && typeof obj[prop] !== 'object') {
|
|
10292
|
+
return String(obj[prop]);
|
|
10293
|
+
}
|
|
10294
|
+
}
|
|
10295
|
+
// If no display property found, try to get first string property
|
|
10296
|
+
for (const key in obj) {
|
|
10297
|
+
if (obj.hasOwnProperty(key) && typeof obj[key] === 'string' && obj[key] !== '') {
|
|
10298
|
+
return String(obj[key]);
|
|
10299
|
+
}
|
|
10300
|
+
}
|
|
10301
|
+
// Last resort: use _id if available
|
|
10302
|
+
if ('_id' in obj && obj['_id'] !== undefined) {
|
|
10303
|
+
return String(obj['_id']);
|
|
10304
|
+
}
|
|
10305
|
+
// If still no value, return a generic label
|
|
10306
|
+
return '(Object)';
|
|
10307
|
+
}
|
|
10308
|
+
// Handle arrays
|
|
10309
|
+
if (Array.isArray(value)) {
|
|
10310
|
+
return value.length > 0 ? `${value.length} items` : '(Empty Array)';
|
|
10311
|
+
}
|
|
10007
10312
|
return String(value);
|
|
10008
10313
|
}
|
|
10009
10314
|
toggleFilterValue(column, value, checked) {
|
|
@@ -10051,6 +10356,8 @@ class CideEleDataGridComponent {
|
|
|
10051
10356
|
this.columnFilters.set([...filters]);
|
|
10052
10357
|
this.emitEvent('columnFilter', filters, column);
|
|
10053
10358
|
this.applyFiltersToData();
|
|
10359
|
+
// Reapply sorting after filtering
|
|
10360
|
+
this.applySortsToData();
|
|
10054
10361
|
}
|
|
10055
10362
|
selectAllFilterValues(column) {
|
|
10056
10363
|
const filters = this.columnFilters();
|
|
@@ -10060,6 +10367,8 @@ class CideEleDataGridComponent {
|
|
|
10060
10367
|
this.columnFilters.set([...filters]);
|
|
10061
10368
|
this.emitEvent('columnFilter', filters, column);
|
|
10062
10369
|
this.applyFiltersToData();
|
|
10370
|
+
// Reapply sorting after filtering
|
|
10371
|
+
this.applySortsToData();
|
|
10063
10372
|
}
|
|
10064
10373
|
}
|
|
10065
10374
|
deselectAllFilterValues(column) {
|
|
@@ -10079,20 +10388,34 @@ class CideEleDataGridComponent {
|
|
|
10079
10388
|
this.columnFilters.set([...filters]);
|
|
10080
10389
|
this.emitEvent('columnFilter', filters, column);
|
|
10081
10390
|
this.applyFiltersToData();
|
|
10391
|
+
// Reapply sorting after filtering
|
|
10392
|
+
this.applySortsToData();
|
|
10082
10393
|
}
|
|
10083
|
-
applyFiltersToData() {
|
|
10084
|
-
let filtered = [...this.internalData()];
|
|
10394
|
+
applyFiltersToData(data) {
|
|
10395
|
+
let filtered = data ? [...data] : [...this.internalData()];
|
|
10085
10396
|
const filters = this.columnFilters();
|
|
10086
10397
|
filters.forEach(filter => {
|
|
10087
10398
|
const column = this.mergedConfig().columns.find(c => c.key === filter.columnKey);
|
|
10088
10399
|
if (!column)
|
|
10089
10400
|
return;
|
|
10090
10401
|
filtered = filtered.filter(row => {
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10402
|
+
// Use dataValueGetter if available (for filtering/export), otherwise use valueGetter or column.key
|
|
10403
|
+
const valueGetter = column.dataValueGetter || column.valueGetter || column.key;
|
|
10404
|
+
let rawValue;
|
|
10405
|
+
if (valueGetter.includes('.') || valueGetter !== column.key) {
|
|
10406
|
+
rawValue = this.getNestedValue(row, valueGetter);
|
|
10407
|
+
}
|
|
10408
|
+
else {
|
|
10409
|
+
rawValue = row[column.key];
|
|
10410
|
+
}
|
|
10411
|
+
// For template/custom columns, if value is undefined/null, try to extract from row
|
|
10412
|
+
if ((rawValue === undefined || rawValue === null) && column.type === 'custom' && !column.dataValueGetter && !column.valueGetter) {
|
|
10413
|
+
rawValue = this.extractValueFromRowForTemplateColumn(row, column);
|
|
10414
|
+
}
|
|
10094
10415
|
if (filter.operator === 'in' && Array.isArray(filter.value)) {
|
|
10095
|
-
|
|
10416
|
+
// Compare using filter value keys for proper object comparison
|
|
10417
|
+
const valueKey = this.getFilterValueKey(rawValue);
|
|
10418
|
+
return filter.value.some(fv => this.getFilterValueKey(fv) === valueKey);
|
|
10096
10419
|
}
|
|
10097
10420
|
return true;
|
|
10098
10421
|
});
|
|
@@ -10249,7 +10572,12 @@ class CideEleDataGridComponent {
|
|
|
10249
10572
|
'lg': 'tw-w-48',
|
|
10250
10573
|
'xl': 'tw-w-64'
|
|
10251
10574
|
};
|
|
10252
|
-
return
|
|
10575
|
+
// If it's a predefined size, return the class
|
|
10576
|
+
if (widthMap[width]) {
|
|
10577
|
+
return widthMap[width];
|
|
10578
|
+
}
|
|
10579
|
+
// For custom pixel values, return empty class (we'll use inline style instead)
|
|
10580
|
+
return '';
|
|
10253
10581
|
}
|
|
10254
10582
|
getColumnMaxWidthClass(width) {
|
|
10255
10583
|
if (!width || width === 'auto')
|
|
@@ -10261,7 +10589,34 @@ class CideEleDataGridComponent {
|
|
|
10261
10589
|
'lg': 'tw-max-w-48',
|
|
10262
10590
|
'xl': 'tw-max-w-64'
|
|
10263
10591
|
};
|
|
10264
|
-
return
|
|
10592
|
+
// If it's a predefined size, return the class
|
|
10593
|
+
if (maxWidthMap[width]) {
|
|
10594
|
+
return maxWidthMap[width];
|
|
10595
|
+
}
|
|
10596
|
+
// For custom pixel values, return empty class (we'll use inline style instead)
|
|
10597
|
+
return '';
|
|
10598
|
+
}
|
|
10599
|
+
getColumnWidthStyle(width) {
|
|
10600
|
+
if (!width || width === 'auto')
|
|
10601
|
+
return null;
|
|
10602
|
+
const predefinedSizes = ['xs', 'sm', 'md', 'lg', 'xl'];
|
|
10603
|
+
// If it's a predefined size, return null (use class instead)
|
|
10604
|
+
if (predefinedSizes.includes(width)) {
|
|
10605
|
+
return null;
|
|
10606
|
+
}
|
|
10607
|
+
// For custom pixel/rem/em values, return the width as inline style
|
|
10608
|
+
return width;
|
|
10609
|
+
}
|
|
10610
|
+
getColumnMaxWidthStyle(width) {
|
|
10611
|
+
if (!width || width === 'auto')
|
|
10612
|
+
return null;
|
|
10613
|
+
const predefinedSizes = ['xs', 'sm', 'md', 'lg', 'xl'];
|
|
10614
|
+
// If it's a predefined size, return null (use class instead)
|
|
10615
|
+
if (predefinedSizes.includes(width)) {
|
|
10616
|
+
return null;
|
|
10617
|
+
}
|
|
10618
|
+
// For custom pixel/rem/em values, return the width as inline style
|
|
10619
|
+
return width;
|
|
10265
10620
|
}
|
|
10266
10621
|
getStatusClass(value, statusConfig) {
|
|
10267
10622
|
if (!statusConfig)
|
|
@@ -10310,6 +10665,11 @@ class CideEleDataGridComponent {
|
|
|
10310
10665
|
return this.localReorderedData;
|
|
10311
10666
|
}
|
|
10312
10667
|
const data = this.filteredData();
|
|
10668
|
+
// Apply grouping if any columns are grouped
|
|
10669
|
+
const grouped = this.groupedColumns();
|
|
10670
|
+
if (grouped.length > 0 && !this.serverSidePagination) {
|
|
10671
|
+
return this.getGroupedData(data, grouped);
|
|
10672
|
+
}
|
|
10313
10673
|
// For server-side pagination, the server already provides the correct page of data
|
|
10314
10674
|
// For client-side pagination, we need to slice the data
|
|
10315
10675
|
if (this.serverSidePagination) {
|
|
@@ -10326,6 +10686,99 @@ class CideEleDataGridComponent {
|
|
|
10326
10686
|
return data.slice(start, end);
|
|
10327
10687
|
}
|
|
10328
10688
|
}
|
|
10689
|
+
/**
|
|
10690
|
+
* Group data by specified columns
|
|
10691
|
+
*/
|
|
10692
|
+
getGroupedData(data, groupColumns) {
|
|
10693
|
+
if (groupColumns.length === 0)
|
|
10694
|
+
return data;
|
|
10695
|
+
// Group by the first column only (single-level grouping for now)
|
|
10696
|
+
const groupColumnKey = groupColumns[0];
|
|
10697
|
+
const column = this.mergedConfig().columns.find(c => c.key === groupColumnKey);
|
|
10698
|
+
if (!column)
|
|
10699
|
+
return data;
|
|
10700
|
+
const valueGetter = column.dataValueGetter || column.valueGetter || column.key;
|
|
10701
|
+
const groups = new Map();
|
|
10702
|
+
// Group the data
|
|
10703
|
+
data.forEach(row => {
|
|
10704
|
+
let rawValue;
|
|
10705
|
+
if (valueGetter.includes('.') || valueGetter !== column.key) {
|
|
10706
|
+
rawValue = this.getNestedValue(row, valueGetter);
|
|
10707
|
+
}
|
|
10708
|
+
else {
|
|
10709
|
+
rawValue = row[column.key];
|
|
10710
|
+
}
|
|
10711
|
+
// Handle objects - extract display text for grouping
|
|
10712
|
+
let displayValue = rawValue;
|
|
10713
|
+
if (typeof rawValue === 'object' && rawValue !== null && !Array.isArray(rawValue) && !(rawValue instanceof Date)) {
|
|
10714
|
+
const obj = rawValue;
|
|
10715
|
+
const displayProps = ['name', 'title', 'label', 'text', 'display_text', 'user_fullname', 'sygms_title'];
|
|
10716
|
+
for (const prop of displayProps) {
|
|
10717
|
+
if (obj[prop] !== undefined && obj[prop] !== null) {
|
|
10718
|
+
displayValue = obj[prop];
|
|
10719
|
+
break;
|
|
10720
|
+
}
|
|
10721
|
+
}
|
|
10722
|
+
}
|
|
10723
|
+
const groupKey = `${groupColumnKey}:${this.getFilterValueKey(rawValue)}`;
|
|
10724
|
+
const existing = groups.get(groupKey);
|
|
10725
|
+
if (existing) {
|
|
10726
|
+
existing.rows.push(row);
|
|
10727
|
+
}
|
|
10728
|
+
else {
|
|
10729
|
+
groups.set(groupKey, { value: displayValue, rows: [row] });
|
|
10730
|
+
}
|
|
10731
|
+
});
|
|
10732
|
+
// Flatten groups into display array
|
|
10733
|
+
const result = [];
|
|
10734
|
+
groups.forEach((group, groupKey) => {
|
|
10735
|
+
// Add group header row (we'll mark it with a special property)
|
|
10736
|
+
const groupHeader = {
|
|
10737
|
+
...group.rows[0],
|
|
10738
|
+
_isGroupHeader: true,
|
|
10739
|
+
_groupKey: groupKey,
|
|
10740
|
+
_groupValue: group.value,
|
|
10741
|
+
_groupColumnKey: groupColumnKey,
|
|
10742
|
+
_groupRowCount: group.rows.length
|
|
10743
|
+
};
|
|
10744
|
+
result.push(groupHeader);
|
|
10745
|
+
// Add group rows if expanded
|
|
10746
|
+
if (this.isGroupExpanded(groupKey)) {
|
|
10747
|
+
result.push(...group.rows);
|
|
10748
|
+
}
|
|
10749
|
+
});
|
|
10750
|
+
return result;
|
|
10751
|
+
}
|
|
10752
|
+
getGroupValue(columnKey) {
|
|
10753
|
+
// This is a helper to get the group value from a row
|
|
10754
|
+
return this.displayedData[0]?._groupValue;
|
|
10755
|
+
}
|
|
10756
|
+
isGroupHeader(row) {
|
|
10757
|
+
return !!row?._isGroupHeader;
|
|
10758
|
+
}
|
|
10759
|
+
getGroupRowCount(row) {
|
|
10760
|
+
return row?._groupRowCount || 0;
|
|
10761
|
+
}
|
|
10762
|
+
getGroupColumnKey(row) {
|
|
10763
|
+
return row?._groupColumnKey || '';
|
|
10764
|
+
}
|
|
10765
|
+
getGroupKey(item) {
|
|
10766
|
+
const groupColumnKey = this.getGroupColumnKey(item);
|
|
10767
|
+
const groupValue = item?._groupValue;
|
|
10768
|
+
return `${groupColumnKey}:${this.getFilterValueKey(groupValue)}`;
|
|
10769
|
+
}
|
|
10770
|
+
getGroupColumn(item) {
|
|
10771
|
+
const groupColumnKey = this.getGroupColumnKey(item);
|
|
10772
|
+
return this.mergedConfig().columns.find(c => c.key === groupColumnKey) || null;
|
|
10773
|
+
}
|
|
10774
|
+
getGroupValueDisplay(item) {
|
|
10775
|
+
const groupValue = item?._groupValue;
|
|
10776
|
+
const groupCol = this.getGroupColumn(item);
|
|
10777
|
+
if (groupCol) {
|
|
10778
|
+
return this.formatFilterValue(groupValue, groupCol);
|
|
10779
|
+
}
|
|
10780
|
+
return String(groupValue || '');
|
|
10781
|
+
}
|
|
10329
10782
|
get columns() {
|
|
10330
10783
|
const baseColumns = this.mergedConfig().columns || [];
|
|
10331
10784
|
return baseColumns;
|
|
@@ -10373,6 +10826,8 @@ class CideEleDataGridComponent {
|
|
|
10373
10826
|
clearAllFilters() {
|
|
10374
10827
|
this.columnFilters.set([]);
|
|
10375
10828
|
this.applyFiltersToData();
|
|
10829
|
+
// Reapply sorting after clearing filters
|
|
10830
|
+
this.applySortsToData();
|
|
10376
10831
|
this.emitEvent('columnFilter', [], undefined);
|
|
10377
10832
|
}
|
|
10378
10833
|
/**
|
|
@@ -10390,6 +10845,8 @@ class CideEleDataGridComponent {
|
|
|
10390
10845
|
const filteredFilters = filters.filter(f => f.columnKey !== columnKey);
|
|
10391
10846
|
this.columnFilters.set(filteredFilters);
|
|
10392
10847
|
this.applyFiltersToData();
|
|
10848
|
+
// Reapply sorting after removing filter
|
|
10849
|
+
this.applySortsToData();
|
|
10393
10850
|
this.emitEvent('columnFilter', filteredFilters, undefined);
|
|
10394
10851
|
}
|
|
10395
10852
|
/**
|
|
@@ -10410,16 +10867,22 @@ class CideEleDataGridComponent {
|
|
|
10410
10867
|
exportData(format) {
|
|
10411
10868
|
const data = this.filteredData();
|
|
10412
10869
|
const visibleCols = this.visibleColumns();
|
|
10413
|
-
//
|
|
10414
|
-
const
|
|
10870
|
+
// Filter out actions columns from export
|
|
10871
|
+
const exportableCols = visibleCols.filter(col => col.type !== 'actions' && col.key !== 'actions');
|
|
10872
|
+
// Build columns array with visible columns only (excluding actions)
|
|
10873
|
+
const columns = exportableCols.map(col => ({
|
|
10415
10874
|
key: col.key,
|
|
10416
10875
|
label: col.header || col.key
|
|
10417
10876
|
}));
|
|
10418
|
-
// Build data array with only visible column values
|
|
10877
|
+
// Build data array with only visible column values (excluding actions)
|
|
10419
10878
|
const exportData = data.map(row => {
|
|
10420
10879
|
const exportRow = {};
|
|
10421
|
-
|
|
10422
|
-
|
|
10880
|
+
exportableCols.forEach(col => {
|
|
10881
|
+
// Use dataValueGetter if available (for filtering/export), otherwise use valueGetter or column.key
|
|
10882
|
+
const valueGetter = col.dataValueGetter || col.valueGetter || col.key;
|
|
10883
|
+
const rawValue = this.getNestedValue(row, valueGetter);
|
|
10884
|
+
// Format the value for export (extract display text from objects)
|
|
10885
|
+
exportRow[col.key] = this.formatExportValue(rawValue, col);
|
|
10423
10886
|
});
|
|
10424
10887
|
return exportRow;
|
|
10425
10888
|
});
|
|
@@ -10438,8 +10901,59 @@ class CideEleDataGridComponent {
|
|
|
10438
10901
|
}
|
|
10439
10902
|
this.closeExportMenu();
|
|
10440
10903
|
}
|
|
10904
|
+
/**
|
|
10905
|
+
* Format value for export - extracts display text from objects
|
|
10906
|
+
*/
|
|
10907
|
+
formatExportValue(value, column) {
|
|
10908
|
+
if (value === null || value === undefined)
|
|
10909
|
+
return '';
|
|
10910
|
+
// Handle objects - extract display text
|
|
10911
|
+
if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
|
|
10912
|
+
const obj = value;
|
|
10913
|
+
// Try common display properties
|
|
10914
|
+
const displayProperties = [
|
|
10915
|
+
'name', 'title', 'label', 'text', 'display_text',
|
|
10916
|
+
'user_fullname', 'fullname', 'full_name',
|
|
10917
|
+
'sygms_title', 'sygmt_title',
|
|
10918
|
+
'user_name', 'username',
|
|
10919
|
+
'email', 'description'
|
|
10920
|
+
];
|
|
10921
|
+
for (const prop of displayProperties) {
|
|
10922
|
+
if (obj[prop] !== undefined && obj[prop] !== null && typeof obj[prop] !== 'object') {
|
|
10923
|
+
return String(obj[prop]);
|
|
10924
|
+
}
|
|
10925
|
+
}
|
|
10926
|
+
// Try first string property
|
|
10927
|
+
for (const key in obj) {
|
|
10928
|
+
if (obj.hasOwnProperty(key) && typeof obj[key] === 'string' && obj[key] !== '') {
|
|
10929
|
+
if (!key.includes('_id') && !key.includes('id_')) {
|
|
10930
|
+
return String(obj[key]);
|
|
10931
|
+
}
|
|
10932
|
+
}
|
|
10933
|
+
}
|
|
10934
|
+
// Use _id as fallback
|
|
10935
|
+
if ('_id' in obj && obj['_id'] !== undefined) {
|
|
10936
|
+
return String(obj['_id']);
|
|
10937
|
+
}
|
|
10938
|
+
return '';
|
|
10939
|
+
}
|
|
10940
|
+
// Handle arrays
|
|
10941
|
+
if (Array.isArray(value)) {
|
|
10942
|
+
return value.length > 0 ? `${value.length} items` : '';
|
|
10943
|
+
}
|
|
10944
|
+
// Handle dates
|
|
10945
|
+
if (value instanceof Date) {
|
|
10946
|
+
return value.toLocaleString();
|
|
10947
|
+
}
|
|
10948
|
+
// Handle booleans
|
|
10949
|
+
if (typeof value === 'boolean') {
|
|
10950
|
+
return value ? 'Yes' : 'No';
|
|
10951
|
+
}
|
|
10952
|
+
// Return string representation
|
|
10953
|
+
return String(value);
|
|
10954
|
+
}
|
|
10441
10955
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleDataGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10442
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleDataGridComponent, isStandalone: true, selector: "cide-ele-data-grid", inputs: { config: "config", templateRenderers: "templateRenderers", customFormatters: "customFormatters", actionHandlers: "actionHandlers", serverSidePagination: "serverSidePagination", totalServerItems: "totalServerItems", currentServerPage: "currentServerPage", currentServerPageSize: "currentServerPageSize", dragDropEnabled: "dragDropEnabled" }, outputs: { gridEvent: "gridEvent" }, usesOnChanges: true, ngImport: i0, template: " <!-- Data Grid Component -->\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \n [ngClass]=\"[\n mergedConfig().tableClass || '',\n mergedConfig().fullHeight ? 'tw-h-full' : '',\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\n isTreeEnabled() ? 'tree-enabled' : ''\n ]\">\n \n <!-- Header Section -->\n @if (mergedConfig().title || mergedConfig().subtitle) {\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\n @if (mergedConfig().title) {\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\n {{ mergedConfig().title }}\n </h3>\n }\n @if (mergedConfig().subtitle) {\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\n {{ mergedConfig().subtitle }}\n </p>\n }\n </div>\n }\n\n <!-- Search Section -->\n @if (searchConfig.enabled) {\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\n <div class=\"tw-flex tw-items-center tw-justify-between\">\n <!-- Left Side: Search Input and Action Icons -->\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\n <!-- Search Input - Apple Style -->\n <div class=\"tw-max-w-md data-grid-search-input\">\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"updateSearchQuery($event)\"\n [placeholder]=\"searchConfig.placeholder\"\n [disabled]=\"loading() || isRefreshing()\"\n leadingIcon=\"search\"\n fill=\"outline\"\n size=\"sm\">\n </cide-ele-input>\n </div>\n \n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\n <!-- Filter Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-blue-600 hover:tw-bg-blue-50 tw-transition-all tw-duration-200 tw-relative\"\n [class.tw-text-blue-600]=\"columnFilters().length > 0\"\n [class.tw-bg-blue-50]=\"columnFilters().length > 0\"\n title=\"Filter\"\n (click)=\"toggleFilterDropdown()\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\n @if (columnFilters().length > 0) {\n <span class=\"tw-absolute -tw-top-1 -tw-right-1 tw-inline-flex tw-items-center tw-justify-center tw-w-4 tw-h-4 tw-text-xs tw-font-semibold tw-text-white tw-bg-blue-600 tw-rounded-full tw-border tw-border-white\">\n {{ columnFilters().length }}\n </span>\n }\n </button>\n \n <!-- Filter Dropdown Menu -->\n @if (showFilterDropdown()) {\n <div class=\"tw-absolute tw-left-0 tw-mt-2 tw-w-80 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200 tw-flex tw-items-center tw-justify-between\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Active Filters</span>\n @if (columnFilters().length > 0) {\n <button type=\"button\" \n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-800 tw-font-medium\"\n (click)=\"clearAllFilters()\">\n Clear All\n </button>\n }\n </div>\n @if (columnFilters().length === 0) {\n <div class=\"tw-px-4 tw-py-6 tw-text-center\">\n <cide-ele-icon class=\"tw-w-12 tw-h-12 tw-text-gray-300 tw-mx-auto tw-mb-2\">filter_list</cide-ele-icon>\n <p class=\"tw-text-sm tw-text-gray-500\">No filters applied</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Use column filters to narrow down your results</p>\n </div>\n } @else {\n <div class=\"tw-max-h-96 tw-overflow-y-auto\">\n @for (filter of columnFilters(); track filter.columnKey) {\n <div class=\"tw-px-4 tw-py-3 hover:tw-bg-gray-50 tw-border-b tw-border-gray-100 last:tw-border-b-0\">\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-2\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <div class=\"tw-text-xs tw-font-semibold tw-text-gray-600 tw-mb-1\">{{ getColumnHeader(filter.columnKey) }}</div>\n <div class=\"tw-text-sm tw-text-gray-700 tw-flex tw-items-center tw-gap-1\">\n @if (filter.operator === 'in' && isArray(filter.value)) {\n <span class=\"tw-inline-flex tw-items-center tw-gap-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3\">check_circle</cide-ele-icon>\n {{ getArrayLength(filter.value) }} selected\n </span>\n } @else {\n <span>{{ $any(filter.value) }}</span>\n }\n </div>\n </div>\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-400 hover:tw-text-red-600 hover:tw-bg-red-50 tw-transition-colors\"\n (click)=\"removeFilter(filter.columnKey)\"\n title=\"Remove filter\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n \n <!-- Download/Export Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\n title=\"Export\"\n (click)=\"toggleExportMenu($event)\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\n </button>\n \n <!-- Export Dropdown Menu - Improved Design -->\n @if (showExportMenu()) {\n <div class=\"tw-absolute tw-right-0 tw-mt-2 tw-w-56 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Export Options</span>\n </div>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-green-50 hover:tw-text-green-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('csv')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-green-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-green-600\">description</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">CSV</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Comma separated values</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-blue-50 hover:tw-text-blue-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('excel')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">table_chart</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">Excel</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Microsoft Excel format</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-red-50 hover:tw-text-red-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('pdf')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">PDF</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Portable document format</div>\n </div>\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n \n <!-- Right Side: Drag Order Actions -->\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <button cideEleButton \n variant=\"outline\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\n Reset Order\n </button>\n <button cideEleButton \n variant=\"primary\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\n Save Order\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Table Section -->\n <div class=\"tw-overflow-x-auto tw-relative\"\n [ngClass]=\"{\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\n 'tw-overflow-y-auto': scrollConfig?.enabled,\n 'tw-max-h-full': scrollConfig?.enabled\n }\"\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\n [class.empty-table]=\"displayedData.length === 0\"\n [ngClass]=\"{\n 'tw-table-striped': mergedConfig().striped,\n 'tw-border': mergedConfig().bordered,\n 'tw-table-sm': mergedConfig().compact\n }\"\n style=\"table-layout: fixed;\">\n \n <!-- Table Header -->\n <thead class=\"tw-bg-gray-50\" \n [ngClass]=\"[\n mergedConfig().headerClass || '',\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\n ]\">\n <tr>\n @for (column of visibleColumns(); track column.key) {\n <th\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : ''\n ]\"\n [title]=\"column.header\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\n <span class=\"tw-truncate tw-flex-1\">{{ column.header }}</span>\n \n <!-- Active Filter Indicator -->\n @if (isColumnFiltered(column.key)) {\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\n {{ getActiveFilterCount(column.key) }}\n </div>\n }\n \n <!-- Column Menu Trigger (Three Dots Icon) -->\n @if (mergedConfig().columnMenu?.enabled) {\n <button\n type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\n (click)=\"toggleColumnMenu(column.key, $event)\"\n title=\"Column options\">\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\n </button>\n }\n </div>\n\n <!-- Column Menu Dropdown -->\n @if (isColumnMenuOpen(column.key)) {\n <div class=\"column-menu-dropdown tw-absolute tw-z-50 tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\n <div class=\"tw-py-1\">\n <!-- Sort Options -->\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnSort(column, 'asc')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\n Ascending\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnSort(column, 'desc')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\n Descending\n </button>\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Filter Option -->\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\n <!-- Check if there's a custom filter renderer template -->\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\n <div class=\"tw-px-4 tw-py-2\">\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\n </ng-container>\n </div>\n } @else {\n <!-- Excel-style Filter Button -->\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\n (click)=\"toggleFilterPanel(column.key, $event)\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\n Filter\n </div>\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\n </cide-ele-icon>\n </button>\n \n <!-- Excel-style Filter Panel -->\n @if (isFilterPanelOpen(column.key)) {\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\n <!-- Search box -->\n <div class=\"tw-px-2 tw-mb-2\">\n <input\n type=\"text\"\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\n placeholder=\"Search...\"\n [(ngModel)]=\"filterSearchTerm\"\n (click)=\"$event.stopPropagation()\">\n </div>\n \n <!-- Select All / Deselect All -->\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\n Select All\n </button>\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\n Clear\n </button>\n </div>\n \n <!-- Filter values list -->\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\n @for (item of getUniqueColumnValues(column); track item.value) {\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\n <input\n type=\"checkbox\"\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\n [checked]=\"item.checked\"\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\n (click)=\"$event.stopPropagation()\">\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\n </label>\n }\n </div>\n </div>\n }\n }\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Autosize Option -->\n @if (mergedConfig().columnMenu?.showAutosize) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAutosize(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\n Autosize\n </button>\n }\n\n <!-- Group By Column Option -->\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnGroupBy(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">group_work</cide-ele-icon>\n Group By Column\n </button>\n }\n\n <!-- Manage Columns Option -->\n @if (mergedConfig().columnMenu?.showManageColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onManageColumns()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\n Manage Columns\n </button>\n }\n\n <!-- Reset Columns Option -->\n @if (mergedConfig().columnMenu?.showResetColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnReset()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\n Reset Columns\n </button>\n }\n\n <!-- Hide Column Option -->\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnHide(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\n Hide Column\n </button>\n }\n\n <!-- Aggregation Select Option -->\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'sum')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\n Sum\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'avg')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\n Average\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'count')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\n Count\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'min')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\n Min\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'max')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\n Max\n </button>\n }\n </div>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\n @if (loading() || isRefreshing() || pageChangeLoading()) {\n <!-- Skeleton Loading Rows -->\n @for (skeletonItem of getSkeletonArray(); track $index) {\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\n @for (column of columns; track column.key) {\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width)\n ]\">\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\n </td>\n }\n </tr>\n }\n } @else {\n @for (item of displayedData; track trackByFn($index, item)) {\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\n [ngClass]=\"[\n mergedConfig().rowClass || '',\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\n isTreeEnabled() ? getTreeLevelClass(item) : ''\n ]\"\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\n (click)=\"onRowClick(item)\"\n (keydown.enter)=\"onRowClick(item)\"\n (keydown.space)=\"onRowClick(item)\"\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\n [draggable]=\"isDragDropEnabled()\"\n (dragstart)=\"onDragStart($event, item, $index)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event, item, $index)\"\n (dragend)=\"onDragEnd($event)\">\n \n @for (column of columns; track column.key) {\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n mergedConfig().cellClass || '',\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : '',\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\n ]\"\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\n [style.maxWidth]=\"getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : 'none'\"\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : '100px'\">\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\n @if (isTreeEnabled() && $index === 0) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <!-- Tree Indentation -->\n <div class=\"tw-flex tw-items-center\">\n @if (hasChildren(item)) {\n <button \n variant=\"outline\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\n <cide-ele-icon \n class=\"tw-w-3 tw-h-3\"\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\n size=\"xs\">\n chevron_right\n </cide-ele-icon>\n </button>\n } @else {\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\n </div>\n }\n </div>\n \n <!-- Cell Content -->\n <div class=\"tw-flex-1 tw-w-full\">\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n </div>\n </div>\n } @else {\n <!-- Regular cell content (non-tree or non-first column) -->\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n }\n </td>\n }\n </tr>\n }\n \n <!-- Empty State -->\n @if (displayedData.length === 0) {\n <tr class=\"tw-h-full\">\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\n </svg>\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\n @if (searchQuery()) {\n No results match your search criteria.\n } @else {\n There are no items to display.\n }\n </p>\n </div>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination Section -->\n @if (paginationConfig.enabled && totalItems() > 0) {\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\n [class.tw-opacity-60]=\"isRefreshing()\">\n \n <!-- Results Info and Page Size -->\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\n \n <!-- Results Info -->\n @if (paginationConfig.showPageInfo) {\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\n <p class=\"tw-text-sm tw-text-gray-700\">\n Showing {{ getItemRangeText() }} results\n </p>\n \n <!-- Page Size Selector -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\n <div class=\"tw-w-16 tw-relative\">\n <cide-ele-select\n [labelHide]=\"true\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"updatePageSize($event)\"\n [options]=\"getPageSizeOptions()\"\n [disabled]=\"isRefreshing()\"\n fill=\"outline\"\n size=\"xs\"\n class=\"tw-z-30\">\n </cide-ele-select>\n </div>\n </div>\n </div>\n }\n\n <!-- Pagination Controls -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n \n <!-- Previous/Next and Page Numbers -->\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n \n <!-- First Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(1)\"\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"First page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\n </button>\n \n <!-- Previous Page -->\n <button\n type=\"button\"\n (click)=\"previousPage()\"\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Previous page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\n </button>\n \n <!-- Page Numbers -->\n @for (page of getEnhancedPageNumbers(); track page) {\n @if (page === '...') {\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\n } @else {\n <button\n type=\"button\"\n (click)=\"onPageChange(page)\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n [ngClass]=\"{\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\n }\"\n [title]=\"'Page ' + page\">\n {{ page }}\n </button>\n }\n }\n \n <!-- Next Page -->\n <button\n type=\"button\"\n (click)=\"nextPage()\"\n [disabled]=\"!hasNextPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Next page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\n </button>\n \n <!-- Last Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(totalPages())\"\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Last page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\n </button>\n \n </div>\n\n <!-- Quick Jump and Refresh -->\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\n \n <!-- Quick Jump -->\n @if (paginationConfig.showQuickJump) {\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\n <div class=\"tw-w-16\">\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\n [disabled]=\"isRefreshing()\"\n size=\"xs\"\n (keydown.enter)=\"onJumpToPage()\">\n </cide-ele-input>\n </div>\n <button\n type=\"button\"\n (click)=\"onJumpToPage()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Go to page\">\n Go\n </button>\n </div>\n }\n \n <!-- Refresh Button -->\n @if (paginationConfig.showRefresh) {\n <button\n type=\"button\"\n (click)=\"onRefresh()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Refresh\">\n @if (isRefreshing()) {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\n } @else {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\n }\n </button>\n }\n </div>\n }\n \n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05)}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "size"], outputs: ["ngModelChange"] }, { kind: "component", type: CideSelectComponent, selector: "cide-ele-select", inputs: ["label", "labelHide", "placeholder", "helperText", "errorText", "required", "disabled", "id", "ngModel", "size", "fill", "labelPlacement", "labelDir", "leadingIcon", "trailingIcon", "clearInput", "options", "multiple", "searchable", "showSearchInput", "loading", "valueKey", "labelKey"], outputs: ["ngModelChange", "change", "searchChange"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton]", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }] });
|
|
10956
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleDataGridComponent, isStandalone: true, selector: "cide-ele-data-grid", inputs: { config: "config", templateRenderers: "templateRenderers", customFormatters: "customFormatters", actionHandlers: "actionHandlers", serverSidePagination: "serverSidePagination", totalServerItems: "totalServerItems", currentServerPage: "currentServerPage", currentServerPageSize: "currentServerPageSize", dragDropEnabled: "dragDropEnabled" }, outputs: { gridEvent: "gridEvent" }, usesOnChanges: true, ngImport: i0, template: " <!-- Data Grid Component -->\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \n [ngClass]=\"[\n mergedConfig().tableClass || '',\n mergedConfig().fullHeight ? 'tw-h-full' : '',\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\n isTreeEnabled() ? 'tree-enabled' : ''\n ]\">\n \n <!-- Header Section -->\n @if (mergedConfig().title || mergedConfig().subtitle) {\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\n @if (mergedConfig().title) {\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\n {{ mergedConfig().title }}\n </h3>\n }\n @if (mergedConfig().subtitle) {\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\n {{ mergedConfig().subtitle }}\n </p>\n }\n </div>\n }\n\n <!-- Search Section -->\n @if (searchConfig.enabled) {\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\n <div class=\"tw-flex tw-items-center tw-justify-between\">\n <!-- Left Side: Search Input and Action Icons -->\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\n <!-- Search Input - Apple Style -->\n <div class=\"tw-max-w-md data-grid-search-input\">\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"updateSearchQuery($event)\"\n [placeholder]=\"searchConfig.placeholder\"\n [disabled]=\"loading() || isRefreshing()\"\n leadingIcon=\"search\"\n fill=\"outline\"\n size=\"sm\">\n </cide-ele-input>\n </div>\n \n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\n <!-- Filter Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"filter-dropdown-trigger tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-blue-600 hover:tw-bg-blue-50 tw-transition-all tw-duration-200 tw-relative\"\n [class.tw-text-blue-600]=\"columnFilters().length > 0\"\n [class.tw-bg-blue-50]=\"columnFilters().length > 0\"\n title=\"Filter\"\n (click)=\"toggleFilterDropdown()\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\n @if (columnFilters().length > 0) {\n <span class=\"tw-absolute -tw-top-1 -tw-right-1 tw-inline-flex tw-items-center tw-justify-center tw-w-4 tw-h-4 tw-text-xs tw-font-semibold tw-text-white tw-bg-blue-600 tw-rounded-full tw-border tw-border-white\">\n {{ columnFilters().length }}\n </span>\n }\n </button>\n \n <!-- Filter Dropdown Menu -->\n @if (showFilterDropdown()) {\n <div class=\"filter-dropdown tw-absolute tw-left-0 tw-mt-2 tw-w-80 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200 tw-flex tw-items-center tw-justify-between\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Active Filters</span>\n @if (columnFilters().length > 0) {\n <button type=\"button\" \n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-800 tw-font-medium\"\n (click)=\"clearAllFilters()\">\n Clear All\n </button>\n }\n </div>\n @if (columnFilters().length === 0) {\n <div class=\"tw-px-4 tw-py-6 tw-text-center\">\n <cide-ele-icon class=\"tw-w-12 tw-h-12 tw-text-gray-300 tw-mx-auto tw-mb-2\">filter_list</cide-ele-icon>\n <p class=\"tw-text-sm tw-text-gray-500\">No filters applied</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Use column filters to narrow down your results</p>\n </div>\n } @else {\n <div class=\"tw-max-h-96 tw-overflow-y-auto\">\n @for (filter of columnFilters(); track filter.columnKey) {\n <div class=\"tw-px-4 tw-py-3 hover:tw-bg-gray-50 tw-border-b tw-border-gray-100 last:tw-border-b-0\">\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-2\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <div class=\"tw-text-xs tw-font-semibold tw-text-gray-600 tw-mb-1\">{{ getColumnHeader(filter.columnKey) }}</div>\n <div class=\"tw-text-sm tw-text-gray-700 tw-flex tw-items-center tw-gap-1\">\n @if (filter.operator === 'in' && isArray(filter.value)) {\n <span class=\"tw-inline-flex tw-items-center tw-gap-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3\">check_circle</cide-ele-icon>\n {{ getArrayLength(filter.value) }} selected\n </span>\n } @else {\n <span>{{ $any(filter.value) }}</span>\n }\n </div>\n </div>\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-400 hover:tw-text-red-600 hover:tw-bg-red-50 tw-transition-colors\"\n (click)=\"removeFilter(filter.columnKey)\"\n title=\"Remove filter\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n \n <!-- Download/Export Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\n title=\"Export\"\n (click)=\"toggleExportMenu($event)\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\n </button>\n \n <!-- Export Dropdown Menu - Improved Design -->\n @if (showExportMenu()) {\n <div class=\"tw-absolute tw-right-0 tw-mt-2 tw-w-56 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Export Options</span>\n </div>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-green-50 hover:tw-text-green-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('csv')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-green-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-green-600\">description</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">CSV</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Comma separated values</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-blue-50 hover:tw-text-blue-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('excel')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">table_chart</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">Excel</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Microsoft Excel format</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-red-50 hover:tw-text-red-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('pdf')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">PDF</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Portable document format</div>\n </div>\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n \n <!-- Right Side: Drag Order Actions -->\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <button cideEleButton \n variant=\"outline\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\n Reset Order\n </button>\n <button cideEleButton \n variant=\"primary\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\n Save Order\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Table Section -->\n <div class=\"tw-overflow-x-auto tw-relative\"\n [ngClass]=\"{\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\n 'tw-overflow-y-auto': scrollConfig?.enabled,\n 'tw-max-h-full': scrollConfig?.enabled\n }\"\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\n [class.empty-table]=\"displayedData.length === 0\"\n [ngClass]=\"{\n 'tw-table-striped': mergedConfig().striped,\n 'tw-border': mergedConfig().bordered,\n 'tw-table-sm': mergedConfig().compact\n }\"\n style=\"table-layout: fixed;\">\n \n <!-- Table Header -->\n <thead class=\"tw-bg-gray-50\" \n [ngClass]=\"[\n mergedConfig().headerClass || '',\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\n ]\">\n <tr>\n @for (column of visibleColumns(); track column.key) {\n <th\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : ''\n ]\"\n [style.width]=\"getColumnWidthStyle(column.width)\"\n [style.maxWidth]=\"getColumnMaxWidthStyle(column.width)\"\n [title]=\"column.header\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\n <div class=\"tw-flex tw-items-center tw-gap-1 tw-truncate tw-flex-1\">\n <span class=\"tw-truncate\">{{ column.header }}</span>\n @if (getColumnSortDirection(column.key) === 'asc') {\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-blue-600 tw-flex-shrink-0\" title=\"Sorted Ascending\">arrow_upward</cide-ele-icon>\n }\n @if (getColumnSortDirection(column.key) === 'desc') {\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-blue-600 tw-flex-shrink-0\" title=\"Sorted Descending\">arrow_downward</cide-ele-icon>\n }\n </div>\n \n <!-- Active Filter Indicator -->\n @if (isColumnFiltered(column.key)) {\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\n {{ getActiveFilterCount(column.key) }}\n </div>\n }\n \n <!-- Column Menu Trigger (Three Dots Icon) -->\n @if (mergedConfig().columnMenu?.enabled) {\n <button\n type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\n (click)=\"toggleColumnMenu(column.key, $event)\"\n title=\"Column options\">\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\n </button>\n }\n </div>\n\n <!-- Column Menu Dropdown -->\n @if (isColumnMenuOpen(column.key)) {\n <div class=\"column-menu-dropdown tw-absolute tw-z-[9999] tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\n <div class=\"tw-py-1\">\n <!-- Sort Options -->\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\n @let sortDirection = getColumnSortDirection(column.key);\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-transition-colors\"\n [class.tw-text-blue-700]=\"sortDirection === 'asc'\"\n [class.tw-bg-blue-50]=\"sortDirection === 'asc'\"\n [class.tw-text-gray-700]=\"sortDirection !== 'asc'\"\n [class.hover:tw-bg-gray-50]=\"sortDirection !== 'asc'\"\n (click)=\"onColumnSort(column, 'asc')\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3\" \n [class.tw-text-blue-600]=\"sortDirection === 'asc'\"\n [class.tw-text-gray-400]=\"sortDirection !== 'asc'\">arrow_upward</cide-ele-icon>\n Ascending\n </div>\n @if (sortDirection === 'asc') {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">check</cide-ele-icon>\n }\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-transition-colors\"\n [class.tw-text-blue-700]=\"sortDirection === 'desc'\"\n [class.tw-bg-blue-50]=\"sortDirection === 'desc'\"\n [class.tw-text-gray-700]=\"sortDirection !== 'desc'\"\n [class.hover:tw-bg-gray-50]=\"sortDirection !== 'desc'\"\n (click)=\"onColumnSort(column, 'desc')\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3\"\n [class.tw-text-blue-600]=\"sortDirection === 'desc'\"\n [class.tw-text-gray-400]=\"sortDirection !== 'desc'\">arrow_downward</cide-ele-icon>\n Descending\n </div>\n @if (sortDirection === 'desc') {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">check</cide-ele-icon>\n }\n </button>\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Filter Option -->\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\n <!-- Check if there's a custom filter renderer template -->\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\n <div class=\"tw-px-4 tw-py-2\">\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\n </ng-container>\n </div>\n } @else {\n <!-- Excel-style Filter Button -->\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\n (click)=\"toggleFilterPanel(column.key, $event)\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\n Filter\n </div>\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\n </cide-ele-icon>\n </button>\n \n <!-- Excel-style Filter Panel -->\n @if (isFilterPanelOpen(column.key)) {\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\n <!-- Search box -->\n <div class=\"tw-px-2 tw-mb-2\">\n <input\n type=\"text\"\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\n placeholder=\"Search...\"\n [(ngModel)]=\"filterSearchTerm\"\n (click)=\"$event.stopPropagation()\">\n </div>\n \n <!-- Select All / Deselect All -->\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\n Select All\n </button>\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\n Clear\n </button>\n </div>\n \n <!-- Filter values list -->\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\n @for (item of getUniqueColumnValues(column); track item.value) {\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\n <input\n type=\"checkbox\"\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\n [checked]=\"item.checked\"\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\n (click)=\"$event.stopPropagation()\">\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\n </label>\n }\n </div>\n </div>\n }\n }\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Autosize Option -->\n @if (mergedConfig().columnMenu?.showAutosize) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAutosize(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\n Autosize\n </button>\n }\n\n <!-- Group By Column Option -->\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\n @let isGrouped = groupedColumns().includes(column.key);\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-transition-colors\"\n [class.tw-text-blue-700]=\"isGrouped\"\n [class.tw-bg-blue-50]=\"isGrouped\"\n [class.tw-text-gray-700]=\"!isGrouped\"\n [class.hover:tw-bg-gray-50]=\"!isGrouped\"\n (click)=\"onColumnGroupBy(column)\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3\" \n [class.tw-text-blue-600]=\"isGrouped\"\n [class.tw-text-gray-400]=\"!isGrouped\">group_work</cide-ele-icon>\n Group By Column\n </div>\n @if (isGrouped) {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">check</cide-ele-icon>\n }\n </button>\n }\n\n <!-- Manage Columns Option -->\n @if (mergedConfig().columnMenu?.showManageColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onManageColumns()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\n Manage Columns\n </button>\n }\n\n <!-- Reset Columns Option -->\n @if (mergedConfig().columnMenu?.showResetColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnReset()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\n Reset Columns\n </button>\n }\n\n <!-- Hide Column Option -->\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnHide(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\n Hide Column\n </button>\n }\n\n <!-- Aggregation Select Option -->\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'sum')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\n Sum\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'avg')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\n Average\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'count')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\n Count\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'min')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\n Min\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'max')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\n Max\n </button>\n }\n </div>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\n @if (loading() || isRefreshing() || pageChangeLoading()) {\n <!-- Skeleton Loading Rows -->\n @for (skeletonItem of getSkeletonArray(); track $index) {\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\n @for (column of columns; track column.key) {\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width)\n ]\"\n [style.width]=\"getColumnWidthStyle(column.width)\"\n [style.maxWidth]=\"getColumnMaxWidthStyle(column.width)\">\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\n </td>\n }\n </tr>\n }\n } @else {\n @for (item of displayedData; track trackByFn($index, item)) {\n @if (isGroupHeader(item)) {\n <!-- Group Header Row -->\n @let groupKey = getGroupKey(item);\n <tr class=\"tw-bg-gray-100 tw-border-b-2 tw-border-gray-300 tw-font-semibold\"\n (click)=\"toggleGroupExpansion(groupKey)\">\n <td class=\"tw-px-3 tw-py-2 tw-cursor-pointer hover:tw-bg-gray-200 tw-transition-colors\"\n [attr.colspan]=\"visibleColumns().length\">\n <div class=\"tw-flex tw-items-center tw-gap-2\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-600\">\n {{ isGroupExpanded(groupKey) ? 'expand_less' : 'expand_more' }}\n </cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">\n {{ getGroupValueDisplay(item) }}\n </span>\n <span class=\"tw-text-xs tw-text-gray-500 tw-ml-2\">\n ({{ getGroupRowCount(item) }} {{ getGroupRowCount(item) === 1 ? 'item' : 'items' }})\n </span>\n </div>\n </td>\n </tr>\n } @else {\n <!-- Regular Data Row -->\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\n [ngClass]=\"[\n mergedConfig().rowClass || '',\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\n isTreeEnabled() ? getTreeLevelClass(item) : ''\n ]\"\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\n (click)=\"onRowClick(item)\"\n (keydown.enter)=\"onRowClick(item)\"\n (keydown.space)=\"onRowClick(item)\"\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\n [draggable]=\"isDragDropEnabled()\"\n (dragstart)=\"onDragStart($event, item, $index)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event, item, $index)\"\n (dragend)=\"onDragEnd($event)\">\n \n @for (column of columns; track column.key) {\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n mergedConfig().cellClass || '',\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : '',\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\n ]\"\n [style.width]=\"getColumnWidthStyle(column.width)\"\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\n [style.maxWidth]=\"getColumnMaxWidthStyle(column.width) || (getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : null)\"\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : (getColumnWidthStyle(column.width) ? null : '100px')\">\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\n @if (isTreeEnabled() && $index === 0) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <!-- Tree Indentation -->\n <div class=\"tw-flex tw-items-center\">\n @if (hasChildren(item)) {\n <button \n variant=\"outline\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\n <cide-ele-icon \n class=\"tw-w-3 tw-h-3\"\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\n size=\"xs\">\n chevron_right\n </cide-ele-icon>\n </button>\n } @else {\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\n </div>\n }\n </div>\n \n <!-- Cell Content -->\n <div class=\"tw-flex-1 tw-w-full\">\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n </div>\n </div>\n } @else {\n <!-- Regular cell content (non-tree or non-first column) -->\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n }\n </td>\n }\n </tr>\n }\n }\n \n <!-- Empty State -->\n @if (displayedData.length === 0) {\n <tr class=\"tw-h-full\">\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\n </svg>\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\n @if (searchQuery()) {\n No results match your search criteria.\n } @else {\n There are no items to display.\n }\n </p>\n </div>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination Section -->\n @if (paginationConfig.enabled && totalItems() > 0) {\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\n [class.tw-opacity-60]=\"isRefreshing()\">\n \n <!-- Results Info and Page Size -->\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\n \n <!-- Results Info -->\n @if (paginationConfig.showPageInfo) {\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\n <p class=\"tw-text-sm tw-text-gray-700\">\n Showing {{ getItemRangeText() }} results\n </p>\n \n <!-- Page Size Selector -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\n <div class=\"tw-w-16 tw-relative\">\n <cide-ele-select\n [labelHide]=\"true\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"updatePageSize($event)\"\n [options]=\"getPageSizeOptions()\"\n [disabled]=\"isRefreshing()\"\n fill=\"outline\"\n size=\"xs\"\n class=\"tw-z-30\">\n </cide-ele-select>\n </div>\n </div>\n </div>\n }\n\n <!-- Pagination Controls -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n \n <!-- Previous/Next and Page Numbers -->\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n \n <!-- First Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(1)\"\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"First page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\n </button>\n \n <!-- Previous Page -->\n <button\n type=\"button\"\n (click)=\"previousPage()\"\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Previous page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\n </button>\n \n <!-- Page Numbers -->\n @for (page of getEnhancedPageNumbers(); track page) {\n @if (page === '...') {\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\n } @else {\n <button\n type=\"button\"\n (click)=\"onPageChange(page)\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n [ngClass]=\"{\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\n }\"\n [title]=\"'Page ' + page\">\n {{ page }}\n </button>\n }\n }\n \n <!-- Next Page -->\n <button\n type=\"button\"\n (click)=\"nextPage()\"\n [disabled]=\"!hasNextPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Next page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\n </button>\n \n <!-- Last Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(totalPages())\"\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Last page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\n </button>\n \n </div>\n\n <!-- Quick Jump and Refresh -->\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\n \n <!-- Quick Jump -->\n @if (paginationConfig.showQuickJump) {\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\n <div class=\"tw-w-16\">\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\n [disabled]=\"isRefreshing()\"\n size=\"xs\"\n (keydown.enter)=\"onJumpToPage()\">\n </cide-ele-input>\n </div>\n <button\n type=\"button\"\n (click)=\"onJumpToPage()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Go to page\">\n Go\n </button>\n </div>\n }\n \n <!-- Refresh Button -->\n @if (paginationConfig.showRefresh) {\n <button\n type=\"button\"\n (click)=\"onRefresh()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Refresh\">\n @if (isRefreshing()) {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\n } @else {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\n }\n </button>\n }\n </div>\n }\n \n </div>\n </div>\n </div>\n }\n\n <!-- Manage Columns Modal -->\n @if (showManageColumnsModal()) {\n <div class=\"tw-fixed tw-inset-0 tw-z-50 tw-overflow-y-auto\" \n (click)=\"closeManageColumnsModal()\"\n style=\"background-color: rgba(0, 0, 0, 0.5);\">\n <div class=\"tw-flex tw-items-center tw-justify-center tw-min-h-screen tw-p-4\">\n <div class=\"tw-bg-white tw-rounded-lg tw-shadow-xl tw-max-w-md tw-w-full tw-max-h-[80vh] tw-overflow-hidden tw-flex tw-flex-col\"\n (click)=\"$event.stopPropagation()\">\n <!-- Modal Header -->\n <div class=\"tw-px-6 tw-py-4 tw-border-b tw-border-gray-200 tw-flex tw-items-center tw-justify-between\">\n <h3 class=\"tw-text-lg tw-font-semibold tw-text-gray-900\">Manage Columns</h3>\n <button type=\"button\"\n class=\"tw-text-gray-400 hover:tw-text-gray-600 tw-transition-colors\"\n (click)=\"closeManageColumnsModal()\">\n <cide-ele-icon class=\"tw-w-5 tw-h-5\">close</cide-ele-icon>\n </button>\n </div>\n \n <!-- Modal Body -->\n <div class=\"tw-px-6 tw-py-4 tw-overflow-y-auto tw-flex-1\">\n <p class=\"tw-text-sm tw-text-gray-600 tw-mb-4\">Select columns to show or hide:</p>\n <div class=\"tw-space-y-2\">\n @for (column of mergedConfig().columns; track column.key) {\n <label class=\"tw-flex tw-items-center tw-px-3 tw-py-2 tw-rounded-md hover:tw-bg-gray-50 tw-cursor-pointer tw-transition-colors\">\n <input type=\"checkbox\"\n class=\"tw-mr-3 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\n [checked]=\"!isColumnHidden(column.key)\"\n (change)=\"toggleColumnVisibility(column.key); $event.stopPropagation()\">\n <span class=\"tw-text-sm tw-text-gray-700 tw-flex-1\">{{ column.header }}</span>\n @if (isColumnHidden(column.key)) {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-400 tw-ml-2\">visibility_off</cide-ele-icon>\n } @else {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-400 tw-ml-2\">visibility</cide-ele-icon>\n }\n </label>\n }\n </div>\n </div>\n \n <!-- Modal Footer -->\n <div class=\"tw-px-6 tw-py-4 tw-border-t tw-border-gray-200 tw-flex tw-items-center tw-justify-end tw-gap-3\">\n <button type=\"button\"\n class=\"tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-rounded-md hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"closeManageColumnsModal()\">\n Close\n </button>\n <button type=\"button\"\n class=\"tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-white tw-bg-blue-600 tw-border tw-border-transparent tw-rounded-md hover:tw-bg-blue-700 tw-transition-colors\"\n (click)=\"onColumnReset(); closeManageColumnsModal()\">\n Reset All\n </button>\n </div>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);position:relative;z-index:100}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05);position:absolute!important;z-index:9999!important}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "size"], outputs: ["ngModelChange"] }, { kind: "component", type: CideSelectComponent, selector: "cide-ele-select", inputs: ["label", "labelHide", "placeholder", "helperText", "errorText", "required", "disabled", "id", "ngModel", "size", "fill", "labelPlacement", "labelDir", "leadingIcon", "trailingIcon", "clearInput", "options", "multiple", "searchable", "showSearchInput", "loading", "valueKey", "labelKey"], outputs: ["ngModelChange", "change", "searchChange"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton]", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }] });
|
|
10443
10957
|
}
|
|
10444
10958
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleDataGridComponent, decorators: [{
|
|
10445
10959
|
type: Component,
|
|
@@ -10451,7 +10965,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
10451
10965
|
CideSelectComponent,
|
|
10452
10966
|
CideIconComponent,
|
|
10453
10967
|
CideEleButtonComponent
|
|
10454
|
-
], template: " <!-- Data Grid Component -->\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \n [ngClass]=\"[\n mergedConfig().tableClass || '',\n mergedConfig().fullHeight ? 'tw-h-full' : '',\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\n isTreeEnabled() ? 'tree-enabled' : ''\n ]\">\n \n <!-- Header Section -->\n @if (mergedConfig().title || mergedConfig().subtitle) {\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\n @if (mergedConfig().title) {\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\n {{ mergedConfig().title }}\n </h3>\n }\n @if (mergedConfig().subtitle) {\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\n {{ mergedConfig().subtitle }}\n </p>\n }\n </div>\n }\n\n <!-- Search Section -->\n @if (searchConfig.enabled) {\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\n <div class=\"tw-flex tw-items-center tw-justify-between\">\n <!-- Left Side: Search Input and Action Icons -->\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\n <!-- Search Input - Apple Style -->\n <div class=\"tw-max-w-md data-grid-search-input\">\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"updateSearchQuery($event)\"\n [placeholder]=\"searchConfig.placeholder\"\n [disabled]=\"loading() || isRefreshing()\"\n leadingIcon=\"search\"\n fill=\"outline\"\n size=\"sm\">\n </cide-ele-input>\n </div>\n \n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\n <!-- Filter Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-blue-600 hover:tw-bg-blue-50 tw-transition-all tw-duration-200 tw-relative\"\n [class.tw-text-blue-600]=\"columnFilters().length > 0\"\n [class.tw-bg-blue-50]=\"columnFilters().length > 0\"\n title=\"Filter\"\n (click)=\"toggleFilterDropdown()\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\n @if (columnFilters().length > 0) {\n <span class=\"tw-absolute -tw-top-1 -tw-right-1 tw-inline-flex tw-items-center tw-justify-center tw-w-4 tw-h-4 tw-text-xs tw-font-semibold tw-text-white tw-bg-blue-600 tw-rounded-full tw-border tw-border-white\">\n {{ columnFilters().length }}\n </span>\n }\n </button>\n \n <!-- Filter Dropdown Menu -->\n @if (showFilterDropdown()) {\n <div class=\"tw-absolute tw-left-0 tw-mt-2 tw-w-80 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200 tw-flex tw-items-center tw-justify-between\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Active Filters</span>\n @if (columnFilters().length > 0) {\n <button type=\"button\" \n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-800 tw-font-medium\"\n (click)=\"clearAllFilters()\">\n Clear All\n </button>\n }\n </div>\n @if (columnFilters().length === 0) {\n <div class=\"tw-px-4 tw-py-6 tw-text-center\">\n <cide-ele-icon class=\"tw-w-12 tw-h-12 tw-text-gray-300 tw-mx-auto tw-mb-2\">filter_list</cide-ele-icon>\n <p class=\"tw-text-sm tw-text-gray-500\">No filters applied</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Use column filters to narrow down your results</p>\n </div>\n } @else {\n <div class=\"tw-max-h-96 tw-overflow-y-auto\">\n @for (filter of columnFilters(); track filter.columnKey) {\n <div class=\"tw-px-4 tw-py-3 hover:tw-bg-gray-50 tw-border-b tw-border-gray-100 last:tw-border-b-0\">\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-2\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <div class=\"tw-text-xs tw-font-semibold tw-text-gray-600 tw-mb-1\">{{ getColumnHeader(filter.columnKey) }}</div>\n <div class=\"tw-text-sm tw-text-gray-700 tw-flex tw-items-center tw-gap-1\">\n @if (filter.operator === 'in' && isArray(filter.value)) {\n <span class=\"tw-inline-flex tw-items-center tw-gap-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3\">check_circle</cide-ele-icon>\n {{ getArrayLength(filter.value) }} selected\n </span>\n } @else {\n <span>{{ $any(filter.value) }}</span>\n }\n </div>\n </div>\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-400 hover:tw-text-red-600 hover:tw-bg-red-50 tw-transition-colors\"\n (click)=\"removeFilter(filter.columnKey)\"\n title=\"Remove filter\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n \n <!-- Download/Export Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\n title=\"Export\"\n (click)=\"toggleExportMenu($event)\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\n </button>\n \n <!-- Export Dropdown Menu - Improved Design -->\n @if (showExportMenu()) {\n <div class=\"tw-absolute tw-right-0 tw-mt-2 tw-w-56 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Export Options</span>\n </div>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-green-50 hover:tw-text-green-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('csv')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-green-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-green-600\">description</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">CSV</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Comma separated values</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-blue-50 hover:tw-text-blue-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('excel')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">table_chart</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">Excel</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Microsoft Excel format</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-red-50 hover:tw-text-red-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('pdf')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">PDF</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Portable document format</div>\n </div>\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n \n <!-- Right Side: Drag Order Actions -->\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <button cideEleButton \n variant=\"outline\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\n Reset Order\n </button>\n <button cideEleButton \n variant=\"primary\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\n Save Order\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Table Section -->\n <div class=\"tw-overflow-x-auto tw-relative\"\n [ngClass]=\"{\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\n 'tw-overflow-y-auto': scrollConfig?.enabled,\n 'tw-max-h-full': scrollConfig?.enabled\n }\"\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\n [class.empty-table]=\"displayedData.length === 0\"\n [ngClass]=\"{\n 'tw-table-striped': mergedConfig().striped,\n 'tw-border': mergedConfig().bordered,\n 'tw-table-sm': mergedConfig().compact\n }\"\n style=\"table-layout: fixed;\">\n \n <!-- Table Header -->\n <thead class=\"tw-bg-gray-50\" \n [ngClass]=\"[\n mergedConfig().headerClass || '',\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\n ]\">\n <tr>\n @for (column of visibleColumns(); track column.key) {\n <th\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : ''\n ]\"\n [title]=\"column.header\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\n <span class=\"tw-truncate tw-flex-1\">{{ column.header }}</span>\n \n <!-- Active Filter Indicator -->\n @if (isColumnFiltered(column.key)) {\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\n {{ getActiveFilterCount(column.key) }}\n </div>\n }\n \n <!-- Column Menu Trigger (Three Dots Icon) -->\n @if (mergedConfig().columnMenu?.enabled) {\n <button\n type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\n (click)=\"toggleColumnMenu(column.key, $event)\"\n title=\"Column options\">\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\n </button>\n }\n </div>\n\n <!-- Column Menu Dropdown -->\n @if (isColumnMenuOpen(column.key)) {\n <div class=\"column-menu-dropdown tw-absolute tw-z-50 tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\n <div class=\"tw-py-1\">\n <!-- Sort Options -->\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnSort(column, 'asc')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\n Ascending\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnSort(column, 'desc')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\n Descending\n </button>\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Filter Option -->\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\n <!-- Check if there's a custom filter renderer template -->\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\n <div class=\"tw-px-4 tw-py-2\">\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\n </ng-container>\n </div>\n } @else {\n <!-- Excel-style Filter Button -->\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\n (click)=\"toggleFilterPanel(column.key, $event)\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\n Filter\n </div>\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\n </cide-ele-icon>\n </button>\n \n <!-- Excel-style Filter Panel -->\n @if (isFilterPanelOpen(column.key)) {\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\n <!-- Search box -->\n <div class=\"tw-px-2 tw-mb-2\">\n <input\n type=\"text\"\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\n placeholder=\"Search...\"\n [(ngModel)]=\"filterSearchTerm\"\n (click)=\"$event.stopPropagation()\">\n </div>\n \n <!-- Select All / Deselect All -->\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\n Select All\n </button>\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\n Clear\n </button>\n </div>\n \n <!-- Filter values list -->\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\n @for (item of getUniqueColumnValues(column); track item.value) {\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\n <input\n type=\"checkbox\"\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\n [checked]=\"item.checked\"\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\n (click)=\"$event.stopPropagation()\">\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\n </label>\n }\n </div>\n </div>\n }\n }\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Autosize Option -->\n @if (mergedConfig().columnMenu?.showAutosize) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAutosize(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\n Autosize\n </button>\n }\n\n <!-- Group By Column Option -->\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnGroupBy(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">group_work</cide-ele-icon>\n Group By Column\n </button>\n }\n\n <!-- Manage Columns Option -->\n @if (mergedConfig().columnMenu?.showManageColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onManageColumns()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\n Manage Columns\n </button>\n }\n\n <!-- Reset Columns Option -->\n @if (mergedConfig().columnMenu?.showResetColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnReset()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\n Reset Columns\n </button>\n }\n\n <!-- Hide Column Option -->\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnHide(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\n Hide Column\n </button>\n }\n\n <!-- Aggregation Select Option -->\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'sum')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\n Sum\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'avg')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\n Average\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'count')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\n Count\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'min')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\n Min\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'max')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\n Max\n </button>\n }\n </div>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\n @if (loading() || isRefreshing() || pageChangeLoading()) {\n <!-- Skeleton Loading Rows -->\n @for (skeletonItem of getSkeletonArray(); track $index) {\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\n @for (column of columns; track column.key) {\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width)\n ]\">\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\n </td>\n }\n </tr>\n }\n } @else {\n @for (item of displayedData; track trackByFn($index, item)) {\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\n [ngClass]=\"[\n mergedConfig().rowClass || '',\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\n isTreeEnabled() ? getTreeLevelClass(item) : ''\n ]\"\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\n (click)=\"onRowClick(item)\"\n (keydown.enter)=\"onRowClick(item)\"\n (keydown.space)=\"onRowClick(item)\"\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\n [draggable]=\"isDragDropEnabled()\"\n (dragstart)=\"onDragStart($event, item, $index)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event, item, $index)\"\n (dragend)=\"onDragEnd($event)\">\n \n @for (column of columns; track column.key) {\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n mergedConfig().cellClass || '',\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : '',\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\n ]\"\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\n [style.maxWidth]=\"getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : 'none'\"\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : '100px'\">\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\n @if (isTreeEnabled() && $index === 0) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <!-- Tree Indentation -->\n <div class=\"tw-flex tw-items-center\">\n @if (hasChildren(item)) {\n <button \n variant=\"outline\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\n <cide-ele-icon \n class=\"tw-w-3 tw-h-3\"\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\n size=\"xs\">\n chevron_right\n </cide-ele-icon>\n </button>\n } @else {\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\n </div>\n }\n </div>\n \n <!-- Cell Content -->\n <div class=\"tw-flex-1 tw-w-full\">\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n </div>\n </div>\n } @else {\n <!-- Regular cell content (non-tree or non-first column) -->\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n }\n </td>\n }\n </tr>\n }\n \n <!-- Empty State -->\n @if (displayedData.length === 0) {\n <tr class=\"tw-h-full\">\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\n </svg>\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\n @if (searchQuery()) {\n No results match your search criteria.\n } @else {\n There are no items to display.\n }\n </p>\n </div>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination Section -->\n @if (paginationConfig.enabled && totalItems() > 0) {\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\n [class.tw-opacity-60]=\"isRefreshing()\">\n \n <!-- Results Info and Page Size -->\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\n \n <!-- Results Info -->\n @if (paginationConfig.showPageInfo) {\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\n <p class=\"tw-text-sm tw-text-gray-700\">\n Showing {{ getItemRangeText() }} results\n </p>\n \n <!-- Page Size Selector -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\n <div class=\"tw-w-16 tw-relative\">\n <cide-ele-select\n [labelHide]=\"true\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"updatePageSize($event)\"\n [options]=\"getPageSizeOptions()\"\n [disabled]=\"isRefreshing()\"\n fill=\"outline\"\n size=\"xs\"\n class=\"tw-z-30\">\n </cide-ele-select>\n </div>\n </div>\n </div>\n }\n\n <!-- Pagination Controls -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n \n <!-- Previous/Next and Page Numbers -->\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n \n <!-- First Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(1)\"\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"First page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\n </button>\n \n <!-- Previous Page -->\n <button\n type=\"button\"\n (click)=\"previousPage()\"\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Previous page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\n </button>\n \n <!-- Page Numbers -->\n @for (page of getEnhancedPageNumbers(); track page) {\n @if (page === '...') {\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\n } @else {\n <button\n type=\"button\"\n (click)=\"onPageChange(page)\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n [ngClass]=\"{\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\n }\"\n [title]=\"'Page ' + page\">\n {{ page }}\n </button>\n }\n }\n \n <!-- Next Page -->\n <button\n type=\"button\"\n (click)=\"nextPage()\"\n [disabled]=\"!hasNextPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Next page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\n </button>\n \n <!-- Last Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(totalPages())\"\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Last page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\n </button>\n \n </div>\n\n <!-- Quick Jump and Refresh -->\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\n \n <!-- Quick Jump -->\n @if (paginationConfig.showQuickJump) {\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\n <div class=\"tw-w-16\">\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\n [disabled]=\"isRefreshing()\"\n size=\"xs\"\n (keydown.enter)=\"onJumpToPage()\">\n </cide-ele-input>\n </div>\n <button\n type=\"button\"\n (click)=\"onJumpToPage()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Go to page\">\n Go\n </button>\n </div>\n }\n \n <!-- Refresh Button -->\n @if (paginationConfig.showRefresh) {\n <button\n type=\"button\"\n (click)=\"onRefresh()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Refresh\">\n @if (isRefreshing()) {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\n } @else {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\n }\n </button>\n }\n </div>\n }\n \n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05)}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"] }]
|
|
10968
|
+
], template: " <!-- Data Grid Component -->\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \n [ngClass]=\"[\n mergedConfig().tableClass || '',\n mergedConfig().fullHeight ? 'tw-h-full' : '',\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\n isTreeEnabled() ? 'tree-enabled' : ''\n ]\">\n \n <!-- Header Section -->\n @if (mergedConfig().title || mergedConfig().subtitle) {\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\n @if (mergedConfig().title) {\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\n {{ mergedConfig().title }}\n </h3>\n }\n @if (mergedConfig().subtitle) {\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\n {{ mergedConfig().subtitle }}\n </p>\n }\n </div>\n }\n\n <!-- Search Section -->\n @if (searchConfig.enabled) {\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\n <div class=\"tw-flex tw-items-center tw-justify-between\">\n <!-- Left Side: Search Input and Action Icons -->\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\n <!-- Search Input - Apple Style -->\n <div class=\"tw-max-w-md data-grid-search-input\">\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"updateSearchQuery($event)\"\n [placeholder]=\"searchConfig.placeholder\"\n [disabled]=\"loading() || isRefreshing()\"\n leadingIcon=\"search\"\n fill=\"outline\"\n size=\"sm\">\n </cide-ele-input>\n </div>\n \n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\n <!-- Filter Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"filter-dropdown-trigger tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-blue-600 hover:tw-bg-blue-50 tw-transition-all tw-duration-200 tw-relative\"\n [class.tw-text-blue-600]=\"columnFilters().length > 0\"\n [class.tw-bg-blue-50]=\"columnFilters().length > 0\"\n title=\"Filter\"\n (click)=\"toggleFilterDropdown()\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\n @if (columnFilters().length > 0) {\n <span class=\"tw-absolute -tw-top-1 -tw-right-1 tw-inline-flex tw-items-center tw-justify-center tw-w-4 tw-h-4 tw-text-xs tw-font-semibold tw-text-white tw-bg-blue-600 tw-rounded-full tw-border tw-border-white\">\n {{ columnFilters().length }}\n </span>\n }\n </button>\n \n <!-- Filter Dropdown Menu -->\n @if (showFilterDropdown()) {\n <div class=\"filter-dropdown tw-absolute tw-left-0 tw-mt-2 tw-w-80 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200 tw-flex tw-items-center tw-justify-between\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Active Filters</span>\n @if (columnFilters().length > 0) {\n <button type=\"button\" \n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-800 tw-font-medium\"\n (click)=\"clearAllFilters()\">\n Clear All\n </button>\n }\n </div>\n @if (columnFilters().length === 0) {\n <div class=\"tw-px-4 tw-py-6 tw-text-center\">\n <cide-ele-icon class=\"tw-w-12 tw-h-12 tw-text-gray-300 tw-mx-auto tw-mb-2\">filter_list</cide-ele-icon>\n <p class=\"tw-text-sm tw-text-gray-500\">No filters applied</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Use column filters to narrow down your results</p>\n </div>\n } @else {\n <div class=\"tw-max-h-96 tw-overflow-y-auto\">\n @for (filter of columnFilters(); track filter.columnKey) {\n <div class=\"tw-px-4 tw-py-3 hover:tw-bg-gray-50 tw-border-b tw-border-gray-100 last:tw-border-b-0\">\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-2\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <div class=\"tw-text-xs tw-font-semibold tw-text-gray-600 tw-mb-1\">{{ getColumnHeader(filter.columnKey) }}</div>\n <div class=\"tw-text-sm tw-text-gray-700 tw-flex tw-items-center tw-gap-1\">\n @if (filter.operator === 'in' && isArray(filter.value)) {\n <span class=\"tw-inline-flex tw-items-center tw-gap-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3\">check_circle</cide-ele-icon>\n {{ getArrayLength(filter.value) }} selected\n </span>\n } @else {\n <span>{{ $any(filter.value) }}</span>\n }\n </div>\n </div>\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-400 hover:tw-text-red-600 hover:tw-bg-red-50 tw-transition-colors\"\n (click)=\"removeFilter(filter.columnKey)\"\n title=\"Remove filter\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n \n <!-- Download/Export Button with Dropdown -->\n <div class=\"tw-relative\">\n <button type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\n title=\"Export\"\n (click)=\"toggleExportMenu($event)\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\n </button>\n \n <!-- Export Dropdown Menu - Improved Design -->\n @if (showExportMenu()) {\n <div class=\"tw-absolute tw-right-0 tw-mt-2 tw-w-56 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"tw-py-1.5\">\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200\">\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Export Options</span>\n </div>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-green-50 hover:tw-text-green-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('csv')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-green-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-green-600\">description</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">CSV</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Comma separated values</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-blue-50 hover:tw-text-blue-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('excel')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">table_chart</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">Excel</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Microsoft Excel format</div>\n </div>\n </button>\n <button type=\"button\"\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-red-50 hover:tw-text-red-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\n (click)=\"exportData('pdf')\">\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-font-medium\">PDF</div>\n <div class=\"tw-text-xs tw-text-gray-500\">Portable document format</div>\n </div>\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n \n <!-- Right Side: Drag Order Actions -->\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <button cideEleButton \n variant=\"outline\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\n Reset Order\n </button>\n <button cideEleButton \n variant=\"primary\" \n size=\"sm\" \n type=\"button\"\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\n Save Order\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Table Section -->\n <div class=\"tw-overflow-x-auto tw-relative\"\n [ngClass]=\"{\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\n 'tw-overflow-y-auto': scrollConfig?.enabled,\n 'tw-max-h-full': scrollConfig?.enabled\n }\"\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\n [class.empty-table]=\"displayedData.length === 0\"\n [ngClass]=\"{\n 'tw-table-striped': mergedConfig().striped,\n 'tw-border': mergedConfig().bordered,\n 'tw-table-sm': mergedConfig().compact\n }\"\n style=\"table-layout: fixed;\">\n \n <!-- Table Header -->\n <thead class=\"tw-bg-gray-50\" \n [ngClass]=\"[\n mergedConfig().headerClass || '',\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\n ]\">\n <tr>\n @for (column of visibleColumns(); track column.key) {\n <th\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : ''\n ]\"\n [style.width]=\"getColumnWidthStyle(column.width)\"\n [style.maxWidth]=\"getColumnMaxWidthStyle(column.width)\"\n [title]=\"column.header\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\n <div class=\"tw-flex tw-items-center tw-gap-1 tw-truncate tw-flex-1\">\n <span class=\"tw-truncate\">{{ column.header }}</span>\n @if (getColumnSortDirection(column.key) === 'asc') {\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-blue-600 tw-flex-shrink-0\" title=\"Sorted Ascending\">arrow_upward</cide-ele-icon>\n }\n @if (getColumnSortDirection(column.key) === 'desc') {\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-blue-600 tw-flex-shrink-0\" title=\"Sorted Descending\">arrow_downward</cide-ele-icon>\n }\n </div>\n \n <!-- Active Filter Indicator -->\n @if (isColumnFiltered(column.key)) {\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\n {{ getActiveFilterCount(column.key) }}\n </div>\n }\n \n <!-- Column Menu Trigger (Three Dots Icon) -->\n @if (mergedConfig().columnMenu?.enabled) {\n <button\n type=\"button\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\n (click)=\"toggleColumnMenu(column.key, $event)\"\n title=\"Column options\">\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\n </button>\n }\n </div>\n\n <!-- Column Menu Dropdown -->\n @if (isColumnMenuOpen(column.key)) {\n <div class=\"column-menu-dropdown tw-absolute tw-z-[9999] tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\n <div class=\"tw-py-1\">\n <!-- Sort Options -->\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\n @let sortDirection = getColumnSortDirection(column.key);\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-transition-colors\"\n [class.tw-text-blue-700]=\"sortDirection === 'asc'\"\n [class.tw-bg-blue-50]=\"sortDirection === 'asc'\"\n [class.tw-text-gray-700]=\"sortDirection !== 'asc'\"\n [class.hover:tw-bg-gray-50]=\"sortDirection !== 'asc'\"\n (click)=\"onColumnSort(column, 'asc')\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3\" \n [class.tw-text-blue-600]=\"sortDirection === 'asc'\"\n [class.tw-text-gray-400]=\"sortDirection !== 'asc'\">arrow_upward</cide-ele-icon>\n Ascending\n </div>\n @if (sortDirection === 'asc') {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">check</cide-ele-icon>\n }\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-transition-colors\"\n [class.tw-text-blue-700]=\"sortDirection === 'desc'\"\n [class.tw-bg-blue-50]=\"sortDirection === 'desc'\"\n [class.tw-text-gray-700]=\"sortDirection !== 'desc'\"\n [class.hover:tw-bg-gray-50]=\"sortDirection !== 'desc'\"\n (click)=\"onColumnSort(column, 'desc')\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3\"\n [class.tw-text-blue-600]=\"sortDirection === 'desc'\"\n [class.tw-text-gray-400]=\"sortDirection !== 'desc'\">arrow_downward</cide-ele-icon>\n Descending\n </div>\n @if (sortDirection === 'desc') {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">check</cide-ele-icon>\n }\n </button>\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Filter Option -->\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\n <!-- Check if there's a custom filter renderer template -->\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\n <div class=\"tw-px-4 tw-py-2\">\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\n </ng-container>\n </div>\n } @else {\n <!-- Excel-style Filter Button -->\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\n (click)=\"toggleFilterPanel(column.key, $event)\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\n Filter\n </div>\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\n </cide-ele-icon>\n </button>\n \n <!-- Excel-style Filter Panel -->\n @if (isFilterPanelOpen(column.key)) {\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\n <!-- Search box -->\n <div class=\"tw-px-2 tw-mb-2\">\n <input\n type=\"text\"\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\n placeholder=\"Search...\"\n [(ngModel)]=\"filterSearchTerm\"\n (click)=\"$event.stopPropagation()\">\n </div>\n \n <!-- Select All / Deselect All -->\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\n Select All\n </button>\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\n <button\n type=\"button\"\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\n Clear\n </button>\n </div>\n \n <!-- Filter values list -->\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\n @for (item of getUniqueColumnValues(column); track item.value) {\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\n <input\n type=\"checkbox\"\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\n [checked]=\"item.checked\"\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\n (click)=\"$event.stopPropagation()\">\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\n </label>\n }\n </div>\n </div>\n }\n }\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n }\n\n <!-- Autosize Option -->\n @if (mergedConfig().columnMenu?.showAutosize) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAutosize(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\n Autosize\n </button>\n }\n\n <!-- Group By Column Option -->\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\n @let isGrouped = groupedColumns().includes(column.key);\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-transition-colors\"\n [class.tw-text-blue-700]=\"isGrouped\"\n [class.tw-bg-blue-50]=\"isGrouped\"\n [class.tw-text-gray-700]=\"!isGrouped\"\n [class.hover:tw-bg-gray-50]=\"!isGrouped\"\n (click)=\"onColumnGroupBy(column)\">\n <div class=\"tw-flex tw-items-center\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3\" \n [class.tw-text-blue-600]=\"isGrouped\"\n [class.tw-text-gray-400]=\"!isGrouped\">group_work</cide-ele-icon>\n Group By Column\n </div>\n @if (isGrouped) {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">check</cide-ele-icon>\n }\n </button>\n }\n\n <!-- Manage Columns Option -->\n @if (mergedConfig().columnMenu?.showManageColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onManageColumns()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\n Manage Columns\n </button>\n }\n\n <!-- Reset Columns Option -->\n @if (mergedConfig().columnMenu?.showResetColumns) {\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnReset()\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\n Reset Columns\n </button>\n }\n\n <!-- Hide Column Option -->\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnHide(column)\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\n Hide Column\n </button>\n }\n\n <!-- Aggregation Select Option -->\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'sum')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\n Sum\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'avg')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\n Average\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'count')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\n Count\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'min')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\n Min\n </button>\n <button\n type=\"button\"\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"onColumnAggregation(column, 'max')\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\n Max\n </button>\n }\n </div>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\n @if (loading() || isRefreshing() || pageChangeLoading()) {\n <!-- Skeleton Loading Rows -->\n @for (skeletonItem of getSkeletonArray(); track $index) {\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\n @for (column of columns; track column.key) {\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width)\n ]\"\n [style.width]=\"getColumnWidthStyle(column.width)\"\n [style.maxWidth]=\"getColumnMaxWidthStyle(column.width)\">\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\n </td>\n }\n </tr>\n }\n } @else {\n @for (item of displayedData; track trackByFn($index, item)) {\n @if (isGroupHeader(item)) {\n <!-- Group Header Row -->\n @let groupKey = getGroupKey(item);\n <tr class=\"tw-bg-gray-100 tw-border-b-2 tw-border-gray-300 tw-font-semibold\"\n (click)=\"toggleGroupExpansion(groupKey)\">\n <td class=\"tw-px-3 tw-py-2 tw-cursor-pointer hover:tw-bg-gray-200 tw-transition-colors\"\n [attr.colspan]=\"visibleColumns().length\">\n <div class=\"tw-flex tw-items-center tw-gap-2\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-600\">\n {{ isGroupExpanded(groupKey) ? 'expand_less' : 'expand_more' }}\n </cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">\n {{ getGroupValueDisplay(item) }}\n </span>\n <span class=\"tw-text-xs tw-text-gray-500 tw-ml-2\">\n ({{ getGroupRowCount(item) }} {{ getGroupRowCount(item) === 1 ? 'item' : 'items' }})\n </span>\n </div>\n </td>\n </tr>\n } @else {\n <!-- Regular Data Row -->\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\n [ngClass]=\"[\n mergedConfig().rowClass || '',\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\n isTreeEnabled() ? getTreeLevelClass(item) : ''\n ]\"\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\n (click)=\"onRowClick(item)\"\n (keydown.enter)=\"onRowClick(item)\"\n (keydown.space)=\"onRowClick(item)\"\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\n [draggable]=\"isDragDropEnabled()\"\n (dragstart)=\"onDragStart($event, item, $index)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event, item, $index)\"\n (dragend)=\"onDragEnd($event)\">\n \n @for (column of columns; track column.key) {\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\n [ngClass]=\"[\n getColumnWidthClass(column.width),\n getColumnMaxWidthClass(column.width),\n mergedConfig().cellClass || '',\n column.align === 'center' ? 'tw-text-center' : '',\n column.align === 'right' ? 'tw-text-right' : '',\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\n ]\"\n [style.width]=\"getColumnWidthStyle(column.width)\"\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\n [style.maxWidth]=\"getColumnMaxWidthStyle(column.width) || (getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : null)\"\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : (getColumnWidthStyle(column.width) ? null : '100px')\">\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\n @if (isTreeEnabled() && $index === 0) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <!-- Tree Indentation -->\n <div class=\"tw-flex tw-items-center\">\n @if (hasChildren(item)) {\n <button \n variant=\"outline\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\n <cide-ele-icon \n class=\"tw-w-3 tw-h-3\"\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\n size=\"xs\">\n chevron_right\n </cide-ele-icon>\n </button>\n } @else {\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\n </div>\n }\n </div>\n \n <!-- Cell Content -->\n <div class=\"tw-flex-1 tw-w-full\">\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n </div>\n </div>\n } @else {\n <!-- Regular cell content (non-tree or non-first column) -->\n @if (column.type === 'text') {\n <p class=\"tw-text-sm tw-text-gray-900\"\n [class.tw-truncate]=\"column.truncate\"\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </p>\n } @else if (column.type === 'number') {\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'date') {\n <span class=\"tw-text-sm tw-text-gray-600\">\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\n </span>\n } @else if (column.type === 'boolean') {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\n </span>\n } @else if (column.type === 'status' && column.statusConfig) {\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\n </span>\n } @else if (column.type === 'actions' && column.actions) {\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n @for (action of column.actions; track action.key) {\n <button\n cideEleButton\n [variant]=\"action.variant || 'ghost'\"\n size=\"xs\"\n type=\"button\"\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\n [title]=\"action.tooltip || action.label\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\n [ngClass]=\"{\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\n }\">\n @if (action.icon) {\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n } @else if (column.type === 'custom') {\n <!-- Template Renderer -->\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\n <ng-container \n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\n </ng-container>\n }\n <!-- Default rendering -->\n @else {\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\n }\n }\n }\n </td>\n }\n </tr>\n }\n }\n \n <!-- Empty State -->\n @if (displayedData.length === 0) {\n <tr class=\"tw-h-full\">\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\n </svg>\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\n @if (searchQuery()) {\n No results match your search criteria.\n } @else {\n There are no items to display.\n }\n </p>\n </div>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination Section -->\n @if (paginationConfig.enabled && totalItems() > 0) {\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\n [class.tw-opacity-60]=\"isRefreshing()\">\n \n <!-- Results Info and Page Size -->\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\n \n <!-- Results Info -->\n @if (paginationConfig.showPageInfo) {\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\n <p class=\"tw-text-sm tw-text-gray-700\">\n Showing {{ getItemRangeText() }} results\n </p>\n \n <!-- Page Size Selector -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\n <div class=\"tw-w-16 tw-relative\">\n <cide-ele-select\n [labelHide]=\"true\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"updatePageSize($event)\"\n [options]=\"getPageSizeOptions()\"\n [disabled]=\"isRefreshing()\"\n fill=\"outline\"\n size=\"xs\"\n class=\"tw-z-30\">\n </cide-ele-select>\n </div>\n </div>\n </div>\n }\n\n <!-- Pagination Controls -->\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\n \n <!-- Previous/Next and Page Numbers -->\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n \n <!-- First Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(1)\"\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"First page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\n </button>\n \n <!-- Previous Page -->\n <button\n type=\"button\"\n (click)=\"previousPage()\"\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Previous page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\n </button>\n \n <!-- Page Numbers -->\n @for (page of getEnhancedPageNumbers(); track page) {\n @if (page === '...') {\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\n } @else {\n <button\n type=\"button\"\n (click)=\"onPageChange(page)\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n [ngClass]=\"{\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\n }\"\n [title]=\"'Page ' + page\">\n {{ page }}\n </button>\n }\n }\n \n <!-- Next Page -->\n <button\n type=\"button\"\n (click)=\"nextPage()\"\n [disabled]=\"!hasNextPage() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Next page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\n </button>\n \n <!-- Last Page -->\n <button\n type=\"button\"\n (click)=\"onPageChange(totalPages())\"\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Last page\">\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\n </button>\n \n </div>\n\n <!-- Quick Jump and Refresh -->\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\n \n <!-- Quick Jump -->\n @if (paginationConfig.showQuickJump) {\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\n <div class=\"tw-w-16\">\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\n [disabled]=\"isRefreshing()\"\n size=\"xs\"\n (keydown.enter)=\"onJumpToPage()\">\n </cide-ele-input>\n </div>\n <button\n type=\"button\"\n (click)=\"onJumpToPage()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Go to page\">\n Go\n </button>\n </div>\n }\n \n <!-- Refresh Button -->\n @if (paginationConfig.showRefresh) {\n <button\n type=\"button\"\n (click)=\"onRefresh()\"\n [disabled]=\"isRefreshing()\"\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\n title=\"Refresh\">\n @if (isRefreshing()) {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\n } @else {\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\n }\n </button>\n }\n </div>\n }\n \n </div>\n </div>\n </div>\n }\n\n <!-- Manage Columns Modal -->\n @if (showManageColumnsModal()) {\n <div class=\"tw-fixed tw-inset-0 tw-z-50 tw-overflow-y-auto\" \n (click)=\"closeManageColumnsModal()\"\n style=\"background-color: rgba(0, 0, 0, 0.5);\">\n <div class=\"tw-flex tw-items-center tw-justify-center tw-min-h-screen tw-p-4\">\n <div class=\"tw-bg-white tw-rounded-lg tw-shadow-xl tw-max-w-md tw-w-full tw-max-h-[80vh] tw-overflow-hidden tw-flex tw-flex-col\"\n (click)=\"$event.stopPropagation()\">\n <!-- Modal Header -->\n <div class=\"tw-px-6 tw-py-4 tw-border-b tw-border-gray-200 tw-flex tw-items-center tw-justify-between\">\n <h3 class=\"tw-text-lg tw-font-semibold tw-text-gray-900\">Manage Columns</h3>\n <button type=\"button\"\n class=\"tw-text-gray-400 hover:tw-text-gray-600 tw-transition-colors\"\n (click)=\"closeManageColumnsModal()\">\n <cide-ele-icon class=\"tw-w-5 tw-h-5\">close</cide-ele-icon>\n </button>\n </div>\n \n <!-- Modal Body -->\n <div class=\"tw-px-6 tw-py-4 tw-overflow-y-auto tw-flex-1\">\n <p class=\"tw-text-sm tw-text-gray-600 tw-mb-4\">Select columns to show or hide:</p>\n <div class=\"tw-space-y-2\">\n @for (column of mergedConfig().columns; track column.key) {\n <label class=\"tw-flex tw-items-center tw-px-3 tw-py-2 tw-rounded-md hover:tw-bg-gray-50 tw-cursor-pointer tw-transition-colors\">\n <input type=\"checkbox\"\n class=\"tw-mr-3 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\n [checked]=\"!isColumnHidden(column.key)\"\n (change)=\"toggleColumnVisibility(column.key); $event.stopPropagation()\">\n <span class=\"tw-text-sm tw-text-gray-700 tw-flex-1\">{{ column.header }}</span>\n @if (isColumnHidden(column.key)) {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-400 tw-ml-2\">visibility_off</cide-ele-icon>\n } @else {\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-400 tw-ml-2\">visibility</cide-ele-icon>\n }\n </label>\n }\n </div>\n </div>\n \n <!-- Modal Footer -->\n <div class=\"tw-px-6 tw-py-4 tw-border-t tw-border-gray-200 tw-flex tw-items-center tw-justify-end tw-gap-3\">\n <button type=\"button\"\n class=\"tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-gray-700 tw-bg-white tw-border tw-border-gray-300 tw-rounded-md hover:tw-bg-gray-50 tw-transition-colors\"\n (click)=\"closeManageColumnsModal()\">\n Close\n </button>\n <button type=\"button\"\n class=\"tw-px-4 tw-py-2 tw-text-sm tw-font-medium tw-text-white tw-bg-blue-600 tw-border tw-border-transparent tw-rounded-md hover:tw-bg-blue-700 tw-transition-colors\"\n (click)=\"onColumnReset(); closeManageColumnsModal()\">\n Reset All\n </button>\n </div>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);position:relative;z-index:100}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05);position:absolute!important;z-index:9999!important}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"] }]
|
|
10455
10969
|
}], propDecorators: { config: [{
|
|
10456
10970
|
type: Input
|
|
10457
10971
|
}], templateRenderers: [{
|