@kodaris/krubble-components 1.0.51 → 1.0.53
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/custom-elements.json +1811 -192
- package/dist/button/button.js +1 -1
- package/dist/form/select-field/select-field.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/krubble-components.bundled.js +3749 -1866
- package/dist/krubble-components.bundled.js.map +1 -1
- package/dist/krubble-components.bundled.min.js +874 -396
- package/dist/krubble-components.bundled.min.js.map +1 -1
- package/dist/krubble-components.umd.js +4564 -2678
- package/dist/krubble-components.umd.js.map +1 -1
- package/dist/krubble-components.umd.min.js +876 -398
- package/dist/krubble-components.umd.min.js.map +1 -1
- package/dist/table/query.d.ts +63 -0
- package/dist/table/query.d.ts.map +1 -0
- package/dist/table/query.js +1015 -0
- package/dist/table/query.js.map +1 -0
- package/dist/table/table.d.ts +63 -10
- package/dist/table/table.d.ts.map +1 -1
- package/dist/table/table.js +986 -113
- package/dist/table/table.js.map +1 -1
- package/package.json +1 -1
package/dist/table/table.js
CHANGED
|
@@ -11,21 +11,25 @@ import { styleMap } from 'lit/directives/style-map.js';
|
|
|
11
11
|
import { krBaseCSS } from '../style/base.js';
|
|
12
12
|
import '../button/button.js';
|
|
13
13
|
import { KRSnackbar } from '../snackbar/snackbar.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
import '../form/select-field/select-field.js';
|
|
15
|
+
import '../form/select-field/select-option.js';
|
|
16
|
+
import { KRQuery, getOperatorsForType, termify } from './query.js';
|
|
17
|
+
import '../tabs/tabs.js';
|
|
18
|
+
import '../tabs/tab.js';
|
|
19
|
+
/** Internal table model built from user-provided def. */
|
|
20
|
+
class KRTableModel {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.title = '';
|
|
23
|
+
this.actions = [];
|
|
24
|
+
this.columns = [];
|
|
25
|
+
this.displayedColumns = [];
|
|
26
|
+
this.data = null;
|
|
27
|
+
this.dataSource = null;
|
|
28
|
+
this.refreshInterval = 0;
|
|
29
|
+
this.pageSize = 0;
|
|
30
|
+
this.rowClickable = false;
|
|
31
|
+
this.rowHref = null;
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
let KRTable = class KRTable extends LitElement {
|
|
31
35
|
constructor() {
|
|
@@ -47,25 +51,33 @@ let KRTable = class KRTable extends LitElement {
|
|
|
47
51
|
this._canScrollRight = false;
|
|
48
52
|
this._canScrollHorizontal = false;
|
|
49
53
|
this._columnPickerOpen = false;
|
|
50
|
-
this.
|
|
54
|
+
this._filterPanelOpened = null;
|
|
55
|
+
this._filterPanelTab = 'filter';
|
|
56
|
+
this._buckets = new Map();
|
|
57
|
+
this._filterPanelPos = { top: 0, left: 0 };
|
|
51
58
|
this._resizing = null;
|
|
52
59
|
this._resizeObserver = null;
|
|
53
60
|
this._searchPositionLocked = false;
|
|
54
|
-
this.
|
|
61
|
+
this._model = new KRTableModel();
|
|
55
62
|
this.def = { columns: [] };
|
|
56
|
-
this.
|
|
57
|
-
if (!this._columnPickerOpen)
|
|
58
|
-
return;
|
|
63
|
+
this._handleClickOutside = (e) => {
|
|
59
64
|
const path = e.composedPath();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
if (this._columnPickerOpen) {
|
|
66
|
+
const picker = this.shadowRoot?.querySelector('.column-picker-wrapper');
|
|
67
|
+
if (picker && !path.includes(picker)) {
|
|
68
|
+
this._columnPickerOpen = false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (this._filterPanelOpened) {
|
|
72
|
+
if (!path.some((el) => el.classList?.contains('filter-panel'))) {
|
|
73
|
+
this._handleFilterApply();
|
|
74
|
+
}
|
|
63
75
|
}
|
|
64
76
|
};
|
|
65
77
|
this._handleResizeMove = (e) => {
|
|
66
78
|
if (!this._resizing)
|
|
67
79
|
return;
|
|
68
|
-
const col = this.
|
|
80
|
+
const col = this._model.columns.find(c => c.id === this._resizing.columnId);
|
|
69
81
|
if (col) {
|
|
70
82
|
const newWidth = this._resizing.startWidth + (e.clientX - this._resizing.startX);
|
|
71
83
|
col.width = `${Math.min(900, Math.max(50, newWidth))}px`;
|
|
@@ -84,7 +96,7 @@ let KRTable = class KRTable extends LitElement {
|
|
|
84
96
|
this.classList.toggle('kr-table--scroll-edge', this._scrollStyle === 'edge');
|
|
85
97
|
this._fetch();
|
|
86
98
|
this._initRefresh();
|
|
87
|
-
document.addEventListener('click', this.
|
|
99
|
+
document.addEventListener('click', this._handleClickOutside);
|
|
88
100
|
this._resizeObserver = new ResizeObserver(() => {
|
|
89
101
|
// Unlock and recalculate on resize since layout changes
|
|
90
102
|
this._searchPositionLocked = false;
|
|
@@ -95,22 +107,71 @@ let KRTable = class KRTable extends LitElement {
|
|
|
95
107
|
disconnectedCallback() {
|
|
96
108
|
super.disconnectedCallback();
|
|
97
109
|
clearInterval(this._refreshTimer);
|
|
98
|
-
document.removeEventListener('click', this.
|
|
110
|
+
document.removeEventListener('click', this._handleClickOutside);
|
|
99
111
|
this._resizeObserver?.disconnect();
|
|
100
112
|
}
|
|
101
113
|
willUpdate(changedProperties) {
|
|
102
114
|
if (changedProperties.has('def')) {
|
|
103
|
-
//
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
// Build internal model from user-provided def
|
|
116
|
+
this._model = new KRTableModel();
|
|
117
|
+
if (this.def.title) {
|
|
118
|
+
this._model.title = this.def.title;
|
|
119
|
+
}
|
|
120
|
+
if (this.def.actions) {
|
|
121
|
+
this._model.actions = this.def.actions;
|
|
122
|
+
}
|
|
123
|
+
if (this.def.data) {
|
|
124
|
+
this._model.data = this.def.data;
|
|
125
|
+
}
|
|
126
|
+
if (this.def.dataSource) {
|
|
127
|
+
this._model.dataSource = this.def.dataSource;
|
|
128
|
+
}
|
|
129
|
+
if (typeof this.def.refreshInterval === 'number') {
|
|
130
|
+
this._model.refreshInterval = this.def.refreshInterval;
|
|
131
|
+
}
|
|
132
|
+
if (typeof this.def.pageSize === 'number') {
|
|
133
|
+
this._model.pageSize = this.def.pageSize;
|
|
134
|
+
}
|
|
135
|
+
if (this.def.rowClickable) {
|
|
136
|
+
this._model.rowClickable = this.def.rowClickable;
|
|
137
|
+
}
|
|
138
|
+
if (this.def.rowHref) {
|
|
139
|
+
this._model.rowHref = this.def.rowHref;
|
|
140
|
+
}
|
|
141
|
+
this._model.columns = this.def.columns.map(col => {
|
|
142
|
+
const column = {
|
|
143
|
+
...col,
|
|
144
|
+
filter: null
|
|
145
|
+
};
|
|
146
|
+
if (!column.type) {
|
|
147
|
+
column.type = 'string';
|
|
148
|
+
}
|
|
149
|
+
if (column.type === 'actions') {
|
|
150
|
+
column.label = col.label ?? '';
|
|
151
|
+
column.sticky = 'right';
|
|
152
|
+
column.resizable = false;
|
|
153
|
+
return column;
|
|
154
|
+
}
|
|
155
|
+
if (col.filterable || col.facetable) {
|
|
156
|
+
column.filter = new KRQuery();
|
|
157
|
+
column.filter.field = col.id;
|
|
158
|
+
column.filter.type = column.type;
|
|
159
|
+
if (col.facetable && !col.filterable) {
|
|
160
|
+
column.filter.operator = 'in';
|
|
161
|
+
column.filter.value = [];
|
|
162
|
+
}
|
|
163
|
+
else if (column.filter.type === 'string') {
|
|
164
|
+
column.filter.operator = 'contains';
|
|
109
165
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
113
|
-
|
|
166
|
+
}
|
|
167
|
+
return column;
|
|
168
|
+
});
|
|
169
|
+
if (this.def.displayedColumns) {
|
|
170
|
+
this._model.displayedColumns = this.def.displayedColumns;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
this._model.displayedColumns = this._model.columns.map(c => c.id);
|
|
174
|
+
}
|
|
114
175
|
this._fetch();
|
|
115
176
|
this._initRefresh();
|
|
116
177
|
}
|
|
@@ -175,6 +236,73 @@ let KRTable = class KRTable extends LitElement {
|
|
|
175
236
|
// ----------------------------------------------------------------------------
|
|
176
237
|
// Data Fetching
|
|
177
238
|
// ----------------------------------------------------------------------------
|
|
239
|
+
_toSolrData() {
|
|
240
|
+
const request = {
|
|
241
|
+
page: this._page - 1,
|
|
242
|
+
size: this._pageSize,
|
|
243
|
+
sorts: [],
|
|
244
|
+
filterFields: [],
|
|
245
|
+
queryFields: [],
|
|
246
|
+
facetFields: []
|
|
247
|
+
};
|
|
248
|
+
for (const col of this._model.columns) {
|
|
249
|
+
if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const filterData = col.filter.toSolrData();
|
|
253
|
+
if (col.facetable && col.filter.operator === 'in') {
|
|
254
|
+
filterData.tagged = true;
|
|
255
|
+
}
|
|
256
|
+
request.filterFields.push(filterData);
|
|
257
|
+
}
|
|
258
|
+
for (const col of this._model.columns) {
|
|
259
|
+
if (!col.facetable) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
request.facetFields.push({
|
|
263
|
+
name: col.id,
|
|
264
|
+
type: 'FIELD',
|
|
265
|
+
limit: 100,
|
|
266
|
+
sort: 'count',
|
|
267
|
+
minimumCount: 1
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (this._searchQuery?.trim().length) {
|
|
271
|
+
request.queryFields.push({
|
|
272
|
+
name: '_text_',
|
|
273
|
+
operation: 'IS',
|
|
274
|
+
value: termify(this._searchQuery, false)
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return request;
|
|
278
|
+
}
|
|
279
|
+
_toDbParams() {
|
|
280
|
+
const request = {
|
|
281
|
+
page: this._page - 1,
|
|
282
|
+
size: this._pageSize,
|
|
283
|
+
sorts: [],
|
|
284
|
+
filterFields: [],
|
|
285
|
+
queryFields: [],
|
|
286
|
+
facetFields: []
|
|
287
|
+
};
|
|
288
|
+
for (const col of this._model.columns) {
|
|
289
|
+
if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
request.filterFields.push(col.filter.toDbParams());
|
|
293
|
+
}
|
|
294
|
+
if (this._searchQuery?.trim().length) {
|
|
295
|
+
this._model.columns.filter(col => col.searchable).forEach(col => {
|
|
296
|
+
request.queryFields.push({
|
|
297
|
+
name: col.id,
|
|
298
|
+
operation: 'CONTAINS',
|
|
299
|
+
value: this._searchQuery,
|
|
300
|
+
and: false
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return request;
|
|
305
|
+
}
|
|
178
306
|
/**
|
|
179
307
|
* Fetches data from the API and updates the table.
|
|
180
308
|
* Shows a loading spinner while fetching, then displays rows on success
|
|
@@ -182,55 +310,27 @@ let KRTable = class KRTable extends LitElement {
|
|
|
182
310
|
* Request/response format depends on dataSource.mode (solr, opensearch, db).
|
|
183
311
|
*/
|
|
184
312
|
_fetch() {
|
|
185
|
-
if (
|
|
313
|
+
if (this._model.data) {
|
|
314
|
+
this._data = this._model.data;
|
|
315
|
+
this._totalItems = this._model.data.length;
|
|
316
|
+
this._totalPages = Math.ceil(this._model.data.length / this._pageSize);
|
|
317
|
+
this._dataState = 'success';
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (!this._model.dataSource)
|
|
186
321
|
return;
|
|
187
322
|
this._dataState = 'loading';
|
|
188
|
-
// Build request based on mode
|
|
189
323
|
let request;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
page: this._page - 1,
|
|
196
|
-
size: this._pageSize,
|
|
197
|
-
sorts: [],
|
|
198
|
-
filterFields: [],
|
|
199
|
-
queryFields: [],
|
|
200
|
-
facetFields: []
|
|
201
|
-
};
|
|
202
|
-
if (this._searchQuery?.trim().length) {
|
|
203
|
-
this._def.columns.filter(col => col.searchable).forEach(col => {
|
|
204
|
-
request.queryFields.push({
|
|
205
|
-
name: col.id,
|
|
206
|
-
operation: 'CONTAINS',
|
|
207
|
-
value: this._searchQuery,
|
|
208
|
-
and: false
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
break;
|
|
213
|
-
default: // solr
|
|
214
|
-
request = {
|
|
215
|
-
page: this._page - 1,
|
|
216
|
-
size: this._pageSize,
|
|
217
|
-
sorts: [],
|
|
218
|
-
filterFields: [],
|
|
219
|
-
queryFields: [],
|
|
220
|
-
facetFields: []
|
|
221
|
-
};
|
|
222
|
-
if (this._searchQuery?.trim().length) {
|
|
223
|
-
request.queryFields.push({
|
|
224
|
-
name: '_text_',
|
|
225
|
-
operation: 'IS',
|
|
226
|
-
value: escapeSolrQuery(this._searchQuery)
|
|
227
|
-
});
|
|
228
|
-
}
|
|
324
|
+
if (this._model.dataSource.mode === 'db') {
|
|
325
|
+
request = this._toDbParams();
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
request = this._toSolrData();
|
|
229
329
|
}
|
|
230
|
-
this.
|
|
330
|
+
this._model.dataSource.fetch(request)
|
|
231
331
|
.then(response => {
|
|
232
332
|
// Parse response based on mode
|
|
233
|
-
switch (this.
|
|
333
|
+
switch (this._model.dataSource?.mode) {
|
|
234
334
|
case 'opensearch': {
|
|
235
335
|
throw Error('Opensearch not supported yet');
|
|
236
336
|
break;
|
|
@@ -249,6 +349,7 @@ let KRTable = class KRTable extends LitElement {
|
|
|
249
349
|
this._totalItems = res.data.totalElements;
|
|
250
350
|
this._totalPages = res.data.totalPages;
|
|
251
351
|
this._pageSize = res.data.size;
|
|
352
|
+
this._parseFacetResults(res);
|
|
252
353
|
}
|
|
253
354
|
}
|
|
254
355
|
this._dataState = 'success';
|
|
@@ -262,6 +363,61 @@ let KRTable = class KRTable extends LitElement {
|
|
|
262
363
|
});
|
|
263
364
|
});
|
|
264
365
|
}
|
|
366
|
+
_parseFacetResults(response) {
|
|
367
|
+
if (!response.data.facetFields) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
for (const col of this._model.columns) {
|
|
371
|
+
if (!col.facetable) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const rawBuckets = response.data.facetFields[col.id];
|
|
375
|
+
if (!rawBuckets) {
|
|
376
|
+
this._buckets.set(col.id, []);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const buckets = [];
|
|
380
|
+
for (const raw of rawBuckets) {
|
|
381
|
+
// Solr returns boolean facet values as strings — coerce to actual booleans
|
|
382
|
+
// so they match the filter values stored by toggle().
|
|
383
|
+
let val = raw.name;
|
|
384
|
+
if (col.type === 'boolean' && typeof raw.name === 'string') {
|
|
385
|
+
if (raw.name === 'true') {
|
|
386
|
+
val = true;
|
|
387
|
+
}
|
|
388
|
+
else if (raw.name === 'false') {
|
|
389
|
+
val = false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (raw.name === null && raw.count > 0) {
|
|
393
|
+
buckets.unshift({
|
|
394
|
+
val: null,
|
|
395
|
+
count: raw.count
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (raw.name !== null) {
|
|
399
|
+
buckets.push({
|
|
400
|
+
val: val,
|
|
401
|
+
count: raw.count
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Bucket sync: ensure selected values appear even with 0 results
|
|
406
|
+
if (col.filter && col.filter.operator === 'in' && Array.isArray(col.filter.value)) {
|
|
407
|
+
for (const selectedVal of col.filter.value) {
|
|
408
|
+
if (!buckets.some(b => b.val === selectedVal)) {
|
|
409
|
+
buckets.push({
|
|
410
|
+
val: selectedVal,
|
|
411
|
+
count: 0
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
this._buckets.set(col.id, buckets);
|
|
417
|
+
}
|
|
418
|
+
// Trigger re-render since Map mutation doesn't trigger Lit updates
|
|
419
|
+
this._buckets = new Map(this._buckets);
|
|
420
|
+
}
|
|
265
421
|
/**
|
|
266
422
|
* Sets up auto-refresh so the table automatically fetches fresh data
|
|
267
423
|
* at a regular interval (useful for dashboards, monitoring views).
|
|
@@ -269,10 +425,10 @@ let KRTable = class KRTable extends LitElement {
|
|
|
269
425
|
*/
|
|
270
426
|
_initRefresh() {
|
|
271
427
|
clearInterval(this._refreshTimer);
|
|
272
|
-
if (this.
|
|
428
|
+
if (this._model.refreshInterval && this._model.refreshInterval > 0) {
|
|
273
429
|
this._refreshTimer = window.setInterval(() => {
|
|
274
430
|
this._fetch();
|
|
275
|
-
}, this.
|
|
431
|
+
}, this._model.refreshInterval);
|
|
276
432
|
}
|
|
277
433
|
}
|
|
278
434
|
_handleSearch(e) {
|
|
@@ -331,23 +487,23 @@ let KRTable = class KRTable extends LitElement {
|
|
|
331
487
|
this._columnPickerOpen = !this._columnPickerOpen;
|
|
332
488
|
}
|
|
333
489
|
_toggleColumn(columnId) {
|
|
334
|
-
if (this.
|
|
335
|
-
this.
|
|
490
|
+
if (this._model.displayedColumns.includes(columnId)) {
|
|
491
|
+
this._model.displayedColumns = this._model.displayedColumns.filter(id => id !== columnId);
|
|
336
492
|
}
|
|
337
493
|
else {
|
|
338
|
-
this.
|
|
494
|
+
this._model.displayedColumns = [...this._model.displayedColumns, columnId];
|
|
339
495
|
}
|
|
340
496
|
}
|
|
341
497
|
// Clear any existing text selection on mousedown so we only detect
|
|
342
498
|
// selections made during this click gesture, not stale selections from elsewhere
|
|
343
499
|
_handleRowMouseDown() {
|
|
344
|
-
if (!this.
|
|
500
|
+
if (!this._model.rowClickable) {
|
|
345
501
|
return;
|
|
346
502
|
}
|
|
347
503
|
window.getSelection()?.removeAllRanges();
|
|
348
504
|
}
|
|
349
505
|
_handleRowClick(row, rowIndex) {
|
|
350
|
-
if (!this.
|
|
506
|
+
if (!this._model.rowClickable) {
|
|
351
507
|
return;
|
|
352
508
|
}
|
|
353
509
|
const selection = window.getSelection();
|
|
@@ -366,8 +522,8 @@ let KRTable = class KRTable extends LitElement {
|
|
|
366
522
|
// back to its original position in the column definition.
|
|
367
523
|
// Actions columns are always moved to the end.
|
|
368
524
|
getDisplayedColumns() {
|
|
369
|
-
return this.
|
|
370
|
-
.map(id => this.
|
|
525
|
+
return this._model.displayedColumns
|
|
526
|
+
.map(id => this._model.columns.find(col => col.id === id))
|
|
371
527
|
.sort((a, b) => {
|
|
372
528
|
if (a.type === 'actions' && b.type !== 'actions')
|
|
373
529
|
return 1;
|
|
@@ -436,6 +592,125 @@ let KRTable = class KRTable extends LitElement {
|
|
|
436
592
|
}));
|
|
437
593
|
}
|
|
438
594
|
// ----------------------------------------------------------------------------
|
|
595
|
+
// Filter Handlers
|
|
596
|
+
// ----------------------------------------------------------------------------
|
|
597
|
+
_handleKqlChange(e, column) {
|
|
598
|
+
const kql = e.target.value.trim();
|
|
599
|
+
if (!kql) {
|
|
600
|
+
column.filter.clear();
|
|
601
|
+
this.requestUpdate();
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
column.filter.setKql(kql);
|
|
605
|
+
this.requestUpdate();
|
|
606
|
+
if (!column.filter.isValid()) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
this._page = 1;
|
|
611
|
+
this._fetch();
|
|
612
|
+
}
|
|
613
|
+
_handleFilterPanelToggle(e, column) {
|
|
614
|
+
e.stopPropagation();
|
|
615
|
+
if (this._filterPanelOpened === column.id) {
|
|
616
|
+
this._filterPanelOpened = null;
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
620
|
+
this._filterPanelPos = { top: rect.bottom + 4, left: rect.left };
|
|
621
|
+
this._filterPanelOpened = column.id;
|
|
622
|
+
if (column.facetable) {
|
|
623
|
+
this._filterPanelTab = 'counts';
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
this._filterPanelTab = 'filter';
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
_handleKqlClear(column) {
|
|
631
|
+
column.filter.clear();
|
|
632
|
+
this._page = 1;
|
|
633
|
+
this._fetch();
|
|
634
|
+
}
|
|
635
|
+
_handleFilterClear() {
|
|
636
|
+
const column = this._model.columns.find(c => c.id === this._filterPanelOpened);
|
|
637
|
+
if (column) {
|
|
638
|
+
column.filter.clear();
|
|
639
|
+
if (column.facetable && !column.filterable) {
|
|
640
|
+
column.filter.operator = 'in';
|
|
641
|
+
column.filter.value = [];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
this._filterPanelOpened = null;
|
|
645
|
+
this._page = 1;
|
|
646
|
+
this._fetch();
|
|
647
|
+
}
|
|
648
|
+
_handleFilterTextKeydown(e, column) {
|
|
649
|
+
if (e.key === 'Enter') {
|
|
650
|
+
e.preventDefault();
|
|
651
|
+
this._handleFilterApply();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
_handleOperatorChange(e, column) {
|
|
655
|
+
column.filter.setOperator(e.target.value);
|
|
656
|
+
this.requestUpdate();
|
|
657
|
+
}
|
|
658
|
+
_handleFilterStringChange(e, column) {
|
|
659
|
+
column.filter.setValue(e.target.value);
|
|
660
|
+
this.requestUpdate();
|
|
661
|
+
}
|
|
662
|
+
_handleFilterNumberChange(e, column) {
|
|
663
|
+
column.filter.setValue(Number(e.target.value));
|
|
664
|
+
this.requestUpdate();
|
|
665
|
+
}
|
|
666
|
+
_handleFilterDateChange(e, column) {
|
|
667
|
+
column.filter.setValue(new Date(e.target.value), 'day');
|
|
668
|
+
this.requestUpdate();
|
|
669
|
+
}
|
|
670
|
+
_handleFilterBooleanChange(e, column) {
|
|
671
|
+
column.filter.setValue(e.target.value === 'true');
|
|
672
|
+
this.requestUpdate();
|
|
673
|
+
}
|
|
674
|
+
_handleFilterDateStartChange(e, column) {
|
|
675
|
+
column.filter.setStart(new Date(e.target.value), 'day');
|
|
676
|
+
this.requestUpdate();
|
|
677
|
+
}
|
|
678
|
+
_handleFilterDateEndChange(e, column) {
|
|
679
|
+
column.filter.setEnd(new Date(e.target.value), 'day');
|
|
680
|
+
this.requestUpdate();
|
|
681
|
+
}
|
|
682
|
+
_handleFilterNumberStartChange(e, column) {
|
|
683
|
+
column.filter.setStart(Number(e.target.value));
|
|
684
|
+
this.requestUpdate();
|
|
685
|
+
}
|
|
686
|
+
_handleFilterNumberEndChange(e, column) {
|
|
687
|
+
column.filter.setEnd(Number(e.target.value));
|
|
688
|
+
this.requestUpdate();
|
|
689
|
+
}
|
|
690
|
+
_handleFilterListChange(e, column) {
|
|
691
|
+
const items = e.target.value.split(',').map((v) => v.trim()).filter((v) => v !== '');
|
|
692
|
+
if (column.type === 'number') {
|
|
693
|
+
column.filter.setValue(items.map((v) => Number(v)));
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
column.filter.setValue(items);
|
|
697
|
+
}
|
|
698
|
+
this.requestUpdate();
|
|
699
|
+
}
|
|
700
|
+
_handleFilterApply() {
|
|
701
|
+
this._filterPanelOpened = null;
|
|
702
|
+
this._page = 1;
|
|
703
|
+
this._fetch();
|
|
704
|
+
}
|
|
705
|
+
_handleFilterPanelTabChange(e) {
|
|
706
|
+
this._filterPanelTab = e.detail.activeTabId;
|
|
707
|
+
}
|
|
708
|
+
_handleBucketToggle(e, column, bucket) {
|
|
709
|
+
column.filter.toggle(bucket.val);
|
|
710
|
+
this._page = 1;
|
|
711
|
+
this._fetch();
|
|
712
|
+
}
|
|
713
|
+
// ----------------------------------------------------------------------------
|
|
439
714
|
// Rendering
|
|
440
715
|
// ----------------------------------------------------------------------------
|
|
441
716
|
_renderCellContent(column, row, rowIndex) {
|
|
@@ -449,11 +724,10 @@ let KRTable = class KRTable extends LitElement {
|
|
|
449
724
|
}
|
|
450
725
|
switch (column.type) {
|
|
451
726
|
case 'number':
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
: String(value);
|
|
727
|
+
if (column.format === 'currency' && typeof value === 'number') {
|
|
728
|
+
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
|
|
729
|
+
}
|
|
730
|
+
return String(value);
|
|
457
731
|
case 'date': {
|
|
458
732
|
let date;
|
|
459
733
|
if (value instanceof Date) {
|
|
@@ -468,10 +742,11 @@ let KRTable = class KRTable extends LitElement {
|
|
|
468
742
|
else {
|
|
469
743
|
date = new Date(value);
|
|
470
744
|
}
|
|
471
|
-
// Show date and time for datetime values
|
|
745
|
+
// Show date and time for datetime values in UTC
|
|
472
746
|
return date.toLocaleString(undefined, {
|
|
473
747
|
year: 'numeric', month: 'short', day: 'numeric',
|
|
474
|
-
hour: 'numeric', minute: '2-digit'
|
|
748
|
+
hour: 'numeric', minute: '2-digit',
|
|
749
|
+
timeZone: 'UTC'
|
|
475
750
|
});
|
|
476
751
|
}
|
|
477
752
|
case 'boolean':
|
|
@@ -588,13 +863,13 @@ let KRTable = class KRTable extends LitElement {
|
|
|
588
863
|
* Hidden when there's no title, no actions, and data fits on one page.
|
|
589
864
|
*/
|
|
590
865
|
_renderHeader() {
|
|
591
|
-
if (!this.
|
|
866
|
+
if (!this._model.title && !this._model.actions?.length && this._totalPages <= 1) {
|
|
592
867
|
return nothing;
|
|
593
868
|
}
|
|
594
869
|
return html `
|
|
595
870
|
<div class="header">
|
|
596
|
-
<div class="title">${this.
|
|
597
|
-
${this.
|
|
871
|
+
<div class="title">${this._model.title ?? ''}</div>
|
|
872
|
+
${this._model.dataSource?.mode === 'db' && !this._model.columns.some(col => col.searchable) ? html `<div class="search"></div>` : html `
|
|
598
873
|
<div class="search">
|
|
599
874
|
<!-- TODO: Saved views dropdown
|
|
600
875
|
<div class="views">
|
|
@@ -624,9 +899,9 @@ let KRTable = class KRTable extends LitElement {
|
|
|
624
899
|
<svg viewBox="0 -960 960 960" fill="currentColor"><path d="M121-280v-400q0-33 23.5-56.5T201-760h559q33 0 56.5 23.5T840-680v400q0 33-23.5 56.5T760-200H201q-33 0-56.5-23.5T121-280Zm79 0h133v-400H200v400Zm213 0h133v-400H413v400Zm213 0h133v-400H626v400Z"/></svg>
|
|
625
900
|
</span>
|
|
626
901
|
<div class="column-picker ${this._columnPickerOpen ? 'open' : ''}">
|
|
627
|
-
${[...this.
|
|
902
|
+
${[...this._model.columns].filter(col => col.type !== 'actions').sort((a, b) => (a.label ?? a.id).localeCompare(b.label ?? b.id)).map(col => html `
|
|
628
903
|
<div class="column-picker-item" @click=${() => this._toggleColumn(col.id)}>
|
|
629
|
-
<div class="column-picker-checkbox ${this.
|
|
904
|
+
<div class="column-picker-checkbox ${this._model.displayedColumns.includes(col.id) ? 'checked' : ''}">
|
|
630
905
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
|
631
906
|
</div>
|
|
632
907
|
<span class="column-picker-label">${col.label ?? col.id}</span>
|
|
@@ -634,19 +909,19 @@ let KRTable = class KRTable extends LitElement {
|
|
|
634
909
|
`)}
|
|
635
910
|
</div>
|
|
636
911
|
</div>
|
|
637
|
-
${this.
|
|
912
|
+
${this._model.actions?.length === 1 ? html `
|
|
638
913
|
<kr-button
|
|
639
914
|
class="actions"
|
|
640
|
-
.href=${this.
|
|
641
|
-
.target=${this.
|
|
642
|
-
@click=${() => this._handleAction(this.
|
|
915
|
+
.href=${this._model.actions[0].href}
|
|
916
|
+
.target=${this._model.actions[0].target}
|
|
917
|
+
@click=${() => this._handleAction(this._model.actions[0])}
|
|
643
918
|
>
|
|
644
|
-
${this.
|
|
919
|
+
${this._model.actions[0].label}
|
|
645
920
|
</kr-button>
|
|
646
|
-
` : this.
|
|
921
|
+
` : this._model.actions?.length ? html `
|
|
647
922
|
<kr-button
|
|
648
923
|
class="actions"
|
|
649
|
-
.options=${this.
|
|
924
|
+
.options=${this._model.actions.map(a => ({ id: a.id, label: a.label }))}
|
|
650
925
|
@option-select=${(e) => this._handleAction({ id: e.detail.id, label: e.detail.label })}
|
|
651
926
|
>
|
|
652
927
|
Actions
|
|
@@ -669,6 +944,299 @@ let KRTable = class KRTable extends LitElement {
|
|
|
669
944
|
}
|
|
670
945
|
return nothing;
|
|
671
946
|
}
|
|
947
|
+
_renderFilterPanel() {
|
|
948
|
+
if (!this._filterPanelOpened) {
|
|
949
|
+
return nothing;
|
|
950
|
+
}
|
|
951
|
+
const column = this._model.columns.find(c => c.id === this._filterPanelOpened);
|
|
952
|
+
// Build filter content (operator + value input)
|
|
953
|
+
let valueInput = html ``;
|
|
954
|
+
if (column.filter.operator === 'empty' || column.filter.operator === 'n_empty') {
|
|
955
|
+
valueInput = html `
|
|
956
|
+
<input
|
|
957
|
+
type="text"
|
|
958
|
+
class="filter-panel__input"
|
|
959
|
+
disabled
|
|
960
|
+
.value=${column.filter.text}
|
|
961
|
+
/>
|
|
962
|
+
`;
|
|
963
|
+
}
|
|
964
|
+
else if (column.filter.operator === 'between' && column.type === 'date') {
|
|
965
|
+
valueInput = html `
|
|
966
|
+
<input
|
|
967
|
+
type="date"
|
|
968
|
+
class="filter-panel__input"
|
|
969
|
+
.valueAsDate=${column.filter.value?.start ?? null}
|
|
970
|
+
@change=${(e) => this._handleFilterDateStartChange(e, column)}
|
|
971
|
+
/>
|
|
972
|
+
<input
|
|
973
|
+
type="date"
|
|
974
|
+
class="filter-panel__input"
|
|
975
|
+
.valueAsDate=${column.filter.value?.end ?? null}
|
|
976
|
+
@change=${(e) => this._handleFilterDateEndChange(e, column)}
|
|
977
|
+
/>
|
|
978
|
+
`;
|
|
979
|
+
}
|
|
980
|
+
else if (column.filter.operator === 'between' && column.type === 'number') {
|
|
981
|
+
valueInput = html `
|
|
982
|
+
<input
|
|
983
|
+
type="number"
|
|
984
|
+
class="filter-panel__input"
|
|
985
|
+
placeholder="Start"
|
|
986
|
+
.value=${column.filter.value?.start ?? ''}
|
|
987
|
+
@input=${(e) => this._handleFilterNumberStartChange(e, column)}
|
|
988
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
989
|
+
/>
|
|
990
|
+
<input
|
|
991
|
+
type="number"
|
|
992
|
+
class="filter-panel__input"
|
|
993
|
+
placeholder="End"
|
|
994
|
+
.value=${column.filter.value?.end ?? ''}
|
|
995
|
+
@input=${(e) => this._handleFilterNumberEndChange(e, column)}
|
|
996
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
997
|
+
/>
|
|
998
|
+
`;
|
|
999
|
+
}
|
|
1000
|
+
else if (column.filter.operator === 'in') {
|
|
1001
|
+
valueInput = html `
|
|
1002
|
+
<textarea
|
|
1003
|
+
class="filter-panel__textarea"
|
|
1004
|
+
rows="3"
|
|
1005
|
+
placeholder="Values (comma-separated)"
|
|
1006
|
+
.value=${column.filter.text}
|
|
1007
|
+
@input=${(e) => this._handleFilterListChange(e, column)}
|
|
1008
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
1009
|
+
></textarea>
|
|
1010
|
+
`;
|
|
1011
|
+
}
|
|
1012
|
+
else if (column.type === 'boolean') {
|
|
1013
|
+
valueInput = html `
|
|
1014
|
+
<kr-select-field
|
|
1015
|
+
placeholder="Value"
|
|
1016
|
+
.value=${String(column.filter.value ?? '')}
|
|
1017
|
+
@change=${(e) => this._handleFilterBooleanChange(e, column)}
|
|
1018
|
+
>
|
|
1019
|
+
<kr-select-option value="true">Yes</kr-select-option>
|
|
1020
|
+
<kr-select-option value="false">No</kr-select-option>
|
|
1021
|
+
</kr-select-field>
|
|
1022
|
+
`;
|
|
1023
|
+
}
|
|
1024
|
+
else if (column.type === 'date') {
|
|
1025
|
+
valueInput = html `
|
|
1026
|
+
<input
|
|
1027
|
+
type="date"
|
|
1028
|
+
class="filter-panel__input"
|
|
1029
|
+
.valueAsDate=${column.filter.value}
|
|
1030
|
+
@change=${(e) => this._handleFilterDateChange(e, column)}
|
|
1031
|
+
/>
|
|
1032
|
+
`;
|
|
1033
|
+
}
|
|
1034
|
+
else if (column.type === 'number') {
|
|
1035
|
+
valueInput = html `
|
|
1036
|
+
<input
|
|
1037
|
+
type="number"
|
|
1038
|
+
class="filter-panel__input"
|
|
1039
|
+
placeholder="Value"
|
|
1040
|
+
min="0"
|
|
1041
|
+
.value=${column.filter.text}
|
|
1042
|
+
@input=${(e) => this._handleFilterNumberChange(e, column)}
|
|
1043
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
1044
|
+
/>
|
|
1045
|
+
`;
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
valueInput = html `
|
|
1049
|
+
<input
|
|
1050
|
+
type="text"
|
|
1051
|
+
class="filter-panel__input"
|
|
1052
|
+
placeholder="Value"
|
|
1053
|
+
.value=${column.filter.text}
|
|
1054
|
+
@input=${(e) => this._handleFilterStringChange(e, column)}
|
|
1055
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
1056
|
+
/>
|
|
1057
|
+
`;
|
|
1058
|
+
}
|
|
1059
|
+
const filterContent = html `
|
|
1060
|
+
<div class="filter-panel__content">
|
|
1061
|
+
<kr-select-field
|
|
1062
|
+
.value=${column.filter.operator}
|
|
1063
|
+
@change=${(e) => this._handleOperatorChange(e, column)}
|
|
1064
|
+
>
|
|
1065
|
+
${getOperatorsForType(column.type).map(op => html `
|
|
1066
|
+
<kr-select-option value=${op.key}>${op.label}</kr-select-option>
|
|
1067
|
+
`)}
|
|
1068
|
+
</kr-select-field>
|
|
1069
|
+
${valueInput}
|
|
1070
|
+
</div>
|
|
1071
|
+
`;
|
|
1072
|
+
// Build bucket list content
|
|
1073
|
+
const buckets = this._buckets.get(column.id) || [];
|
|
1074
|
+
let bucketContent;
|
|
1075
|
+
if (!buckets.length) {
|
|
1076
|
+
bucketContent = html `<div class="bucket-empty">No data</div>`;
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
bucketContent = html `
|
|
1080
|
+
<div class="buckets">
|
|
1081
|
+
${buckets.map(bucket => {
|
|
1082
|
+
let bucketLabel = '(Empty)';
|
|
1083
|
+
if (bucket.val !== null && bucket.val !== undefined) {
|
|
1084
|
+
if (column.type === 'boolean') {
|
|
1085
|
+
if (bucket.val === true || bucket.val === 'true') {
|
|
1086
|
+
bucketLabel = 'Yes';
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
bucketLabel = 'No';
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
bucketLabel = String(bucket.val);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
let checkIcon = nothing;
|
|
1097
|
+
if (column.filter.has(bucket.val)) {
|
|
1098
|
+
checkIcon = html `
|
|
1099
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1100
|
+
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
|
1101
|
+
</svg>
|
|
1102
|
+
`;
|
|
1103
|
+
}
|
|
1104
|
+
return html `
|
|
1105
|
+
<div
|
|
1106
|
+
class="bucket"
|
|
1107
|
+
@click=${(e) => this._handleBucketToggle(e, column, bucket)}
|
|
1108
|
+
>
|
|
1109
|
+
<div class=${classMap({
|
|
1110
|
+
'bucket__checkbox': true,
|
|
1111
|
+
'bucket__checkbox--checked': column.filter.has(bucket.val)
|
|
1112
|
+
})}>
|
|
1113
|
+
${checkIcon}
|
|
1114
|
+
</div>
|
|
1115
|
+
<span class="bucket__label">${bucketLabel}</span>
|
|
1116
|
+
<span class="bucket__count">${bucket.count}</span>
|
|
1117
|
+
</div>
|
|
1118
|
+
`;
|
|
1119
|
+
})}
|
|
1120
|
+
</div>
|
|
1121
|
+
`;
|
|
1122
|
+
}
|
|
1123
|
+
// Build panel body — tabs if both filterable+facetable, otherwise just the relevant content
|
|
1124
|
+
let panelBody;
|
|
1125
|
+
if (column.facetable && column.filterable) {
|
|
1126
|
+
panelBody = html `
|
|
1127
|
+
<kr-tab-group
|
|
1128
|
+
size="small"
|
|
1129
|
+
active-tab-id=${this._filterPanelTab}
|
|
1130
|
+
@tab-change=${(e) => this._handleFilterPanelTabChange(e)}
|
|
1131
|
+
>
|
|
1132
|
+
<kr-tab id="filter" label="Filter">
|
|
1133
|
+
${filterContent}
|
|
1134
|
+
</kr-tab>
|
|
1135
|
+
<kr-tab id="counts" label="Counts">
|
|
1136
|
+
${bucketContent}
|
|
1137
|
+
</kr-tab>
|
|
1138
|
+
</kr-tab-group>
|
|
1139
|
+
`;
|
|
1140
|
+
}
|
|
1141
|
+
else if (column.facetable) {
|
|
1142
|
+
panelBody = bucketContent;
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
panelBody = filterContent;
|
|
1146
|
+
}
|
|
1147
|
+
return html `
|
|
1148
|
+
<div
|
|
1149
|
+
class="filter-panel"
|
|
1150
|
+
style=${styleMap({
|
|
1151
|
+
top: this._filterPanelPos.top + 'px',
|
|
1152
|
+
left: this._filterPanelPos.left + 'px'
|
|
1153
|
+
})}
|
|
1154
|
+
>
|
|
1155
|
+
${panelBody}
|
|
1156
|
+
<div class="filter-panel__actions">
|
|
1157
|
+
<kr-button variant="outline" color="secondary" size="small" @click=${this._handleFilterClear}>
|
|
1158
|
+
Clear
|
|
1159
|
+
</kr-button>
|
|
1160
|
+
<kr-button size="small" @click=${this._handleFilterApply}>
|
|
1161
|
+
Apply
|
|
1162
|
+
</kr-button>
|
|
1163
|
+
</div>
|
|
1164
|
+
</div>
|
|
1165
|
+
`;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Renders filter row below column headers.
|
|
1169
|
+
* Only displays for columns with filterable: true.
|
|
1170
|
+
*/
|
|
1171
|
+
_renderFilterRow() {
|
|
1172
|
+
const columns = this.getDisplayedColumns();
|
|
1173
|
+
if (!columns.some(col => col.filterable || col.facetable)) {
|
|
1174
|
+
return nothing;
|
|
1175
|
+
}
|
|
1176
|
+
return html `
|
|
1177
|
+
<div class="filter-row">
|
|
1178
|
+
${columns.map((col, i) => {
|
|
1179
|
+
if (!col.filterable && !col.facetable) {
|
|
1180
|
+
return html `<div
|
|
1181
|
+
class=${classMap({
|
|
1182
|
+
'filter-cell': true,
|
|
1183
|
+
'filter-cell--sticky-left': col.sticky === 'left',
|
|
1184
|
+
'filter-cell--sticky-right': col.sticky === 'right',
|
|
1185
|
+
'filter-cell--sticky-right-first': col.sticky === 'right' &&
|
|
1186
|
+
!columns.slice(0, i).some((c) => c.sticky === 'right')
|
|
1187
|
+
})}
|
|
1188
|
+
style=${styleMap(this._getCellStyle(col, i))}
|
|
1189
|
+
></div>`;
|
|
1190
|
+
}
|
|
1191
|
+
return html `
|
|
1192
|
+
<div
|
|
1193
|
+
class=${classMap({
|
|
1194
|
+
'filter-cell': true,
|
|
1195
|
+
'filter-cell--sticky-left': col.sticky === 'left',
|
|
1196
|
+
'filter-cell--sticky-right': col.sticky === 'right',
|
|
1197
|
+
'filter-cell--sticky-right-first': col.sticky === 'right' &&
|
|
1198
|
+
!columns.slice(0, i).some((c) => c.sticky === 'right')
|
|
1199
|
+
})}
|
|
1200
|
+
style=${styleMap(this._getCellStyle(col, i))}
|
|
1201
|
+
>
|
|
1202
|
+
<div class="filter-cell__wrapper">
|
|
1203
|
+
<input
|
|
1204
|
+
type="text"
|
|
1205
|
+
class=${classMap({
|
|
1206
|
+
'filter-cell__input': true,
|
|
1207
|
+
'filter-cell__input--invalid': !col.filter.isValid()
|
|
1208
|
+
})}
|
|
1209
|
+
.value=${col.filter.kql}
|
|
1210
|
+
@change=${(e) => this._handleKqlChange(e, col)}
|
|
1211
|
+
/>
|
|
1212
|
+
${col.filter?.kql?.length > 0 ? html `
|
|
1213
|
+
<button
|
|
1214
|
+
class="filter-cell__clear"
|
|
1215
|
+
@click=${() => this._handleKqlClear(col)}
|
|
1216
|
+
>
|
|
1217
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1218
|
+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
|
1219
|
+
</svg>
|
|
1220
|
+
</button>
|
|
1221
|
+
` : nothing}
|
|
1222
|
+
<button
|
|
1223
|
+
class=${classMap({
|
|
1224
|
+
'filter-cell__advanced': true,
|
|
1225
|
+
'filter-cell__advanced--opened': this._filterPanelOpened === col.id
|
|
1226
|
+
})}
|
|
1227
|
+
@click=${(e) => this._handleFilterPanelToggle(e, col)}
|
|
1228
|
+
>
|
|
1229
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1230
|
+
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
|
|
1231
|
+
</svg>
|
|
1232
|
+
</button>
|
|
1233
|
+
</div>
|
|
1234
|
+
</div>
|
|
1235
|
+
`;
|
|
1236
|
+
})}
|
|
1237
|
+
</div>
|
|
1238
|
+
`;
|
|
1239
|
+
}
|
|
672
1240
|
/** Renders the scrollable data grid with column headers and rows. */
|
|
673
1241
|
_renderTable() {
|
|
674
1242
|
return html `
|
|
@@ -690,6 +1258,7 @@ let KRTable = class KRTable extends LitElement {
|
|
|
690
1258
|
></div>` : nothing}</div>
|
|
691
1259
|
`)}
|
|
692
1260
|
</div>
|
|
1261
|
+
${this._renderFilterRow()}
|
|
693
1262
|
${this._data.map((row, rowIndex) => {
|
|
694
1263
|
const cells = this.getDisplayedColumns().map((col, i) => html `
|
|
695
1264
|
<div
|
|
@@ -700,10 +1269,10 @@ let KRTable = class KRTable extends LitElement {
|
|
|
700
1269
|
${this._renderCellContent(col, row, rowIndex)}
|
|
701
1270
|
</div>
|
|
702
1271
|
`);
|
|
703
|
-
if (this.
|
|
1272
|
+
if (this._model.rowHref) {
|
|
704
1273
|
return html `
|
|
705
1274
|
<a
|
|
706
|
-
href=${this.
|
|
1275
|
+
href=${this._model.rowHref(row)}
|
|
707
1276
|
class=${classMap({ 'row': true, 'row--clickable': true, 'row--link': true })}
|
|
708
1277
|
@mousedown=${() => this._handleRowMouseDown()}
|
|
709
1278
|
@click=${() => this._handleRowClick(row, rowIndex)}
|
|
@@ -712,7 +1281,7 @@ let KRTable = class KRTable extends LitElement {
|
|
|
712
1281
|
}
|
|
713
1282
|
return html `
|
|
714
1283
|
<div
|
|
715
|
-
class=${classMap({ 'row': true, 'row--clickable': !!this.
|
|
1284
|
+
class=${classMap({ 'row': true, 'row--clickable': !!this._model.rowClickable })}
|
|
716
1285
|
@mousedown=${() => this._handleRowMouseDown()}
|
|
717
1286
|
@click=${() => this._handleRowClick(row, rowIndex)}
|
|
718
1287
|
>${cells}</div>
|
|
@@ -730,12 +1299,13 @@ let KRTable = class KRTable extends LitElement {
|
|
|
730
1299
|
* - Loading, error message, or empty state when no data
|
|
731
1300
|
*/
|
|
732
1301
|
render() {
|
|
733
|
-
if (!this.
|
|
1302
|
+
if (!this._model.columns.length) {
|
|
734
1303
|
return html `<slot></slot>`;
|
|
735
1304
|
}
|
|
736
1305
|
return html `
|
|
737
1306
|
${this._renderHeader()}
|
|
738
1307
|
${this._renderTable()}
|
|
1308
|
+
${this._renderFilterPanel()}
|
|
739
1309
|
`;
|
|
740
1310
|
}
|
|
741
1311
|
};
|
|
@@ -1084,6 +1654,7 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1084
1654
|
overflow: hidden;
|
|
1085
1655
|
text-overflow: ellipsis;
|
|
1086
1656
|
box-sizing: border-box;
|
|
1657
|
+
border-right: 1px solid #e5e7ebba;
|
|
1087
1658
|
}
|
|
1088
1659
|
|
|
1089
1660
|
.cell--actions {
|
|
@@ -1100,7 +1671,9 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1100
1671
|
white-space: nowrap;
|
|
1101
1672
|
box-sizing: border-box;
|
|
1102
1673
|
background: #f9fafb;
|
|
1674
|
+
border-top: 1px solid #e5e7eb;
|
|
1103
1675
|
border-bottom: 2px solid #e5e7eb;
|
|
1676
|
+
border-right: 1px solid #e5e7ebba;
|
|
1104
1677
|
font-weight: 600;
|
|
1105
1678
|
color: #374151;
|
|
1106
1679
|
overflow: hidden;
|
|
@@ -1198,7 +1771,6 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1198
1771
|
* ----------------------------------------------------------------------- */
|
|
1199
1772
|
:host(.kr-table--scroll-overlay) .content {
|
|
1200
1773
|
padding-left: 24px;
|
|
1201
|
-
padding-right: 24px;
|
|
1202
1774
|
}
|
|
1203
1775
|
|
|
1204
1776
|
.overlay-left,
|
|
@@ -1257,6 +1829,301 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1257
1829
|
.status--error {
|
|
1258
1830
|
color: #dc2626;
|
|
1259
1831
|
}
|
|
1832
|
+
|
|
1833
|
+
/* -------------------------------------------------------------------------
|
|
1834
|
+
* Filter Row
|
|
1835
|
+
* ----------------------------------------------------------------------- */
|
|
1836
|
+
.filter-row {
|
|
1837
|
+
display: contents;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
.filter-cell {
|
|
1841
|
+
position: sticky;
|
|
1842
|
+
top: 48px;
|
|
1843
|
+
z-index: 2;
|
|
1844
|
+
height: 40px;
|
|
1845
|
+
padding: 4px 8px;
|
|
1846
|
+
display: flex;
|
|
1847
|
+
align-items: center;
|
|
1848
|
+
background: #fafbfc;
|
|
1849
|
+
border-bottom: 1px solid #e5e7eb;
|
|
1850
|
+
border-right: 1px solid #e5e7ebba;
|
|
1851
|
+
box-sizing: border-box;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
.filter-cell--sticky-left,
|
|
1856
|
+
.filter-cell--sticky-right {
|
|
1857
|
+
position: sticky;
|
|
1858
|
+
z-index: 3;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
.filter-cell--sticky-right-first {
|
|
1862
|
+
border-left: 1px solid #d1d5db;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
.filter-cell__wrapper {
|
|
1866
|
+
position: relative;
|
|
1867
|
+
display: flex;
|
|
1868
|
+
align-items: center;
|
|
1869
|
+
gap: 4px;
|
|
1870
|
+
width: 100%;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
.filter-cell__input {
|
|
1874
|
+
width: 100%;
|
|
1875
|
+
height: 32px;
|
|
1876
|
+
padding: 0 52px 0 8px;
|
|
1877
|
+
border: 1px solid #d1d5db;
|
|
1878
|
+
border-radius: 6px;
|
|
1879
|
+
font-size: 14px;
|
|
1880
|
+
font-family: inherit;
|
|
1881
|
+
color: #111827;
|
|
1882
|
+
background: #fff;
|
|
1883
|
+
outline: none;
|
|
1884
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
.filter-cell__input:focus {
|
|
1888
|
+
border-color: #163052;
|
|
1889
|
+
box-shadow: 0 0 0 2px rgba(22, 48, 82, 0.1);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
.filter-cell__input--invalid {
|
|
1893
|
+
border-color: #dc2626;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
.filter-cell__input--invalid:focus {
|
|
1897
|
+
border-color: #dc2626;
|
|
1898
|
+
box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.1);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
.filter-cell__input::placeholder {
|
|
1902
|
+
color: #9ca3af;
|
|
1903
|
+
font-size: 13px;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
.filter-cell__clear {
|
|
1907
|
+
position: absolute;
|
|
1908
|
+
right: 28px;
|
|
1909
|
+
top: 50%;
|
|
1910
|
+
transform: translateY(-50%);
|
|
1911
|
+
display: flex;
|
|
1912
|
+
align-items: center;
|
|
1913
|
+
justify-content: center;
|
|
1914
|
+
width: 24px;
|
|
1915
|
+
height: 24px;
|
|
1916
|
+
padding: 0;
|
|
1917
|
+
border: none;
|
|
1918
|
+
border-radius: 4px;
|
|
1919
|
+
background: transparent;
|
|
1920
|
+
color: #6b7280;
|
|
1921
|
+
cursor: pointer;
|
|
1922
|
+
transition: background 0.15s, color 0.15s;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
.filter-cell__clear:hover {
|
|
1926
|
+
background: #e5e7eb;
|
|
1927
|
+
color: #374151;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
.filter-cell__clear svg {
|
|
1931
|
+
width: 16px;
|
|
1932
|
+
height: 16px;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
.filter-cell__advanced {
|
|
1936
|
+
position: absolute;
|
|
1937
|
+
right: 4px;
|
|
1938
|
+
top: 50%;
|
|
1939
|
+
transform: translateY(-50%);
|
|
1940
|
+
display: flex;
|
|
1941
|
+
align-items: center;
|
|
1942
|
+
justify-content: center;
|
|
1943
|
+
width: 24px;
|
|
1944
|
+
height: 24px;
|
|
1945
|
+
padding: 0;
|
|
1946
|
+
border: none;
|
|
1947
|
+
border-radius: 4px;
|
|
1948
|
+
background: transparent;
|
|
1949
|
+
color: #163052;
|
|
1950
|
+
cursor: pointer;
|
|
1951
|
+
transition: background 0.15s, color 0.15s;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
.filter-cell__advanced:hover {
|
|
1955
|
+
background: #e5e7eb;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
.filter-cell__advanced svg {
|
|
1959
|
+
width: 16px;
|
|
1960
|
+
height: 16px;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
.filter-cell__advanced--opened {
|
|
1964
|
+
background: #163052;
|
|
1965
|
+
color: #fff;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
.filter-cell__advanced--opened:hover {
|
|
1969
|
+
background: #1a3a5f;
|
|
1970
|
+
color: #fff;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/* -------------------------------------------------------------------------
|
|
1974
|
+
* Filter Panel (Advanced)
|
|
1975
|
+
* ----------------------------------------------------------------------- */
|
|
1976
|
+
.filter-panel {
|
|
1977
|
+
position: fixed;
|
|
1978
|
+
min-width: 320px;
|
|
1979
|
+
background: white;
|
|
1980
|
+
border: 1px solid #9ba7b6;
|
|
1981
|
+
border-radius: 8px;
|
|
1982
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
1983
|
+
z-index: 100;
|
|
1984
|
+
animation: filter-panel-fade-in 150ms ease-out;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
@keyframes filter-panel-fade-in {
|
|
1988
|
+
from {
|
|
1989
|
+
opacity: 0;
|
|
1990
|
+
transform: translateY(-4px);
|
|
1991
|
+
}
|
|
1992
|
+
to {
|
|
1993
|
+
opacity: 1;
|
|
1994
|
+
transform: translateY(0);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
.filter-panel__content {
|
|
1999
|
+
padding: 16px;
|
|
2000
|
+
display: flex;
|
|
2001
|
+
flex-direction: column;
|
|
2002
|
+
gap: 12px;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
.filter-panel__actions {
|
|
2006
|
+
padding: 12px 16px;
|
|
2007
|
+
border-top: 1px solid #e5e7eb;
|
|
2008
|
+
display: flex;
|
|
2009
|
+
justify-content: flex-end;
|
|
2010
|
+
align-items: center;
|
|
2011
|
+
gap: 12px;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
|
|
2015
|
+
.filter-panel__input {
|
|
2016
|
+
width: 100%;
|
|
2017
|
+
padding: 10px 12px;
|
|
2018
|
+
border: 1px solid #d1d5db;
|
|
2019
|
+
border-radius: 8px;
|
|
2020
|
+
font-size: 14px;
|
|
2021
|
+
font-family: inherit;
|
|
2022
|
+
color: #111827;
|
|
2023
|
+
background: #fff;
|
|
2024
|
+
outline: none;
|
|
2025
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
.filter-panel__input:focus {
|
|
2029
|
+
border-color: #163052;
|
|
2030
|
+
box-shadow: 0 0 0 2px rgba(22, 48, 82, 0.1);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
.filter-panel__input::placeholder {
|
|
2034
|
+
color: #9ca3af;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
.filter-panel__textarea {
|
|
2038
|
+
width: 100%;
|
|
2039
|
+
padding: 10px 12px;
|
|
2040
|
+
border: 1px solid #d1d5db;
|
|
2041
|
+
border-radius: 8px;
|
|
2042
|
+
font-size: 14px;
|
|
2043
|
+
font-family: inherit;
|
|
2044
|
+
color: #111827;
|
|
2045
|
+
background: #fff;
|
|
2046
|
+
outline: none;
|
|
2047
|
+
resize: vertical;
|
|
2048
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
.filter-panel__textarea:focus {
|
|
2052
|
+
border-color: #163052;
|
|
2053
|
+
box-shadow: 0 0 0 2px rgba(22, 48, 82, 0.1);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
.filter-panel__textarea::placeholder {
|
|
2057
|
+
color: #9ca3af;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
/* -------------------------------------------------------------------------
|
|
2061
|
+
* Bucket List
|
|
2062
|
+
* ----------------------------------------------------------------------- */
|
|
2063
|
+
.buckets {
|
|
2064
|
+
max-height: 280px;
|
|
2065
|
+
overflow-y: auto;
|
|
2066
|
+
padding: 8px 0;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
.bucket {
|
|
2070
|
+
display: flex;
|
|
2071
|
+
align-items: center;
|
|
2072
|
+
gap: 16px;
|
|
2073
|
+
height: 32px;
|
|
2074
|
+
padding: 0 16px;
|
|
2075
|
+
cursor: pointer;
|
|
2076
|
+
transition: background 0.1s;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
.bucket:hover {
|
|
2080
|
+
background: #f3f4f6;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
.bucket__checkbox {
|
|
2084
|
+
width: 16px;
|
|
2085
|
+
height: 16px;
|
|
2086
|
+
border: 1.5px solid #9ca3af;
|
|
2087
|
+
border-radius: 3px;
|
|
2088
|
+
display: flex;
|
|
2089
|
+
align-items: center;
|
|
2090
|
+
justify-content: center;
|
|
2091
|
+
flex-shrink: 0;
|
|
2092
|
+
transition: all 0.15s;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
.bucket__checkbox--checked {
|
|
2096
|
+
background: var(--kr-primary, #163052);
|
|
2097
|
+
border-color: var(--kr-primary, #163052);
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
.bucket__checkbox svg {
|
|
2101
|
+
width: 12px;
|
|
2102
|
+
height: 12px;
|
|
2103
|
+
color: white;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
.bucket__label {
|
|
2107
|
+
flex: 1;
|
|
2108
|
+
font-size: 14px;
|
|
2109
|
+
color: #000;
|
|
2110
|
+
overflow: hidden;
|
|
2111
|
+
text-overflow: ellipsis;
|
|
2112
|
+
white-space: nowrap;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
.bucket__count {
|
|
2116
|
+
font-size: 14px;
|
|
2117
|
+
color: #000;
|
|
2118
|
+
flex-shrink: 0;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
.bucket-empty {
|
|
2122
|
+
font-size: 14px;
|
|
2123
|
+
color: #000;
|
|
2124
|
+
padding: 16px;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
1260
2127
|
`];
|
|
1261
2128
|
__decorate([
|
|
1262
2129
|
state()
|
|
@@ -1293,7 +2160,13 @@ __decorate([
|
|
|
1293
2160
|
], KRTable.prototype, "_columnPickerOpen", void 0);
|
|
1294
2161
|
__decorate([
|
|
1295
2162
|
state()
|
|
1296
|
-
], KRTable.prototype, "
|
|
2163
|
+
], KRTable.prototype, "_filterPanelOpened", void 0);
|
|
2164
|
+
__decorate([
|
|
2165
|
+
state()
|
|
2166
|
+
], KRTable.prototype, "_filterPanelTab", void 0);
|
|
2167
|
+
__decorate([
|
|
2168
|
+
state()
|
|
2169
|
+
], KRTable.prototype, "_buckets", void 0);
|
|
1297
2170
|
__decorate([
|
|
1298
2171
|
property({ type: Object })
|
|
1299
2172
|
], KRTable.prototype, "def", void 0);
|