@krumio/trailhand-ui 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/Button.d.ts +15 -0
- package/dist/Button.d.ts.map +1 -0
- package/dist/Button.js +18 -0
- package/dist/Button.js.map +1 -0
- package/dist/Header.d.ts +15 -0
- package/dist/Header.d.ts.map +1 -0
- package/dist/Header.js +44 -0
- package/dist/Header.js.map +1 -0
- package/dist/Page.d.ts +9 -0
- package/dist/Page.d.ts.map +1 -0
- package/dist/Page.js +61 -0
- package/dist/Page.js.map +1 -0
- package/dist/action-menu.d.ts +79 -0
- package/dist/action-menu.d.ts.map +1 -0
- package/dist/action-menu.js +321 -0
- package/dist/action-menu.js.map +1 -0
- package/dist/assets/index-B7q5L5KS.js +91 -0
- package/dist/data-table.d.ts +191 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +796 -0
- package/dist/data-table.js.map +1 -0
- package/dist/index.html +102 -0
- package/dist/toggle-switch.d.ts +38 -0
- package/dist/toggle-switch.d.ts.map +1 -0
- package/dist/toggle-switch.js +175 -0
- package/dist/toggle-switch.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, html, css } from 'lit';
|
|
8
|
+
import { property, state } from 'lit/decorators.js';
|
|
9
|
+
import './action-menu';
|
|
10
|
+
import 'iconify-icon';
|
|
11
|
+
import { addIcon } from 'iconify-icon';
|
|
12
|
+
import chevronUp from '@iconify/icons-heroicons/chevron-up-20-solid';
|
|
13
|
+
import chevronDown from '@iconify/icons-heroicons/chevron-down-20-solid';
|
|
14
|
+
import chevronLeft from '@iconify/icons-heroicons/chevron-left-20-solid';
|
|
15
|
+
import chevronRight from '@iconify/icons-heroicons/chevron-right-20-solid';
|
|
16
|
+
// Pre-load icons to avoid CDN delay
|
|
17
|
+
addIcon('heroicons:chevron-up-20-solid', chevronUp);
|
|
18
|
+
addIcon('heroicons:chevron-down-20-solid', chevronDown);
|
|
19
|
+
addIcon('heroicons:chevron-left-20-solid', chevronLeft);
|
|
20
|
+
addIcon('heroicons:chevron-right-20-solid', chevronRight);
|
|
21
|
+
/**
|
|
22
|
+
* Data table formatters for common column types
|
|
23
|
+
*/
|
|
24
|
+
export const dataTableFormatters = {
|
|
25
|
+
/**
|
|
26
|
+
* Format a date value as relative time (e.g., "5d", "3h", "15m")
|
|
27
|
+
*/
|
|
28
|
+
age: (value) => {
|
|
29
|
+
if (!value)
|
|
30
|
+
return '-';
|
|
31
|
+
const date = new Date(value);
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const diffMs = now.getTime() - date.getTime();
|
|
34
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
35
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
36
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
37
|
+
if (diffDays > 0)
|
|
38
|
+
return `${diffDays}d`;
|
|
39
|
+
if (diffHours > 0)
|
|
40
|
+
return `${diffHours}h`;
|
|
41
|
+
if (diffMins > 0)
|
|
42
|
+
return `${diffMins}m`;
|
|
43
|
+
return 'Just now';
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* Format a date as a localized date string
|
|
47
|
+
*/
|
|
48
|
+
date: (value) => {
|
|
49
|
+
if (!value)
|
|
50
|
+
return '-';
|
|
51
|
+
return new Date(value).toLocaleDateString();
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* Format a date as a localized date and time string
|
|
55
|
+
*/
|
|
56
|
+
dateTime: (value) => {
|
|
57
|
+
if (!value)
|
|
58
|
+
return '-';
|
|
59
|
+
return new Date(value).toLocaleString();
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* Format memory bytes to human readable format (B, KB, MB, GB, TB)
|
|
63
|
+
*/
|
|
64
|
+
memory: (value) => {
|
|
65
|
+
if (!value || value === 0)
|
|
66
|
+
return '0 B';
|
|
67
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
68
|
+
let val = Number(value);
|
|
69
|
+
let unitIndex = 0;
|
|
70
|
+
while (val >= 1024 && unitIndex < units.length - 1) {
|
|
71
|
+
val = val / 1024;
|
|
72
|
+
unitIndex++;
|
|
73
|
+
}
|
|
74
|
+
return `${Math.round(val)} ${units[unitIndex]}`;
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* Format millicpus value
|
|
78
|
+
*/
|
|
79
|
+
milliCPUs: (value) => {
|
|
80
|
+
if (!value || value === 0)
|
|
81
|
+
return '0';
|
|
82
|
+
const formatted = Math.round(Number(value)).toString();
|
|
83
|
+
return formatted === '0' ? '0' : formatted;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* A feature-rich data table component with sorting, filtering, and pagination.
|
|
88
|
+
* Supports custom cell rendering, row actions, and various formatters.
|
|
89
|
+
*/
|
|
90
|
+
export class DataTable extends LitElement {
|
|
91
|
+
constructor() {
|
|
92
|
+
super(...arguments);
|
|
93
|
+
this.columns = [];
|
|
94
|
+
this.rows = [];
|
|
95
|
+
this.rowsPerPage = 10;
|
|
96
|
+
this.searchable = true;
|
|
97
|
+
this.sortable = true;
|
|
98
|
+
this.paginated = true;
|
|
99
|
+
this.loading = false;
|
|
100
|
+
this.keyField = 'id';
|
|
101
|
+
this.rowActions = true;
|
|
102
|
+
this.rowActionsWidth = 40;
|
|
103
|
+
this.emptyMessage = 'No data available';
|
|
104
|
+
this.noResultsMessage = 'No results found';
|
|
105
|
+
// Internal state
|
|
106
|
+
this._searchQuery = '';
|
|
107
|
+
this._currentPage = 1;
|
|
108
|
+
this._sortColumn = null;
|
|
109
|
+
this._sortDirection = 'asc';
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get nested value from object using dot notation
|
|
113
|
+
* @param obj - The object to extract value from
|
|
114
|
+
* @param path - The path (e.g., 'user.name')
|
|
115
|
+
* @returns The value at the path
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
_getNestedValue(obj, path) {
|
|
119
|
+
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Format a cell value using column formatter
|
|
123
|
+
* @param row - The row data
|
|
124
|
+
* @param column - The column definition
|
|
125
|
+
* @returns The formatted value
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
_formatValue(row, column) {
|
|
129
|
+
const value = this._getNestedValue(row, column.field);
|
|
130
|
+
if (column.formatter) {
|
|
131
|
+
// If formatter is a string, use dataTableFormatters
|
|
132
|
+
if (typeof column.formatter === 'string') {
|
|
133
|
+
const formatter = dataTableFormatters[column.formatter];
|
|
134
|
+
if (formatter) {
|
|
135
|
+
return formatter(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (typeof column.formatter === 'function') {
|
|
139
|
+
// Otherwise, use custom formatter function
|
|
140
|
+
return column.formatter(value, row);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get link URL for a cell if column has link property
|
|
147
|
+
* @param row - The row data
|
|
148
|
+
* @param column - The column definition
|
|
149
|
+
* @returns The link URL or null
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
_getLinkUrl(row, column) {
|
|
153
|
+
if (!column.link) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
if (typeof column.link === 'string') {
|
|
157
|
+
// Link is a field name in the row data
|
|
158
|
+
return this._getNestedValue(row, column.link);
|
|
159
|
+
}
|
|
160
|
+
else if (typeof column.link === 'function') {
|
|
161
|
+
// Link is a function that takes the row and returns a URL
|
|
162
|
+
return column.link(row);
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Render cell content with optional link
|
|
168
|
+
* @param row - The row data
|
|
169
|
+
* @param column - The column definition
|
|
170
|
+
* @returns The rendered cell content
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
_renderCellContent(row, column) {
|
|
174
|
+
const value = this._formatValue(row, column);
|
|
175
|
+
const linkUrl = this._getLinkUrl(row, column);
|
|
176
|
+
if (linkUrl) {
|
|
177
|
+
const target = column.linkTarget || '_self';
|
|
178
|
+
// For external links or _blank target, use regular <a> tag
|
|
179
|
+
if (target === '_blank' || linkUrl.startsWith('http://') || linkUrl.startsWith('https://')) {
|
|
180
|
+
const rel = target === '_blank' ? 'noopener noreferrer' : '';
|
|
181
|
+
return html `<a href="${linkUrl}" target="${target}" rel="${rel}">${value}</a>`;
|
|
182
|
+
}
|
|
183
|
+
// For internal links, use a clickable element that emits a navigation event
|
|
184
|
+
return html `<a
|
|
185
|
+
href="${linkUrl}"
|
|
186
|
+
@click="${(e) => this._handleLinkClick(e, linkUrl, row)}"
|
|
187
|
+
>${value}</a>`;
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Handle link click - emit navigation event instead of following link
|
|
193
|
+
* @param event - The click event
|
|
194
|
+
* @param url - The URL to navigate to
|
|
195
|
+
* @param row - The row data
|
|
196
|
+
* @private
|
|
197
|
+
*/
|
|
198
|
+
_handleLinkClick(event, url, row) {
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
// Emit custom event for navigation
|
|
201
|
+
this.dispatchEvent(new CustomEvent('navigate', {
|
|
202
|
+
detail: { url, row },
|
|
203
|
+
bubbles: true,
|
|
204
|
+
composed: true
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get filtered rows based on search query
|
|
209
|
+
* @returns Filtered rows
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
get _filteredRows() {
|
|
213
|
+
if (!this._searchQuery || !this.searchable) {
|
|
214
|
+
return this.rows;
|
|
215
|
+
}
|
|
216
|
+
const query = this._searchQuery.toLowerCase();
|
|
217
|
+
return this.rows.filter(row => {
|
|
218
|
+
return this.columns.some(column => {
|
|
219
|
+
if (column.searchable === false) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
const value = this._getNestedValue(row, column.field);
|
|
223
|
+
return String(value).toLowerCase().includes(query);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get sorted rows
|
|
229
|
+
* @returns Sorted rows
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
get _sortedRows() {
|
|
233
|
+
if (!this._sortColumn || !this.sortable) {
|
|
234
|
+
return this._filteredRows;
|
|
235
|
+
}
|
|
236
|
+
const column = this.columns.find(col => col.field === this._sortColumn);
|
|
237
|
+
if (!column || column.sortable === false) {
|
|
238
|
+
return this._filteredRows;
|
|
239
|
+
}
|
|
240
|
+
return [...this._filteredRows].sort((a, b) => {
|
|
241
|
+
const aValue = this._getNestedValue(a, this._sortColumn);
|
|
242
|
+
const bValue = this._getNestedValue(b, this._sortColumn);
|
|
243
|
+
// Handle custom sort function
|
|
244
|
+
if (column.sortFn) {
|
|
245
|
+
return column.sortFn(a, b, this._sortDirection);
|
|
246
|
+
}
|
|
247
|
+
// Default sorting logic
|
|
248
|
+
let comparison = 0;
|
|
249
|
+
if (aValue < bValue) {
|
|
250
|
+
comparison = -1;
|
|
251
|
+
}
|
|
252
|
+
else if (aValue > bValue) {
|
|
253
|
+
comparison = 1;
|
|
254
|
+
}
|
|
255
|
+
return this._sortDirection === 'asc' ? comparison : -comparison;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Get paginated rows
|
|
260
|
+
* @returns Paginated rows
|
|
261
|
+
* @private
|
|
262
|
+
*/
|
|
263
|
+
get _paginatedRows() {
|
|
264
|
+
if (!this.paginated) {
|
|
265
|
+
return this._sortedRows;
|
|
266
|
+
}
|
|
267
|
+
const start = (this._currentPage - 1) * this.rowsPerPage;
|
|
268
|
+
const end = start + this.rowsPerPage;
|
|
269
|
+
return this._sortedRows.slice(start, end);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get total number of pages
|
|
273
|
+
* @returns Total pages
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
get _totalPages() {
|
|
277
|
+
if (!this.paginated) {
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
280
|
+
return Math.ceil(this._sortedRows.length / this.rowsPerPage);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get pagination info
|
|
284
|
+
* @returns Pagination info
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
get _paginationInfo() {
|
|
288
|
+
const start = (this._currentPage - 1) * this.rowsPerPage + 1;
|
|
289
|
+
const end = Math.min(this._currentPage * this.rowsPerPage, this._sortedRows.length);
|
|
290
|
+
return {
|
|
291
|
+
start,
|
|
292
|
+
end,
|
|
293
|
+
total: this._sortedRows.length
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Handle search input
|
|
298
|
+
* @param e - The input event
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
_handleSearch(e) {
|
|
302
|
+
const target = e.target;
|
|
303
|
+
this._searchQuery = target.value;
|
|
304
|
+
this._currentPage = 1;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Handle column sort
|
|
308
|
+
* @param columnField - The column field to sort by
|
|
309
|
+
* @private
|
|
310
|
+
*/
|
|
311
|
+
_handleSort(columnField) {
|
|
312
|
+
const column = this.columns.find(col => col.field === columnField);
|
|
313
|
+
if (!column || column.sortable === false || !this.sortable) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (this._sortColumn === columnField) {
|
|
317
|
+
// Toggle direction
|
|
318
|
+
this._sortDirection = this._sortDirection === 'asc' ? 'desc' : 'asc';
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// New column, default to ascending
|
|
322
|
+
this._sortColumn = columnField;
|
|
323
|
+
this._sortDirection = 'asc';
|
|
324
|
+
}
|
|
325
|
+
this._currentPage = 1;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Navigate to a specific page
|
|
329
|
+
* @param page - The page number
|
|
330
|
+
*/
|
|
331
|
+
goToPage(page) {
|
|
332
|
+
if (page >= 1 && page <= this._totalPages) {
|
|
333
|
+
this._currentPage = page;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Navigate to the next page
|
|
338
|
+
*/
|
|
339
|
+
nextPage() {
|
|
340
|
+
this.goToPage(this._currentPage + 1);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Navigate to the previous page
|
|
344
|
+
*/
|
|
345
|
+
prevPage() {
|
|
346
|
+
this.goToPage(this._currentPage - 1);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Reset search query
|
|
350
|
+
*/
|
|
351
|
+
resetSearch() {
|
|
352
|
+
this._searchQuery = '';
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Reset sort to default state
|
|
356
|
+
*/
|
|
357
|
+
resetSort() {
|
|
358
|
+
this._sortColumn = null;
|
|
359
|
+
this._sortDirection = 'asc';
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Render sort icon
|
|
363
|
+
* @param column - The column definition
|
|
364
|
+
* @returns TemplateResult
|
|
365
|
+
* @private
|
|
366
|
+
*/
|
|
367
|
+
_renderSortIcon(column) {
|
|
368
|
+
if (!this.sortable || column.sortable === false) {
|
|
369
|
+
return '';
|
|
370
|
+
}
|
|
371
|
+
const isSorted = this._sortColumn === column.field;
|
|
372
|
+
if (isSorted) {
|
|
373
|
+
if (this._sortDirection === 'asc') {
|
|
374
|
+
return html `
|
|
375
|
+
<iconify-icon class="data-table__sort-icon" icon="heroicons:chevron-up-20-solid"></iconify-icon>
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
return html `
|
|
380
|
+
<iconify-icon class="data-table__sort-icon" icon="heroicons:chevron-down-20-solid"></iconify-icon>
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return html `
|
|
385
|
+
<iconify-icon class="data-table__sort-icon" icon="heroicons:chevron-up-20-solid" style="opacity: 0.3"></iconify-icon>
|
|
386
|
+
`;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Render chevron left icon
|
|
390
|
+
* @returns TemplateResult
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
_renderChevronLeft() {
|
|
394
|
+
return html `
|
|
395
|
+
<iconify-icon class="data-table__pagination-icon" icon="heroicons:chevron-left-20-solid"></iconify-icon>
|
|
396
|
+
`;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Render chevron right icon
|
|
400
|
+
* @returns TemplateResult
|
|
401
|
+
* @private
|
|
402
|
+
*/
|
|
403
|
+
_renderChevronRight() {
|
|
404
|
+
return html `
|
|
405
|
+
<iconify-icon class="data-table__pagination-icon" icon="heroicons:chevron-right-20-solid"></iconify-icon>
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Render the component
|
|
410
|
+
* @returns TemplateResult
|
|
411
|
+
*/
|
|
412
|
+
render() {
|
|
413
|
+
return html `
|
|
414
|
+
<div class="data-table">
|
|
415
|
+
<!-- Search bar -->
|
|
416
|
+
${this.searchable ? html `
|
|
417
|
+
<div class="data-table__search">
|
|
418
|
+
<input
|
|
419
|
+
type="text"
|
|
420
|
+
class="data-table__search-input"
|
|
421
|
+
placeholder="Search..."
|
|
422
|
+
.value=${this._searchQuery}
|
|
423
|
+
@input=${this._handleSearch}
|
|
424
|
+
>
|
|
425
|
+
</div>
|
|
426
|
+
` : ''}
|
|
427
|
+
|
|
428
|
+
<!-- Loading state -->
|
|
429
|
+
${this.loading ? html `
|
|
430
|
+
<div class="data-table__loading">
|
|
431
|
+
<div class="data-table__spinner"></div>
|
|
432
|
+
<span>Loading...</span>
|
|
433
|
+
</div>
|
|
434
|
+
` : html `
|
|
435
|
+
<!-- Table -->
|
|
436
|
+
<div class="data-table__wrapper">
|
|
437
|
+
<table class="data-table__table">
|
|
438
|
+
<thead class="data-table__thead">
|
|
439
|
+
<tr>
|
|
440
|
+
${this.columns.map(column => html `
|
|
441
|
+
<th
|
|
442
|
+
class="data-table__th ${this.sortable && column.sortable !== false ? 'data-table__th--sortable' : ''} ${this._sortColumn === column.field ? 'data-table__th--sorted' : ''}"
|
|
443
|
+
style=${column.width ? `width: ${column.width}` : ''}
|
|
444
|
+
@click=${() => this._handleSort(column.field)}
|
|
445
|
+
>
|
|
446
|
+
<div class="data-table__th-content">
|
|
447
|
+
<span>${column.label}</span>
|
|
448
|
+
${this._renderSortIcon(column)}
|
|
449
|
+
</div>
|
|
450
|
+
</th>
|
|
451
|
+
`)}
|
|
452
|
+
${this.rowActions ? html `
|
|
453
|
+
<th class="data-table__th data-table__th--actions" style="width: ${this.rowActionsWidth}px"></th>
|
|
454
|
+
` : ''}
|
|
455
|
+
</tr>
|
|
456
|
+
</thead>
|
|
457
|
+
<tbody class="data-table__tbody">
|
|
458
|
+
${this._paginatedRows.length === 0 ? html `
|
|
459
|
+
<tr class="data-table__tr">
|
|
460
|
+
<td class="data-table__td data-table__td--empty" colspan=${this.rowActions ? this.columns.length + 1 : this.columns.length}>
|
|
461
|
+
<slot name="empty">
|
|
462
|
+
${this._searchQuery ? this.noResultsMessage : this.emptyMessage}
|
|
463
|
+
</slot>
|
|
464
|
+
</td>
|
|
465
|
+
</tr>
|
|
466
|
+
` : this._paginatedRows.map((row) => html `
|
|
467
|
+
<tr class="data-table__tr">
|
|
468
|
+
${this.columns.map(column => html `
|
|
469
|
+
<td class="data-table__td">
|
|
470
|
+
<slot name="cell:${column.field}" .row=${row} .value=${this._getNestedValue(row, column.field)} .column=${column}>
|
|
471
|
+
${this._renderCellContent(row, column)}
|
|
472
|
+
</slot>
|
|
473
|
+
</td>
|
|
474
|
+
`)}
|
|
475
|
+
${this.rowActions ? html `
|
|
476
|
+
<td class="data-table__td data-table__td--actions">
|
|
477
|
+
<slot name="actions" .row=${row}>
|
|
478
|
+
<action-menu .resource=${row}></action-menu>
|
|
479
|
+
</slot>
|
|
480
|
+
</td>
|
|
481
|
+
` : ''}
|
|
482
|
+
</tr>
|
|
483
|
+
`)}
|
|
484
|
+
</tbody>
|
|
485
|
+
</table>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<!-- Pagination -->
|
|
489
|
+
${this.paginated && !this.loading && this._totalPages > 1 ? html `
|
|
490
|
+
<div class="data-table__pagination">
|
|
491
|
+
<div class="data-table__pagination-info">
|
|
492
|
+
${this._paginationInfo.start}-${this._paginationInfo.end} of ${this._paginationInfo.total}
|
|
493
|
+
</div>
|
|
494
|
+
<div class="data-table__pagination-controls">
|
|
495
|
+
<button
|
|
496
|
+
class="data-table__pagination-btn"
|
|
497
|
+
?disabled=${this._currentPage === 1}
|
|
498
|
+
@click=${this.prevPage}
|
|
499
|
+
aria-label="Previous page"
|
|
500
|
+
>
|
|
501
|
+
${this._renderChevronLeft()}
|
|
502
|
+
</button>
|
|
503
|
+
|
|
504
|
+
<span class="data-table__pagination-current">
|
|
505
|
+
${this._currentPage} / ${this._totalPages}
|
|
506
|
+
</span>
|
|
507
|
+
|
|
508
|
+
<button
|
|
509
|
+
class="data-table__pagination-btn"
|
|
510
|
+
?disabled=${this._currentPage === this._totalPages}
|
|
511
|
+
@click=${this.nextPage}
|
|
512
|
+
aria-label="Next page"
|
|
513
|
+
>
|
|
514
|
+
${this._renderChevronRight()}
|
|
515
|
+
</button>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
` : ''}
|
|
519
|
+
`}
|
|
520
|
+
</div>
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
DataTable.styles = css `
|
|
525
|
+
:host {
|
|
526
|
+
display: block;
|
|
527
|
+
width: 100%;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.data-table {
|
|
531
|
+
display: flex;
|
|
532
|
+
flex-direction: column;
|
|
533
|
+
gap: 1rem;
|
|
534
|
+
width: 100%;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.data-table__search {
|
|
538
|
+
display: flex;
|
|
539
|
+
justify-content: flex-end;
|
|
540
|
+
padding: 0.5rem 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.data-table__search-input {
|
|
544
|
+
width: 100%;
|
|
545
|
+
max-width: 300px;
|
|
546
|
+
padding: 0.5rem 1rem;
|
|
547
|
+
border: 1px solid var(--border, #ddd);
|
|
548
|
+
border-radius: 4px;
|
|
549
|
+
background-color: var(--input-bg, #fff);
|
|
550
|
+
color: var(--input-text, #333);
|
|
551
|
+
font-size: 14px;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.data-table__search-input:focus {
|
|
555
|
+
outline: none;
|
|
556
|
+
border-color: var(--primary, #007bff);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.data-table__search-input::placeholder {
|
|
560
|
+
color: var(--input-placeholder, #999);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.data-table__loading {
|
|
564
|
+
display: flex;
|
|
565
|
+
flex-direction: column;
|
|
566
|
+
align-items: center;
|
|
567
|
+
justify-content: center;
|
|
568
|
+
padding: 3rem;
|
|
569
|
+
gap: 1rem;
|
|
570
|
+
color: var(--body-text, #333);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.data-table__spinner {
|
|
574
|
+
width: 40px;
|
|
575
|
+
height: 40px;
|
|
576
|
+
border: 4px solid var(--border, #ddd);
|
|
577
|
+
border-top-color: var(--primary, #007bff);
|
|
578
|
+
border-radius: 50%;
|
|
579
|
+
animation: spin 1s linear infinite;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
@keyframes spin {
|
|
583
|
+
to {
|
|
584
|
+
transform: rotate(360deg);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.data-table__wrapper {
|
|
589
|
+
border: 1px solid var(--border, #ddd);
|
|
590
|
+
border-radius: 4px;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.data-table__table {
|
|
594
|
+
width: 100%;
|
|
595
|
+
border-collapse: collapse;
|
|
596
|
+
background-color: var(--body-bg, #fff);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.data-table__thead {
|
|
600
|
+
background-color: var(--sortable-table-header-bg, #f8f9fa);
|
|
601
|
+
border-bottom: 1px solid var(--border, #ddd);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.data-table__th {
|
|
605
|
+
padding: 0.75rem 1rem;
|
|
606
|
+
text-align: left;
|
|
607
|
+
font-weight: 600;
|
|
608
|
+
color: var(--body-text, #333);
|
|
609
|
+
white-space: nowrap;
|
|
610
|
+
border-bottom: 1px solid var(--border, #ddd);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.data-table__th--sortable {
|
|
614
|
+
cursor: pointer;
|
|
615
|
+
user-select: none;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.data-table__th--sortable:hover {
|
|
619
|
+
background-color: var(--sortable-table-header-hover-bg, #e9ecef);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.data-table__th--sorted {
|
|
623
|
+
background-color: var(--sortable-table-header-sorted-bg, #e2e6ea);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.data-table__th--actions {
|
|
627
|
+
width: 40px;
|
|
628
|
+
padding: 0.75rem 0.5rem;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.data-table__th-content {
|
|
632
|
+
display: flex;
|
|
633
|
+
align-items: center;
|
|
634
|
+
gap: 0.5rem;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.data-table__sort-icon {
|
|
638
|
+
display: inline-flex;
|
|
639
|
+
align-items: center;
|
|
640
|
+
color: var(--muted, #6c757d);
|
|
641
|
+
width: 16px;
|
|
642
|
+
height: 16px;
|
|
643
|
+
font-size: 16px;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.data-table__tbody {
|
|
647
|
+
background-color: var(--body-bg, #fff);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.data-table__tr {
|
|
651
|
+
border-bottom: 1px solid var(--border, #ddd);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.data-table__tr:hover {
|
|
655
|
+
background-color: var(--sortable-table-row-hover-bg, #f8f9fa);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.data-table__tr:last-child {
|
|
659
|
+
border-bottom: none;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.data-table__td {
|
|
663
|
+
padding: 0.75rem 1rem;
|
|
664
|
+
color: var(--body-text, #333);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.data-table__td a {
|
|
668
|
+
color: var(--link, #007bff);
|
|
669
|
+
text-decoration: none;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.data-table__td a:hover {
|
|
673
|
+
text-decoration: underline;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.data-table__td--empty {
|
|
677
|
+
text-align: center;
|
|
678
|
+
padding: 2rem;
|
|
679
|
+
color: var(--muted, #6c757d);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.data-table__td--actions {
|
|
683
|
+
width: 40px;
|
|
684
|
+
padding: 0.5rem;
|
|
685
|
+
text-align: center;
|
|
686
|
+
vertical-align: middle;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.data-table__pagination {
|
|
690
|
+
display: flex;
|
|
691
|
+
justify-content: space-between;
|
|
692
|
+
align-items: center;
|
|
693
|
+
padding: 1rem 0;
|
|
694
|
+
gap: 1rem;
|
|
695
|
+
flex-wrap: wrap;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.data-table__pagination-info {
|
|
699
|
+
color: var(--muted, #6c757d);
|
|
700
|
+
font-size: 13px;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.data-table__pagination-controls {
|
|
704
|
+
display: flex;
|
|
705
|
+
align-items: center;
|
|
706
|
+
gap: 1rem;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.data-table__pagination-current {
|
|
710
|
+
color: var(--body-text, #333);
|
|
711
|
+
font-size: 13px;
|
|
712
|
+
min-width: 60px;
|
|
713
|
+
text-align: center;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.data-table__pagination-btn {
|
|
717
|
+
display: flex;
|
|
718
|
+
align-items: center;
|
|
719
|
+
justify-content: center;
|
|
720
|
+
width: 32px;
|
|
721
|
+
height: 32px;
|
|
722
|
+
padding: 0;
|
|
723
|
+
border: 1px solid var(--border, #ddd);
|
|
724
|
+
border-radius: 4px;
|
|
725
|
+
background-color: var(--body-bg, #fff);
|
|
726
|
+
color: var(--body-text, #333);
|
|
727
|
+
cursor: pointer;
|
|
728
|
+
transition: all 0.2s;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.data-table__pagination-btn:hover:not(:disabled) {
|
|
732
|
+
background-color: var(--sortable-table-row-hover-bg, #f8f9fa);
|
|
733
|
+
border-color: var(--link, #007bff);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.data-table__pagination-btn:disabled {
|
|
737
|
+
opacity: 0.4;
|
|
738
|
+
cursor: not-allowed;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.data-table__pagination-icon {
|
|
742
|
+
width: 16px;
|
|
743
|
+
height: 16px;
|
|
744
|
+
}
|
|
745
|
+
`;
|
|
746
|
+
__decorate([
|
|
747
|
+
property({ type: Array })
|
|
748
|
+
], DataTable.prototype, "columns", void 0);
|
|
749
|
+
__decorate([
|
|
750
|
+
property({ type: Array })
|
|
751
|
+
], DataTable.prototype, "rows", void 0);
|
|
752
|
+
__decorate([
|
|
753
|
+
property({ type: Number, attribute: 'rows-per-page' })
|
|
754
|
+
], DataTable.prototype, "rowsPerPage", void 0);
|
|
755
|
+
__decorate([
|
|
756
|
+
property({ type: Boolean })
|
|
757
|
+
], DataTable.prototype, "searchable", void 0);
|
|
758
|
+
__decorate([
|
|
759
|
+
property({ type: Boolean })
|
|
760
|
+
], DataTable.prototype, "sortable", void 0);
|
|
761
|
+
__decorate([
|
|
762
|
+
property({ type: Boolean })
|
|
763
|
+
], DataTable.prototype, "paginated", void 0);
|
|
764
|
+
__decorate([
|
|
765
|
+
property({ type: Boolean })
|
|
766
|
+
], DataTable.prototype, "loading", void 0);
|
|
767
|
+
__decorate([
|
|
768
|
+
property({ type: String, attribute: 'key-field' })
|
|
769
|
+
], DataTable.prototype, "keyField", void 0);
|
|
770
|
+
__decorate([
|
|
771
|
+
property({ type: Boolean, attribute: 'row-actions' })
|
|
772
|
+
], DataTable.prototype, "rowActions", void 0);
|
|
773
|
+
__decorate([
|
|
774
|
+
property({ type: Number, attribute: 'row-actions-width' })
|
|
775
|
+
], DataTable.prototype, "rowActionsWidth", void 0);
|
|
776
|
+
__decorate([
|
|
777
|
+
property({ type: String, attribute: 'empty-message' })
|
|
778
|
+
], DataTable.prototype, "emptyMessage", void 0);
|
|
779
|
+
__decorate([
|
|
780
|
+
property({ type: String, attribute: 'no-results-message' })
|
|
781
|
+
], DataTable.prototype, "noResultsMessage", void 0);
|
|
782
|
+
__decorate([
|
|
783
|
+
state()
|
|
784
|
+
], DataTable.prototype, "_searchQuery", void 0);
|
|
785
|
+
__decorate([
|
|
786
|
+
state()
|
|
787
|
+
], DataTable.prototype, "_currentPage", void 0);
|
|
788
|
+
__decorate([
|
|
789
|
+
state()
|
|
790
|
+
], DataTable.prototype, "_sortColumn", void 0);
|
|
791
|
+
__decorate([
|
|
792
|
+
state()
|
|
793
|
+
], DataTable.prototype, "_sortDirection", void 0);
|
|
794
|
+
// Register the element
|
|
795
|
+
customElements.define('data-table', DataTable);
|
|
796
|
+
//# sourceMappingURL=data-table.js.map
|