@kodaris/krubble-components 1.0.52 → 1.0.54
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 +3227 -1011
- package/dist/button/button.js +1 -1
- package/dist/form/auto-suggest/auto-suggest.d.ts +20 -42
- package/dist/form/auto-suggest/auto-suggest.d.ts.map +1 -1
- package/dist/form/auto-suggest/auto-suggest.js +193 -426
- package/dist/form/auto-suggest/auto-suggest.js.map +1 -1
- package/dist/form/combo-box/combo-box.d.ts +92 -0
- package/dist/form/combo-box/combo-box.d.ts.map +1 -0
- package/dist/form/combo-box/combo-box.js +714 -0
- package/dist/form/combo-box/combo-box.js.map +1 -0
- package/dist/form/index.d.ts +1 -0
- package/dist/form/index.d.ts.map +1 -1
- package/dist/form/index.js +1 -0
- package/dist/form/index.js.map +1 -1
- package/dist/form/select-field/select-field.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/krubble-components.bundled.js +3845 -1477
- package/dist/krubble-components.bundled.js.map +1 -1
- package/dist/krubble-components.bundled.min.js +1156 -548
- package/dist/krubble-components.bundled.min.js.map +1 -1
- package/dist/krubble-components.umd.js +5715 -3344
- package/dist/krubble-components.umd.js.map +1 -1
- package/dist/krubble-components.umd.min.js +1159 -551
- 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 +993 -113
- package/dist/table/table.js.map +1 -1
- package/package.json +5 -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 = [];
|
|
109
162
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
163
|
+
else if (column.filter.type === 'string') {
|
|
164
|
+
column.filter.operator = 'contains';
|
|
165
|
+
}
|
|
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,132 @@ 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
|
+
let left = rect.left;
|
|
621
|
+
if (left + 328 > window.innerWidth) {
|
|
622
|
+
left = window.innerWidth - 328;
|
|
623
|
+
}
|
|
624
|
+
this._filterPanelPos = {
|
|
625
|
+
top: rect.bottom + 4,
|
|
626
|
+
left
|
|
627
|
+
};
|
|
628
|
+
this._filterPanelOpened = column.id;
|
|
629
|
+
if (column.facetable) {
|
|
630
|
+
this._filterPanelTab = 'counts';
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
this._filterPanelTab = 'filter';
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
_handleKqlClear(column) {
|
|
638
|
+
column.filter.clear();
|
|
639
|
+
this._page = 1;
|
|
640
|
+
this._fetch();
|
|
641
|
+
}
|
|
642
|
+
_handleFilterClear() {
|
|
643
|
+
const column = this._model.columns.find(c => c.id === this._filterPanelOpened);
|
|
644
|
+
if (column) {
|
|
645
|
+
column.filter.clear();
|
|
646
|
+
if (column.facetable && !column.filterable) {
|
|
647
|
+
column.filter.operator = 'in';
|
|
648
|
+
column.filter.value = [];
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
this._filterPanelOpened = null;
|
|
652
|
+
this._page = 1;
|
|
653
|
+
this._fetch();
|
|
654
|
+
}
|
|
655
|
+
_handleFilterTextKeydown(e, column) {
|
|
656
|
+
if (e.key === 'Enter') {
|
|
657
|
+
e.preventDefault();
|
|
658
|
+
this._handleFilterApply();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
_handleOperatorChange(e, column) {
|
|
662
|
+
column.filter.setOperator(e.target.value);
|
|
663
|
+
this.requestUpdate();
|
|
664
|
+
}
|
|
665
|
+
_handleFilterStringChange(e, column) {
|
|
666
|
+
column.filter.setValue(e.target.value);
|
|
667
|
+
this.requestUpdate();
|
|
668
|
+
}
|
|
669
|
+
_handleFilterNumberChange(e, column) {
|
|
670
|
+
column.filter.setValue(Number(e.target.value));
|
|
671
|
+
this.requestUpdate();
|
|
672
|
+
}
|
|
673
|
+
_handleFilterDateChange(e, column) {
|
|
674
|
+
column.filter.setValue(new Date(e.target.value), 'day');
|
|
675
|
+
this.requestUpdate();
|
|
676
|
+
}
|
|
677
|
+
_handleFilterBooleanChange(e, column) {
|
|
678
|
+
column.filter.setValue(e.target.value === 'true');
|
|
679
|
+
this.requestUpdate();
|
|
680
|
+
}
|
|
681
|
+
_handleFilterDateStartChange(e, column) {
|
|
682
|
+
column.filter.setStart(new Date(e.target.value), 'day');
|
|
683
|
+
this.requestUpdate();
|
|
684
|
+
}
|
|
685
|
+
_handleFilterDateEndChange(e, column) {
|
|
686
|
+
column.filter.setEnd(new Date(e.target.value), 'day');
|
|
687
|
+
this.requestUpdate();
|
|
688
|
+
}
|
|
689
|
+
_handleFilterNumberStartChange(e, column) {
|
|
690
|
+
column.filter.setStart(Number(e.target.value));
|
|
691
|
+
this.requestUpdate();
|
|
692
|
+
}
|
|
693
|
+
_handleFilterNumberEndChange(e, column) {
|
|
694
|
+
column.filter.setEnd(Number(e.target.value));
|
|
695
|
+
this.requestUpdate();
|
|
696
|
+
}
|
|
697
|
+
_handleFilterListChange(e, column) {
|
|
698
|
+
const items = e.target.value.split(',').map((v) => v.trim()).filter((v) => v !== '');
|
|
699
|
+
if (column.type === 'number') {
|
|
700
|
+
column.filter.setValue(items.map((v) => Number(v)));
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
column.filter.setValue(items);
|
|
704
|
+
}
|
|
705
|
+
this.requestUpdate();
|
|
706
|
+
}
|
|
707
|
+
_handleFilterApply() {
|
|
708
|
+
this._filterPanelOpened = null;
|
|
709
|
+
this._page = 1;
|
|
710
|
+
this._fetch();
|
|
711
|
+
}
|
|
712
|
+
_handleFilterPanelTabChange(e) {
|
|
713
|
+
this._filterPanelTab = e.detail.activeTabId;
|
|
714
|
+
}
|
|
715
|
+
_handleBucketToggle(e, column, bucket) {
|
|
716
|
+
column.filter.toggle(bucket.val);
|
|
717
|
+
this._page = 1;
|
|
718
|
+
this._fetch();
|
|
719
|
+
}
|
|
720
|
+
// ----------------------------------------------------------------------------
|
|
439
721
|
// Rendering
|
|
440
722
|
// ----------------------------------------------------------------------------
|
|
441
723
|
_renderCellContent(column, row, rowIndex) {
|
|
@@ -449,11 +731,10 @@ let KRTable = class KRTable extends LitElement {
|
|
|
449
731
|
}
|
|
450
732
|
switch (column.type) {
|
|
451
733
|
case 'number':
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
: String(value);
|
|
734
|
+
if (column.format === 'currency' && typeof value === 'number') {
|
|
735
|
+
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
|
|
736
|
+
}
|
|
737
|
+
return String(value);
|
|
457
738
|
case 'date': {
|
|
458
739
|
let date;
|
|
459
740
|
if (value instanceof Date) {
|
|
@@ -468,10 +749,11 @@ let KRTable = class KRTable extends LitElement {
|
|
|
468
749
|
else {
|
|
469
750
|
date = new Date(value);
|
|
470
751
|
}
|
|
471
|
-
// Show date and time for datetime values
|
|
752
|
+
// Show date and time for datetime values in UTC
|
|
472
753
|
return date.toLocaleString(undefined, {
|
|
473
754
|
year: 'numeric', month: 'short', day: 'numeric',
|
|
474
|
-
hour: 'numeric', minute: '2-digit'
|
|
755
|
+
hour: 'numeric', minute: '2-digit',
|
|
756
|
+
timeZone: 'UTC'
|
|
475
757
|
});
|
|
476
758
|
}
|
|
477
759
|
case 'boolean':
|
|
@@ -588,13 +870,13 @@ let KRTable = class KRTable extends LitElement {
|
|
|
588
870
|
* Hidden when there's no title, no actions, and data fits on one page.
|
|
589
871
|
*/
|
|
590
872
|
_renderHeader() {
|
|
591
|
-
if (!this.
|
|
873
|
+
if (!this._model.title && !this._model.actions?.length && this._totalPages <= 1) {
|
|
592
874
|
return nothing;
|
|
593
875
|
}
|
|
594
876
|
return html `
|
|
595
877
|
<div class="header">
|
|
596
|
-
<div class="title">${this.
|
|
597
|
-
${this.
|
|
878
|
+
<div class="title">${this._model.title ?? ''}</div>
|
|
879
|
+
${this._model.dataSource?.mode === 'db' && !this._model.columns.some(col => col.searchable) ? html `<div class="search"></div>` : html `
|
|
598
880
|
<div class="search">
|
|
599
881
|
<!-- TODO: Saved views dropdown
|
|
600
882
|
<div class="views">
|
|
@@ -624,9 +906,9 @@ let KRTable = class KRTable extends LitElement {
|
|
|
624
906
|
<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
907
|
</span>
|
|
626
908
|
<div class="column-picker ${this._columnPickerOpen ? 'open' : ''}">
|
|
627
|
-
${[...this.
|
|
909
|
+
${[...this._model.columns].filter(col => col.type !== 'actions').sort((a, b) => (a.label ?? a.id).localeCompare(b.label ?? b.id)).map(col => html `
|
|
628
910
|
<div class="column-picker-item" @click=${() => this._toggleColumn(col.id)}>
|
|
629
|
-
<div class="column-picker-checkbox ${this.
|
|
911
|
+
<div class="column-picker-checkbox ${this._model.displayedColumns.includes(col.id) ? 'checked' : ''}">
|
|
630
912
|
<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
913
|
</div>
|
|
632
914
|
<span class="column-picker-label">${col.label ?? col.id}</span>
|
|
@@ -634,19 +916,19 @@ let KRTable = class KRTable extends LitElement {
|
|
|
634
916
|
`)}
|
|
635
917
|
</div>
|
|
636
918
|
</div>
|
|
637
|
-
${this.
|
|
919
|
+
${this._model.actions?.length === 1 ? html `
|
|
638
920
|
<kr-button
|
|
639
921
|
class="actions"
|
|
640
|
-
.href=${this.
|
|
641
|
-
.target=${this.
|
|
642
|
-
@click=${() => this._handleAction(this.
|
|
922
|
+
.href=${this._model.actions[0].href}
|
|
923
|
+
.target=${this._model.actions[0].target}
|
|
924
|
+
@click=${() => this._handleAction(this._model.actions[0])}
|
|
643
925
|
>
|
|
644
|
-
${this.
|
|
926
|
+
${this._model.actions[0].label}
|
|
645
927
|
</kr-button>
|
|
646
|
-
` : this.
|
|
928
|
+
` : this._model.actions?.length ? html `
|
|
647
929
|
<kr-button
|
|
648
930
|
class="actions"
|
|
649
|
-
.options=${this.
|
|
931
|
+
.options=${this._model.actions.map(a => ({ id: a.id, label: a.label }))}
|
|
650
932
|
@option-select=${(e) => this._handleAction({ id: e.detail.id, label: e.detail.label })}
|
|
651
933
|
>
|
|
652
934
|
Actions
|
|
@@ -669,6 +951,299 @@ let KRTable = class KRTable extends LitElement {
|
|
|
669
951
|
}
|
|
670
952
|
return nothing;
|
|
671
953
|
}
|
|
954
|
+
_renderFilterPanel() {
|
|
955
|
+
if (!this._filterPanelOpened) {
|
|
956
|
+
return nothing;
|
|
957
|
+
}
|
|
958
|
+
const column = this._model.columns.find(c => c.id === this._filterPanelOpened);
|
|
959
|
+
// Build filter content (operator + value input)
|
|
960
|
+
let valueInput = html ``;
|
|
961
|
+
if (column.filter.operator === 'empty' || column.filter.operator === 'n_empty') {
|
|
962
|
+
valueInput = html `
|
|
963
|
+
<input
|
|
964
|
+
type="text"
|
|
965
|
+
class="filter-panel__input"
|
|
966
|
+
disabled
|
|
967
|
+
.value=${column.filter.text}
|
|
968
|
+
/>
|
|
969
|
+
`;
|
|
970
|
+
}
|
|
971
|
+
else if (column.filter.operator === 'between' && column.type === 'date') {
|
|
972
|
+
valueInput = html `
|
|
973
|
+
<input
|
|
974
|
+
type="date"
|
|
975
|
+
class="filter-panel__input"
|
|
976
|
+
.valueAsDate=${column.filter.value?.start ?? null}
|
|
977
|
+
@change=${(e) => this._handleFilterDateStartChange(e, column)}
|
|
978
|
+
/>
|
|
979
|
+
<input
|
|
980
|
+
type="date"
|
|
981
|
+
class="filter-panel__input"
|
|
982
|
+
.valueAsDate=${column.filter.value?.end ?? null}
|
|
983
|
+
@change=${(e) => this._handleFilterDateEndChange(e, column)}
|
|
984
|
+
/>
|
|
985
|
+
`;
|
|
986
|
+
}
|
|
987
|
+
else if (column.filter.operator === 'between' && column.type === 'number') {
|
|
988
|
+
valueInput = html `
|
|
989
|
+
<input
|
|
990
|
+
type="number"
|
|
991
|
+
class="filter-panel__input"
|
|
992
|
+
placeholder="Start"
|
|
993
|
+
.value=${column.filter.value?.start ?? ''}
|
|
994
|
+
@input=${(e) => this._handleFilterNumberStartChange(e, column)}
|
|
995
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
996
|
+
/>
|
|
997
|
+
<input
|
|
998
|
+
type="number"
|
|
999
|
+
class="filter-panel__input"
|
|
1000
|
+
placeholder="End"
|
|
1001
|
+
.value=${column.filter.value?.end ?? ''}
|
|
1002
|
+
@input=${(e) => this._handleFilterNumberEndChange(e, column)}
|
|
1003
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
1004
|
+
/>
|
|
1005
|
+
`;
|
|
1006
|
+
}
|
|
1007
|
+
else if (column.filter.operator === 'in') {
|
|
1008
|
+
valueInput = html `
|
|
1009
|
+
<textarea
|
|
1010
|
+
class="filter-panel__textarea"
|
|
1011
|
+
rows="3"
|
|
1012
|
+
placeholder="Values (comma-separated)"
|
|
1013
|
+
.value=${column.filter.text}
|
|
1014
|
+
@input=${(e) => this._handleFilterListChange(e, column)}
|
|
1015
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
1016
|
+
></textarea>
|
|
1017
|
+
`;
|
|
1018
|
+
}
|
|
1019
|
+
else if (column.type === 'boolean') {
|
|
1020
|
+
valueInput = html `
|
|
1021
|
+
<kr-select-field
|
|
1022
|
+
placeholder="Value"
|
|
1023
|
+
.value=${String(column.filter.value ?? '')}
|
|
1024
|
+
@change=${(e) => this._handleFilterBooleanChange(e, column)}
|
|
1025
|
+
>
|
|
1026
|
+
<kr-select-option value="true">Yes</kr-select-option>
|
|
1027
|
+
<kr-select-option value="false">No</kr-select-option>
|
|
1028
|
+
</kr-select-field>
|
|
1029
|
+
`;
|
|
1030
|
+
}
|
|
1031
|
+
else if (column.type === 'date') {
|
|
1032
|
+
valueInput = html `
|
|
1033
|
+
<input
|
|
1034
|
+
type="date"
|
|
1035
|
+
class="filter-panel__input"
|
|
1036
|
+
.valueAsDate=${column.filter.value}
|
|
1037
|
+
@change=${(e) => this._handleFilterDateChange(e, column)}
|
|
1038
|
+
/>
|
|
1039
|
+
`;
|
|
1040
|
+
}
|
|
1041
|
+
else if (column.type === 'number') {
|
|
1042
|
+
valueInput = html `
|
|
1043
|
+
<input
|
|
1044
|
+
type="number"
|
|
1045
|
+
class="filter-panel__input"
|
|
1046
|
+
placeholder="Value"
|
|
1047
|
+
min="0"
|
|
1048
|
+
.value=${column.filter.text}
|
|
1049
|
+
@input=${(e) => this._handleFilterNumberChange(e, column)}
|
|
1050
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
1051
|
+
/>
|
|
1052
|
+
`;
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
valueInput = html `
|
|
1056
|
+
<input
|
|
1057
|
+
type="text"
|
|
1058
|
+
class="filter-panel__input"
|
|
1059
|
+
placeholder="Value"
|
|
1060
|
+
.value=${column.filter.text}
|
|
1061
|
+
@input=${(e) => this._handleFilterStringChange(e, column)}
|
|
1062
|
+
@keydown=${(e) => this._handleFilterTextKeydown(e, column)}
|
|
1063
|
+
/>
|
|
1064
|
+
`;
|
|
1065
|
+
}
|
|
1066
|
+
const filterContent = html `
|
|
1067
|
+
<div class="filter-panel__content">
|
|
1068
|
+
<kr-select-field
|
|
1069
|
+
.value=${column.filter.operator}
|
|
1070
|
+
@change=${(e) => this._handleOperatorChange(e, column)}
|
|
1071
|
+
>
|
|
1072
|
+
${getOperatorsForType(column.type).map(op => html `
|
|
1073
|
+
<kr-select-option value=${op.key}>${op.label}</kr-select-option>
|
|
1074
|
+
`)}
|
|
1075
|
+
</kr-select-field>
|
|
1076
|
+
${valueInput}
|
|
1077
|
+
</div>
|
|
1078
|
+
`;
|
|
1079
|
+
// Build bucket list content
|
|
1080
|
+
const buckets = this._buckets.get(column.id) || [];
|
|
1081
|
+
let bucketContent;
|
|
1082
|
+
if (!buckets.length) {
|
|
1083
|
+
bucketContent = html `<div class="bucket-empty">No data</div>`;
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
bucketContent = html `
|
|
1087
|
+
<div class="buckets">
|
|
1088
|
+
${buckets.map(bucket => {
|
|
1089
|
+
let bucketLabel = '(Empty)';
|
|
1090
|
+
if (bucket.val !== null && bucket.val !== undefined) {
|
|
1091
|
+
if (column.type === 'boolean') {
|
|
1092
|
+
if (bucket.val === true || bucket.val === 'true') {
|
|
1093
|
+
bucketLabel = 'Yes';
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
bucketLabel = 'No';
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
bucketLabel = String(bucket.val);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
let checkIcon = nothing;
|
|
1104
|
+
if (column.filter.has(bucket.val)) {
|
|
1105
|
+
checkIcon = html `
|
|
1106
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1107
|
+
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
|
1108
|
+
</svg>
|
|
1109
|
+
`;
|
|
1110
|
+
}
|
|
1111
|
+
return html `
|
|
1112
|
+
<div
|
|
1113
|
+
class="bucket"
|
|
1114
|
+
@click=${(e) => this._handleBucketToggle(e, column, bucket)}
|
|
1115
|
+
>
|
|
1116
|
+
<div class=${classMap({
|
|
1117
|
+
'bucket__checkbox': true,
|
|
1118
|
+
'bucket__checkbox--checked': column.filter.has(bucket.val)
|
|
1119
|
+
})}>
|
|
1120
|
+
${checkIcon}
|
|
1121
|
+
</div>
|
|
1122
|
+
<span class="bucket__label">${bucketLabel}</span>
|
|
1123
|
+
<span class="bucket__count">${bucket.count}</span>
|
|
1124
|
+
</div>
|
|
1125
|
+
`;
|
|
1126
|
+
})}
|
|
1127
|
+
</div>
|
|
1128
|
+
`;
|
|
1129
|
+
}
|
|
1130
|
+
// Build panel body — tabs if both filterable+facetable, otherwise just the relevant content
|
|
1131
|
+
let panelBody;
|
|
1132
|
+
if (column.facetable && column.filterable) {
|
|
1133
|
+
panelBody = html `
|
|
1134
|
+
<kr-tab-group
|
|
1135
|
+
size="small"
|
|
1136
|
+
active-tab-id=${this._filterPanelTab}
|
|
1137
|
+
@tab-change=${(e) => this._handleFilterPanelTabChange(e)}
|
|
1138
|
+
>
|
|
1139
|
+
<kr-tab id="filter" label="Filter">
|
|
1140
|
+
${filterContent}
|
|
1141
|
+
</kr-tab>
|
|
1142
|
+
<kr-tab id="counts" label="Counts">
|
|
1143
|
+
${bucketContent}
|
|
1144
|
+
</kr-tab>
|
|
1145
|
+
</kr-tab-group>
|
|
1146
|
+
`;
|
|
1147
|
+
}
|
|
1148
|
+
else if (column.facetable) {
|
|
1149
|
+
panelBody = bucketContent;
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
panelBody = filterContent;
|
|
1153
|
+
}
|
|
1154
|
+
return html `
|
|
1155
|
+
<div
|
|
1156
|
+
class="filter-panel"
|
|
1157
|
+
style=${styleMap({
|
|
1158
|
+
top: this._filterPanelPos.top + 'px',
|
|
1159
|
+
left: this._filterPanelPos.left + 'px'
|
|
1160
|
+
})}
|
|
1161
|
+
>
|
|
1162
|
+
${panelBody}
|
|
1163
|
+
<div class="filter-panel__actions">
|
|
1164
|
+
<kr-button variant="outline" color="secondary" size="small" @click=${this._handleFilterClear}>
|
|
1165
|
+
Clear
|
|
1166
|
+
</kr-button>
|
|
1167
|
+
<kr-button size="small" @click=${this._handleFilterApply}>
|
|
1168
|
+
Apply
|
|
1169
|
+
</kr-button>
|
|
1170
|
+
</div>
|
|
1171
|
+
</div>
|
|
1172
|
+
`;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Renders filter row below column headers.
|
|
1176
|
+
* Only displays for columns with filterable: true.
|
|
1177
|
+
*/
|
|
1178
|
+
_renderFilterRow() {
|
|
1179
|
+
const columns = this.getDisplayedColumns();
|
|
1180
|
+
if (!columns.some(col => col.filterable || col.facetable)) {
|
|
1181
|
+
return nothing;
|
|
1182
|
+
}
|
|
1183
|
+
return html `
|
|
1184
|
+
<div class="filter-row">
|
|
1185
|
+
${columns.map((col, i) => {
|
|
1186
|
+
if (!col.filterable && !col.facetable) {
|
|
1187
|
+
return html `<div
|
|
1188
|
+
class=${classMap({
|
|
1189
|
+
'filter-cell': true,
|
|
1190
|
+
'filter-cell--sticky-left': col.sticky === 'left',
|
|
1191
|
+
'filter-cell--sticky-right': col.sticky === 'right',
|
|
1192
|
+
'filter-cell--sticky-right-first': col.sticky === 'right' &&
|
|
1193
|
+
!columns.slice(0, i).some((c) => c.sticky === 'right')
|
|
1194
|
+
})}
|
|
1195
|
+
style=${styleMap(this._getCellStyle(col, i))}
|
|
1196
|
+
></div>`;
|
|
1197
|
+
}
|
|
1198
|
+
return html `
|
|
1199
|
+
<div
|
|
1200
|
+
class=${classMap({
|
|
1201
|
+
'filter-cell': true,
|
|
1202
|
+
'filter-cell--sticky-left': col.sticky === 'left',
|
|
1203
|
+
'filter-cell--sticky-right': col.sticky === 'right',
|
|
1204
|
+
'filter-cell--sticky-right-first': col.sticky === 'right' &&
|
|
1205
|
+
!columns.slice(0, i).some((c) => c.sticky === 'right')
|
|
1206
|
+
})}
|
|
1207
|
+
style=${styleMap(this._getCellStyle(col, i))}
|
|
1208
|
+
>
|
|
1209
|
+
<div class="filter-cell__wrapper">
|
|
1210
|
+
<input
|
|
1211
|
+
type="text"
|
|
1212
|
+
class=${classMap({
|
|
1213
|
+
'filter-cell__input': true,
|
|
1214
|
+
'filter-cell__input--invalid': !col.filter.isValid()
|
|
1215
|
+
})}
|
|
1216
|
+
.value=${col.filter.kql}
|
|
1217
|
+
@change=${(e) => this._handleKqlChange(e, col)}
|
|
1218
|
+
/>
|
|
1219
|
+
${col.filter?.kql?.length > 0 ? html `
|
|
1220
|
+
<button
|
|
1221
|
+
class="filter-cell__clear"
|
|
1222
|
+
@click=${() => this._handleKqlClear(col)}
|
|
1223
|
+
>
|
|
1224
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1225
|
+
<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"/>
|
|
1226
|
+
</svg>
|
|
1227
|
+
</button>
|
|
1228
|
+
` : nothing}
|
|
1229
|
+
<button
|
|
1230
|
+
class=${classMap({
|
|
1231
|
+
'filter-cell__advanced': true,
|
|
1232
|
+
'filter-cell__advanced--opened': this._filterPanelOpened === col.id
|
|
1233
|
+
})}
|
|
1234
|
+
@click=${(e) => this._handleFilterPanelToggle(e, col)}
|
|
1235
|
+
>
|
|
1236
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1237
|
+
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
|
|
1238
|
+
</svg>
|
|
1239
|
+
</button>
|
|
1240
|
+
</div>
|
|
1241
|
+
</div>
|
|
1242
|
+
`;
|
|
1243
|
+
})}
|
|
1244
|
+
</div>
|
|
1245
|
+
`;
|
|
1246
|
+
}
|
|
672
1247
|
/** Renders the scrollable data grid with column headers and rows. */
|
|
673
1248
|
_renderTable() {
|
|
674
1249
|
return html `
|
|
@@ -690,6 +1265,7 @@ let KRTable = class KRTable extends LitElement {
|
|
|
690
1265
|
></div>` : nothing}</div>
|
|
691
1266
|
`)}
|
|
692
1267
|
</div>
|
|
1268
|
+
${this._renderFilterRow()}
|
|
693
1269
|
${this._data.map((row, rowIndex) => {
|
|
694
1270
|
const cells = this.getDisplayedColumns().map((col, i) => html `
|
|
695
1271
|
<div
|
|
@@ -700,10 +1276,10 @@ let KRTable = class KRTable extends LitElement {
|
|
|
700
1276
|
${this._renderCellContent(col, row, rowIndex)}
|
|
701
1277
|
</div>
|
|
702
1278
|
`);
|
|
703
|
-
if (this.
|
|
1279
|
+
if (this._model.rowHref) {
|
|
704
1280
|
return html `
|
|
705
1281
|
<a
|
|
706
|
-
href=${this.
|
|
1282
|
+
href=${this._model.rowHref(row)}
|
|
707
1283
|
class=${classMap({ 'row': true, 'row--clickable': true, 'row--link': true })}
|
|
708
1284
|
@mousedown=${() => this._handleRowMouseDown()}
|
|
709
1285
|
@click=${() => this._handleRowClick(row, rowIndex)}
|
|
@@ -712,7 +1288,7 @@ let KRTable = class KRTable extends LitElement {
|
|
|
712
1288
|
}
|
|
713
1289
|
return html `
|
|
714
1290
|
<div
|
|
715
|
-
class=${classMap({ 'row': true, 'row--clickable': !!this.
|
|
1291
|
+
class=${classMap({ 'row': true, 'row--clickable': !!this._model.rowClickable })}
|
|
716
1292
|
@mousedown=${() => this._handleRowMouseDown()}
|
|
717
1293
|
@click=${() => this._handleRowClick(row, rowIndex)}
|
|
718
1294
|
>${cells}</div>
|
|
@@ -730,12 +1306,13 @@ let KRTable = class KRTable extends LitElement {
|
|
|
730
1306
|
* - Loading, error message, or empty state when no data
|
|
731
1307
|
*/
|
|
732
1308
|
render() {
|
|
733
|
-
if (!this.
|
|
1309
|
+
if (!this._model.columns.length) {
|
|
734
1310
|
return html `<slot></slot>`;
|
|
735
1311
|
}
|
|
736
1312
|
return html `
|
|
737
1313
|
${this._renderHeader()}
|
|
738
1314
|
${this._renderTable()}
|
|
1315
|
+
${this._renderFilterPanel()}
|
|
739
1316
|
`;
|
|
740
1317
|
}
|
|
741
1318
|
};
|
|
@@ -1084,6 +1661,7 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1084
1661
|
overflow: hidden;
|
|
1085
1662
|
text-overflow: ellipsis;
|
|
1086
1663
|
box-sizing: border-box;
|
|
1664
|
+
border-right: 1px solid #e5e7ebba;
|
|
1087
1665
|
}
|
|
1088
1666
|
|
|
1089
1667
|
.cell--actions {
|
|
@@ -1100,7 +1678,9 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1100
1678
|
white-space: nowrap;
|
|
1101
1679
|
box-sizing: border-box;
|
|
1102
1680
|
background: #f9fafb;
|
|
1681
|
+
border-top: 1px solid #e5e7eb;
|
|
1103
1682
|
border-bottom: 2px solid #e5e7eb;
|
|
1683
|
+
border-right: 1px solid #e5e7ebba;
|
|
1104
1684
|
font-weight: 600;
|
|
1105
1685
|
color: #374151;
|
|
1106
1686
|
overflow: hidden;
|
|
@@ -1198,7 +1778,6 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1198
1778
|
* ----------------------------------------------------------------------- */
|
|
1199
1779
|
:host(.kr-table--scroll-overlay) .content {
|
|
1200
1780
|
padding-left: 24px;
|
|
1201
|
-
padding-right: 24px;
|
|
1202
1781
|
}
|
|
1203
1782
|
|
|
1204
1783
|
.overlay-left,
|
|
@@ -1257,6 +1836,301 @@ KRTable.styles = [krBaseCSS, css `
|
|
|
1257
1836
|
.status--error {
|
|
1258
1837
|
color: #dc2626;
|
|
1259
1838
|
}
|
|
1839
|
+
|
|
1840
|
+
/* -------------------------------------------------------------------------
|
|
1841
|
+
* Filter Row
|
|
1842
|
+
* ----------------------------------------------------------------------- */
|
|
1843
|
+
.filter-row {
|
|
1844
|
+
display: contents;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
.filter-cell {
|
|
1848
|
+
position: sticky;
|
|
1849
|
+
top: 48px;
|
|
1850
|
+
z-index: 2;
|
|
1851
|
+
height: 40px;
|
|
1852
|
+
padding: 4px 8px;
|
|
1853
|
+
display: flex;
|
|
1854
|
+
align-items: center;
|
|
1855
|
+
background: #fafbfc;
|
|
1856
|
+
border-bottom: 1px solid #e5e7eb;
|
|
1857
|
+
border-right: 1px solid #e5e7ebba;
|
|
1858
|
+
box-sizing: border-box;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
|
|
1862
|
+
.filter-cell--sticky-left,
|
|
1863
|
+
.filter-cell--sticky-right {
|
|
1864
|
+
position: sticky;
|
|
1865
|
+
z-index: 3;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
.filter-cell--sticky-right-first {
|
|
1869
|
+
border-left: 1px solid #d1d5db;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
.filter-cell__wrapper {
|
|
1873
|
+
position: relative;
|
|
1874
|
+
display: flex;
|
|
1875
|
+
align-items: center;
|
|
1876
|
+
gap: 4px;
|
|
1877
|
+
width: 100%;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
.filter-cell__input {
|
|
1881
|
+
width: 100%;
|
|
1882
|
+
height: 32px;
|
|
1883
|
+
padding: 0 52px 0 8px;
|
|
1884
|
+
border: 1px solid #d1d5db;
|
|
1885
|
+
border-radius: 6px;
|
|
1886
|
+
font-size: 14px;
|
|
1887
|
+
font-family: inherit;
|
|
1888
|
+
color: #111827;
|
|
1889
|
+
background: #fff;
|
|
1890
|
+
outline: none;
|
|
1891
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
.filter-cell__input:focus {
|
|
1895
|
+
border-color: #163052;
|
|
1896
|
+
box-shadow: 0 0 0 2px rgba(22, 48, 82, 0.1);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
.filter-cell__input--invalid {
|
|
1900
|
+
border-color: #dc2626;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
.filter-cell__input--invalid:focus {
|
|
1904
|
+
border-color: #dc2626;
|
|
1905
|
+
box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.1);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
.filter-cell__input::placeholder {
|
|
1909
|
+
color: #9ca3af;
|
|
1910
|
+
font-size: 13px;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
.filter-cell__clear {
|
|
1914
|
+
position: absolute;
|
|
1915
|
+
right: 28px;
|
|
1916
|
+
top: 50%;
|
|
1917
|
+
transform: translateY(-50%);
|
|
1918
|
+
display: flex;
|
|
1919
|
+
align-items: center;
|
|
1920
|
+
justify-content: center;
|
|
1921
|
+
width: 24px;
|
|
1922
|
+
height: 24px;
|
|
1923
|
+
padding: 0;
|
|
1924
|
+
border: none;
|
|
1925
|
+
border-radius: 4px;
|
|
1926
|
+
background: transparent;
|
|
1927
|
+
color: #6b7280;
|
|
1928
|
+
cursor: pointer;
|
|
1929
|
+
transition: background 0.15s, color 0.15s;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
.filter-cell__clear:hover {
|
|
1933
|
+
background: #e5e7eb;
|
|
1934
|
+
color: #374151;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
.filter-cell__clear svg {
|
|
1938
|
+
width: 16px;
|
|
1939
|
+
height: 16px;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
.filter-cell__advanced {
|
|
1943
|
+
position: absolute;
|
|
1944
|
+
right: 4px;
|
|
1945
|
+
top: 50%;
|
|
1946
|
+
transform: translateY(-50%);
|
|
1947
|
+
display: flex;
|
|
1948
|
+
align-items: center;
|
|
1949
|
+
justify-content: center;
|
|
1950
|
+
width: 24px;
|
|
1951
|
+
height: 24px;
|
|
1952
|
+
padding: 0;
|
|
1953
|
+
border: none;
|
|
1954
|
+
border-radius: 4px;
|
|
1955
|
+
background: transparent;
|
|
1956
|
+
color: #163052;
|
|
1957
|
+
cursor: pointer;
|
|
1958
|
+
transition: background 0.15s, color 0.15s;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
.filter-cell__advanced:hover {
|
|
1962
|
+
background: #e5e7eb;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
.filter-cell__advanced svg {
|
|
1966
|
+
width: 16px;
|
|
1967
|
+
height: 16px;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
.filter-cell__advanced--opened {
|
|
1971
|
+
background: #163052;
|
|
1972
|
+
color: #fff;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
.filter-cell__advanced--opened:hover {
|
|
1976
|
+
background: #1a3a5f;
|
|
1977
|
+
color: #fff;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/* -------------------------------------------------------------------------
|
|
1981
|
+
* Filter Panel (Advanced)
|
|
1982
|
+
* ----------------------------------------------------------------------- */
|
|
1983
|
+
.filter-panel {
|
|
1984
|
+
position: fixed;
|
|
1985
|
+
min-width: 320px;
|
|
1986
|
+
background: white;
|
|
1987
|
+
border: 1px solid #9ba7b6;
|
|
1988
|
+
border-radius: 8px;
|
|
1989
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
1990
|
+
z-index: 100;
|
|
1991
|
+
animation: filter-panel-fade-in 150ms ease-out;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
@keyframes filter-panel-fade-in {
|
|
1995
|
+
from {
|
|
1996
|
+
opacity: 0;
|
|
1997
|
+
transform: translateY(-4px);
|
|
1998
|
+
}
|
|
1999
|
+
to {
|
|
2000
|
+
opacity: 1;
|
|
2001
|
+
transform: translateY(0);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
.filter-panel__content {
|
|
2006
|
+
padding: 16px;
|
|
2007
|
+
display: flex;
|
|
2008
|
+
flex-direction: column;
|
|
2009
|
+
gap: 12px;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
.filter-panel__actions {
|
|
2013
|
+
padding: 12px 16px;
|
|
2014
|
+
border-top: 1px solid #e5e7eb;
|
|
2015
|
+
display: flex;
|
|
2016
|
+
justify-content: flex-end;
|
|
2017
|
+
align-items: center;
|
|
2018
|
+
gap: 12px;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
|
|
2022
|
+
.filter-panel__input {
|
|
2023
|
+
width: 100%;
|
|
2024
|
+
padding: 10px 12px;
|
|
2025
|
+
border: 1px solid #d1d5db;
|
|
2026
|
+
border-radius: 8px;
|
|
2027
|
+
font-size: 14px;
|
|
2028
|
+
font-family: inherit;
|
|
2029
|
+
color: #111827;
|
|
2030
|
+
background: #fff;
|
|
2031
|
+
outline: none;
|
|
2032
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
.filter-panel__input:focus {
|
|
2036
|
+
border-color: #163052;
|
|
2037
|
+
box-shadow: 0 0 0 2px rgba(22, 48, 82, 0.1);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
.filter-panel__input::placeholder {
|
|
2041
|
+
color: #9ca3af;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
.filter-panel__textarea {
|
|
2045
|
+
width: 100%;
|
|
2046
|
+
padding: 10px 12px;
|
|
2047
|
+
border: 1px solid #d1d5db;
|
|
2048
|
+
border-radius: 8px;
|
|
2049
|
+
font-size: 14px;
|
|
2050
|
+
font-family: inherit;
|
|
2051
|
+
color: #111827;
|
|
2052
|
+
background: #fff;
|
|
2053
|
+
outline: none;
|
|
2054
|
+
resize: vertical;
|
|
2055
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
.filter-panel__textarea:focus {
|
|
2059
|
+
border-color: #163052;
|
|
2060
|
+
box-shadow: 0 0 0 2px rgba(22, 48, 82, 0.1);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
.filter-panel__textarea::placeholder {
|
|
2064
|
+
color: #9ca3af;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/* -------------------------------------------------------------------------
|
|
2068
|
+
* Bucket List
|
|
2069
|
+
* ----------------------------------------------------------------------- */
|
|
2070
|
+
.buckets {
|
|
2071
|
+
max-height: 280px;
|
|
2072
|
+
overflow-y: auto;
|
|
2073
|
+
padding: 8px 0;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
.bucket {
|
|
2077
|
+
display: flex;
|
|
2078
|
+
align-items: center;
|
|
2079
|
+
gap: 16px;
|
|
2080
|
+
height: 32px;
|
|
2081
|
+
padding: 0 16px;
|
|
2082
|
+
cursor: pointer;
|
|
2083
|
+
transition: background 0.1s;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
.bucket:hover {
|
|
2087
|
+
background: #f3f4f6;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
.bucket__checkbox {
|
|
2091
|
+
width: 16px;
|
|
2092
|
+
height: 16px;
|
|
2093
|
+
border: 1.5px solid #9ca3af;
|
|
2094
|
+
border-radius: 3px;
|
|
2095
|
+
display: flex;
|
|
2096
|
+
align-items: center;
|
|
2097
|
+
justify-content: center;
|
|
2098
|
+
flex-shrink: 0;
|
|
2099
|
+
transition: all 0.15s;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
.bucket__checkbox--checked {
|
|
2103
|
+
background: var(--kr-primary, #163052);
|
|
2104
|
+
border-color: var(--kr-primary, #163052);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
.bucket__checkbox svg {
|
|
2108
|
+
width: 12px;
|
|
2109
|
+
height: 12px;
|
|
2110
|
+
color: white;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
.bucket__label {
|
|
2114
|
+
flex: 1;
|
|
2115
|
+
font-size: 14px;
|
|
2116
|
+
color: #000;
|
|
2117
|
+
overflow: hidden;
|
|
2118
|
+
text-overflow: ellipsis;
|
|
2119
|
+
white-space: nowrap;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
.bucket__count {
|
|
2123
|
+
font-size: 14px;
|
|
2124
|
+
color: #000;
|
|
2125
|
+
flex-shrink: 0;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
.bucket-empty {
|
|
2129
|
+
font-size: 14px;
|
|
2130
|
+
color: #000;
|
|
2131
|
+
padding: 16px;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
1260
2134
|
`];
|
|
1261
2135
|
__decorate([
|
|
1262
2136
|
state()
|
|
@@ -1293,7 +2167,13 @@ __decorate([
|
|
|
1293
2167
|
], KRTable.prototype, "_columnPickerOpen", void 0);
|
|
1294
2168
|
__decorate([
|
|
1295
2169
|
state()
|
|
1296
|
-
], KRTable.prototype, "
|
|
2170
|
+
], KRTable.prototype, "_filterPanelOpened", void 0);
|
|
2171
|
+
__decorate([
|
|
2172
|
+
state()
|
|
2173
|
+
], KRTable.prototype, "_filterPanelTab", void 0);
|
|
2174
|
+
__decorate([
|
|
2175
|
+
state()
|
|
2176
|
+
], KRTable.prototype, "_buckets", void 0);
|
|
1297
2177
|
__decorate([
|
|
1298
2178
|
property({ type: Object })
|
|
1299
2179
|
], KRTable.prototype, "def", void 0);
|