@chefuicore/core 1.0.0 → 1.0.2
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/dist/components/chef-ui-data-grid.d.ts +144 -0
- package/dist/components/chef-ui-data-grid.d.ts.map +1 -0
- package/dist/index.cjs +2403 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2401 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/data-grid-clipboard.d.ts +15 -0
- package/dist/utils/data-grid-clipboard.d.ts.map +1 -0
- package/dist/utils/data-grid-export.d.ts +22 -0
- package/dist/utils/data-grid-export.d.ts.map +1 -0
- package/dist/utils/data-grid-filter.d.ts +10 -0
- package/dist/utils/data-grid-filter.d.ts.map +1 -0
- package/dist/utils/data-grid-sort.d.ts +16 -0
- package/dist/utils/data-grid-sort.d.ts.map +1 -0
- package/dist/utils/data-grid-types.d.ts +114 -0
- package/dist/utils/data-grid-types.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11378,5 +11378,2405 @@ ChefUiRating = __decorate([
|
|
|
11378
11378
|
t('chef-ui-rating')
|
|
11379
11379
|
], ChefUiRating);
|
|
11380
11380
|
|
|
11381
|
-
|
|
11381
|
+
// ─── Column Types ───────────────────────────────────────────────────────────
|
|
11382
|
+
// ─── Density Config ─────────────────────────────────────────────────────────
|
|
11383
|
+
const DENSITY_ROW_HEIGHT = {
|
|
11384
|
+
compact: 36,
|
|
11385
|
+
standard: 52,
|
|
11386
|
+
comfortable: 64,
|
|
11387
|
+
};
|
|
11388
|
+
const DENSITY_HEADER_HEIGHT = {
|
|
11389
|
+
compact: 40,
|
|
11390
|
+
standard: 56,
|
|
11391
|
+
comfortable: 64,
|
|
11392
|
+
};
|
|
11393
|
+
// ─── Filter Operators by Type ───────────────────────────────────────────────
|
|
11394
|
+
const FILTER_OPERATORS = {
|
|
11395
|
+
string: [
|
|
11396
|
+
{ label: 'contains', value: 'contains' },
|
|
11397
|
+
{ label: 'equals', value: 'equals' },
|
|
11398
|
+
{ label: 'starts with', value: 'startsWith' },
|
|
11399
|
+
{ label: 'ends with', value: 'endsWith' },
|
|
11400
|
+
{ label: 'is empty', value: 'isEmpty' },
|
|
11401
|
+
{ label: 'is not empty', value: 'isNotEmpty' },
|
|
11402
|
+
],
|
|
11403
|
+
number: [
|
|
11404
|
+
{ label: '=', value: '=' },
|
|
11405
|
+
{ label: '!=', value: '!=' },
|
|
11406
|
+
{ label: '>', value: '>' },
|
|
11407
|
+
{ label: '>=', value: '>=' },
|
|
11408
|
+
{ label: '<', value: '<' },
|
|
11409
|
+
{ label: '<=', value: '<=' },
|
|
11410
|
+
],
|
|
11411
|
+
date: [
|
|
11412
|
+
{ label: 'is', value: 'is' },
|
|
11413
|
+
{ label: 'before', value: 'before' },
|
|
11414
|
+
{ label: 'after', value: 'after' },
|
|
11415
|
+
{ label: 'on or before', value: 'onOrBefore' },
|
|
11416
|
+
{ label: 'on or after', value: 'onOrAfter' },
|
|
11417
|
+
],
|
|
11418
|
+
boolean: [
|
|
11419
|
+
{ label: 'is true', value: 'isTrue' },
|
|
11420
|
+
{ label: 'is false', value: 'isFalse' },
|
|
11421
|
+
],
|
|
11422
|
+
singleSelect: [
|
|
11423
|
+
{ label: 'equals', value: 'equals' },
|
|
11424
|
+
{ label: 'is not', value: '!=' },
|
|
11425
|
+
],
|
|
11426
|
+
};
|
|
11427
|
+
|
|
11428
|
+
/**
|
|
11429
|
+
* Get the value of a cell for sorting, applying valueGetter if defined.
|
|
11430
|
+
*/
|
|
11431
|
+
function getCellValue$1(row, colDef) {
|
|
11432
|
+
if (colDef.valueGetter) {
|
|
11433
|
+
return colDef.valueGetter(row);
|
|
11434
|
+
}
|
|
11435
|
+
return row[colDef.field];
|
|
11436
|
+
}
|
|
11437
|
+
/**
|
|
11438
|
+
* Compare two values based on column type.
|
|
11439
|
+
*/
|
|
11440
|
+
function compareValues(a, b, type) {
|
|
11441
|
+
// Handle nulls/undefined
|
|
11442
|
+
if (a == null && b == null)
|
|
11443
|
+
return 0;
|
|
11444
|
+
if (a == null)
|
|
11445
|
+
return -1;
|
|
11446
|
+
if (b == null)
|
|
11447
|
+
return 1;
|
|
11448
|
+
switch (type) {
|
|
11449
|
+
case 'number':
|
|
11450
|
+
return Number(a) - Number(b);
|
|
11451
|
+
case 'date': {
|
|
11452
|
+
const da = a instanceof Date ? a.getTime() : new Date(a).getTime();
|
|
11453
|
+
const db = b instanceof Date ? b.getTime() : new Date(b).getTime();
|
|
11454
|
+
if (isNaN(da) && isNaN(db))
|
|
11455
|
+
return 0;
|
|
11456
|
+
if (isNaN(da))
|
|
11457
|
+
return -1;
|
|
11458
|
+
if (isNaN(db))
|
|
11459
|
+
return 1;
|
|
11460
|
+
return da - db;
|
|
11461
|
+
}
|
|
11462
|
+
case 'boolean': {
|
|
11463
|
+
const ba = a === true || a === 'true' ? 1 : 0;
|
|
11464
|
+
const bb = b === true || b === 'true' ? 1 : 0;
|
|
11465
|
+
return ba - bb;
|
|
11466
|
+
}
|
|
11467
|
+
case 'string':
|
|
11468
|
+
case 'singleSelect':
|
|
11469
|
+
default:
|
|
11470
|
+
return String(a).localeCompare(String(b), undefined, { sensitivity: 'base' });
|
|
11471
|
+
}
|
|
11472
|
+
}
|
|
11473
|
+
/**
|
|
11474
|
+
* Sort rows using a multi-column sort model.
|
|
11475
|
+
* Uses stable sort (maintains relative order of equal elements).
|
|
11476
|
+
*/
|
|
11477
|
+
function sortRows(rows, sortModel, columns) {
|
|
11478
|
+
if (!sortModel || sortModel.length === 0)
|
|
11479
|
+
return rows;
|
|
11480
|
+
const colMap = new Map();
|
|
11481
|
+
for (const col of columns) {
|
|
11482
|
+
colMap.set(col.field, col);
|
|
11483
|
+
}
|
|
11484
|
+
// Create indexed array for stable sort
|
|
11485
|
+
const indexed = rows.map((row, idx) => ({ row, idx }));
|
|
11486
|
+
indexed.sort((a, b) => {
|
|
11487
|
+
for (const sortItem of sortModel) {
|
|
11488
|
+
const colDef = colMap.get(sortItem.field);
|
|
11489
|
+
if (!colDef)
|
|
11490
|
+
continue;
|
|
11491
|
+
const valA = getCellValue$1(a.row, colDef);
|
|
11492
|
+
const valB = getCellValue$1(b.row, colDef);
|
|
11493
|
+
const type = colDef.type || 'string';
|
|
11494
|
+
let cmp = compareValues(valA, valB, type);
|
|
11495
|
+
if (sortItem.sort === 'desc')
|
|
11496
|
+
cmp = -cmp;
|
|
11497
|
+
if (cmp !== 0)
|
|
11498
|
+
return cmp;
|
|
11499
|
+
}
|
|
11500
|
+
// Stable sort: preserve original order
|
|
11501
|
+
return a.idx - b.idx;
|
|
11502
|
+
});
|
|
11503
|
+
return indexed.map(item => item.row);
|
|
11504
|
+
}
|
|
11505
|
+
/**
|
|
11506
|
+
* Cycle sort direction: null -> 'asc' -> 'desc' -> null
|
|
11507
|
+
*/
|
|
11508
|
+
function getNextSortDirection(current) {
|
|
11509
|
+
if (current === null || current === undefined)
|
|
11510
|
+
return 'asc';
|
|
11511
|
+
if (current === 'asc')
|
|
11512
|
+
return 'desc';
|
|
11513
|
+
return null;
|
|
11514
|
+
}
|
|
11515
|
+
/**
|
|
11516
|
+
* Toggle sort for a field in the sort model.
|
|
11517
|
+
* If multiSort is false, replaces the entire sort model.
|
|
11518
|
+
*/
|
|
11519
|
+
function toggleSort(sortModel, field, multiSort) {
|
|
11520
|
+
const existingIndex = sortModel.findIndex(s => s.field === field);
|
|
11521
|
+
const existingDirection = existingIndex >= 0 ? sortModel[existingIndex].sort : null;
|
|
11522
|
+
const nextDirection = getNextSortDirection(existingDirection === 'asc' ? 'asc' : existingDirection === 'desc' ? 'desc' : null);
|
|
11523
|
+
if (multiSort) {
|
|
11524
|
+
const newModel = [...sortModel];
|
|
11525
|
+
if (existingIndex >= 0) {
|
|
11526
|
+
if (nextDirection === null) {
|
|
11527
|
+
newModel.splice(existingIndex, 1);
|
|
11528
|
+
}
|
|
11529
|
+
else {
|
|
11530
|
+
newModel[existingIndex] = { field, sort: nextDirection };
|
|
11531
|
+
}
|
|
11532
|
+
}
|
|
11533
|
+
else if (nextDirection !== null) {
|
|
11534
|
+
newModel.push({ field, sort: nextDirection });
|
|
11535
|
+
}
|
|
11536
|
+
return newModel;
|
|
11537
|
+
}
|
|
11538
|
+
else {
|
|
11539
|
+
if (nextDirection === null)
|
|
11540
|
+
return [];
|
|
11541
|
+
return [{ field, sort: nextDirection }];
|
|
11542
|
+
}
|
|
11543
|
+
}
|
|
11544
|
+
|
|
11545
|
+
/**
|
|
11546
|
+
* Get the value of a cell for filtering, applying valueGetter if defined.
|
|
11547
|
+
*/
|
|
11548
|
+
function getCellValue(row, colDef) {
|
|
11549
|
+
if (colDef.valueGetter) {
|
|
11550
|
+
return colDef.valueGetter(row);
|
|
11551
|
+
}
|
|
11552
|
+
return row[colDef.field];
|
|
11553
|
+
}
|
|
11554
|
+
/**
|
|
11555
|
+
* Evaluate a single filter item against a row.
|
|
11556
|
+
*/
|
|
11557
|
+
function evaluateFilter(row, filter, colDef) {
|
|
11558
|
+
if (!colDef)
|
|
11559
|
+
return true;
|
|
11560
|
+
const value = getCellValue(row, colDef);
|
|
11561
|
+
const filterValue = filter.value;
|
|
11562
|
+
const type = colDef.type || 'string';
|
|
11563
|
+
switch (filter.operator) {
|
|
11564
|
+
// ── String operators ──
|
|
11565
|
+
case 'contains':
|
|
11566
|
+
if (value == null)
|
|
11567
|
+
return false;
|
|
11568
|
+
return String(value).toLowerCase().includes(String(filterValue || '').toLowerCase());
|
|
11569
|
+
case 'equals':
|
|
11570
|
+
if (value == null && filterValue == null)
|
|
11571
|
+
return true;
|
|
11572
|
+
if (value == null || filterValue == null)
|
|
11573
|
+
return false;
|
|
11574
|
+
return String(value).toLowerCase() === String(filterValue).toLowerCase();
|
|
11575
|
+
case 'startsWith':
|
|
11576
|
+
if (value == null)
|
|
11577
|
+
return false;
|
|
11578
|
+
return String(value).toLowerCase().startsWith(String(filterValue || '').toLowerCase());
|
|
11579
|
+
case 'endsWith':
|
|
11580
|
+
if (value == null)
|
|
11581
|
+
return false;
|
|
11582
|
+
return String(value).toLowerCase().endsWith(String(filterValue || '').toLowerCase());
|
|
11583
|
+
case 'isEmpty':
|
|
11584
|
+
return value == null || String(value).trim() === '';
|
|
11585
|
+
case 'isNotEmpty':
|
|
11586
|
+
return value != null && String(value).trim() !== '';
|
|
11587
|
+
// ── Number operators ──
|
|
11588
|
+
case '=':
|
|
11589
|
+
return Number(value) === Number(filterValue);
|
|
11590
|
+
case '!=':
|
|
11591
|
+
return Number(value) !== Number(filterValue);
|
|
11592
|
+
case '>':
|
|
11593
|
+
return Number(value) > Number(filterValue);
|
|
11594
|
+
case '>=':
|
|
11595
|
+
return Number(value) >= Number(filterValue);
|
|
11596
|
+
case '<':
|
|
11597
|
+
return Number(value) < Number(filterValue);
|
|
11598
|
+
case '<=':
|
|
11599
|
+
return Number(value) <= Number(filterValue);
|
|
11600
|
+
// ── Date operators ──
|
|
11601
|
+
case 'is': {
|
|
11602
|
+
if (type === 'date') {
|
|
11603
|
+
const d = toDateString(value);
|
|
11604
|
+
const fd = toDateString(filterValue);
|
|
11605
|
+
return d === fd;
|
|
11606
|
+
}
|
|
11607
|
+
return String(value) === String(filterValue);
|
|
11608
|
+
}
|
|
11609
|
+
case 'before': {
|
|
11610
|
+
const d = toDate(value);
|
|
11611
|
+
const fd = toDate(filterValue);
|
|
11612
|
+
if (!d || !fd)
|
|
11613
|
+
return false;
|
|
11614
|
+
return d < fd;
|
|
11615
|
+
}
|
|
11616
|
+
case 'after': {
|
|
11617
|
+
const d = toDate(value);
|
|
11618
|
+
const fd = toDate(filterValue);
|
|
11619
|
+
if (!d || !fd)
|
|
11620
|
+
return false;
|
|
11621
|
+
return d > fd;
|
|
11622
|
+
}
|
|
11623
|
+
case 'onOrBefore': {
|
|
11624
|
+
const d = toDate(value);
|
|
11625
|
+
const fd = toDate(filterValue);
|
|
11626
|
+
if (!d || !fd)
|
|
11627
|
+
return false;
|
|
11628
|
+
return d <= fd;
|
|
11629
|
+
}
|
|
11630
|
+
case 'onOrAfter': {
|
|
11631
|
+
const d = toDate(value);
|
|
11632
|
+
const fd = toDate(filterValue);
|
|
11633
|
+
if (!d || !fd)
|
|
11634
|
+
return false;
|
|
11635
|
+
return d >= fd;
|
|
11636
|
+
}
|
|
11637
|
+
// ── Boolean operators ──
|
|
11638
|
+
case 'isTrue':
|
|
11639
|
+
return value === true || value === 'true' || value === 1;
|
|
11640
|
+
case 'isFalse':
|
|
11641
|
+
return value === false || value === 'false' || value === 0 || value == null;
|
|
11642
|
+
default:
|
|
11643
|
+
return true;
|
|
11644
|
+
}
|
|
11645
|
+
}
|
|
11646
|
+
function toDate(v) {
|
|
11647
|
+
if (!v)
|
|
11648
|
+
return null;
|
|
11649
|
+
const d = v instanceof Date ? v : new Date(v);
|
|
11650
|
+
return isNaN(d.getTime()) ? null : d;
|
|
11651
|
+
}
|
|
11652
|
+
function toDateString(v) {
|
|
11653
|
+
const d = toDate(v);
|
|
11654
|
+
if (!d)
|
|
11655
|
+
return '';
|
|
11656
|
+
return d.toISOString().split('T')[0];
|
|
11657
|
+
}
|
|
11658
|
+
/**
|
|
11659
|
+
* Filter rows using the filter model.
|
|
11660
|
+
*/
|
|
11661
|
+
function filterRows(rows, filterModel, columns) {
|
|
11662
|
+
const colMap = new Map();
|
|
11663
|
+
for (const col of columns) {
|
|
11664
|
+
colMap.set(col.field, col);
|
|
11665
|
+
}
|
|
11666
|
+
let result = rows;
|
|
11667
|
+
// Apply quick filter
|
|
11668
|
+
if (filterModel.quickFilterValue && filterModel.quickFilterValue.trim()) {
|
|
11669
|
+
const query = filterModel.quickFilterValue.toLowerCase().trim();
|
|
11670
|
+
result = result.filter(row => {
|
|
11671
|
+
return columns.some(col => {
|
|
11672
|
+
const val = getCellValue(row, col);
|
|
11673
|
+
if (val == null)
|
|
11674
|
+
return false;
|
|
11675
|
+
return String(val).toLowerCase().includes(query);
|
|
11676
|
+
});
|
|
11677
|
+
});
|
|
11678
|
+
}
|
|
11679
|
+
// Apply column filters
|
|
11680
|
+
const activeFilters = filterModel.items.filter(item => {
|
|
11681
|
+
// Skip filters without values (except isEmpty/isNotEmpty/isTrue/isFalse)
|
|
11682
|
+
const noValueOps = ['isEmpty', 'isNotEmpty', 'isTrue', 'isFalse'];
|
|
11683
|
+
if (noValueOps.includes(item.operator))
|
|
11684
|
+
return true;
|
|
11685
|
+
return item.value !== undefined && item.value !== null && item.value !== '';
|
|
11686
|
+
});
|
|
11687
|
+
if (activeFilters.length === 0)
|
|
11688
|
+
return result;
|
|
11689
|
+
const logicOp = filterModel.logicOperator || 'and';
|
|
11690
|
+
return result.filter(row => {
|
|
11691
|
+
if (logicOp === 'and') {
|
|
11692
|
+
return activeFilters.every(filter => evaluateFilter(row, filter, colMap.get(filter.field)));
|
|
11693
|
+
}
|
|
11694
|
+
else {
|
|
11695
|
+
return activeFilters.some(filter => evaluateFilter(row, filter, colMap.get(filter.field)));
|
|
11696
|
+
}
|
|
11697
|
+
});
|
|
11698
|
+
}
|
|
11699
|
+
/**
|
|
11700
|
+
* Get the default operator for a column type.
|
|
11701
|
+
*/
|
|
11702
|
+
function getDefaultOperator(type) {
|
|
11703
|
+
switch (type) {
|
|
11704
|
+
case 'number': return '=';
|
|
11705
|
+
case 'date': return 'is';
|
|
11706
|
+
case 'boolean': return 'isTrue';
|
|
11707
|
+
case 'singleSelect': return 'equals';
|
|
11708
|
+
default: return 'contains';
|
|
11709
|
+
}
|
|
11710
|
+
}
|
|
11711
|
+
|
|
11712
|
+
/**
|
|
11713
|
+
* Get the display value for a cell, applying valueFormatter if defined.
|
|
11714
|
+
*/
|
|
11715
|
+
function getDisplayValue$1(row, colDef) {
|
|
11716
|
+
let value = colDef.valueGetter ? colDef.valueGetter(row) : row[colDef.field];
|
|
11717
|
+
if (colDef.valueFormatter) {
|
|
11718
|
+
value = colDef.valueFormatter(value, row);
|
|
11719
|
+
}
|
|
11720
|
+
if (value == null)
|
|
11721
|
+
return '';
|
|
11722
|
+
return String(value);
|
|
11723
|
+
}
|
|
11724
|
+
/**
|
|
11725
|
+
* Escape a CSV cell value (wrap in quotes if contains comma, newline, or quote).
|
|
11726
|
+
*/
|
|
11727
|
+
function escapeCsvCell(value) {
|
|
11728
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) {
|
|
11729
|
+
return '"' + value.replace(/"/g, '""') + '"';
|
|
11730
|
+
}
|
|
11731
|
+
return value;
|
|
11732
|
+
}
|
|
11733
|
+
/**
|
|
11734
|
+
* Export rows to CSV format string.
|
|
11735
|
+
*/
|
|
11736
|
+
function exportToCsv(rows, columns, options = {}) {
|
|
11737
|
+
const delimiter = options.delimiter || ',';
|
|
11738
|
+
const includeHeaders = options.includeHeaders !== false;
|
|
11739
|
+
const lines = [];
|
|
11740
|
+
if (includeHeaders) {
|
|
11741
|
+
const headers = columns.map(col => escapeCsvCell(col.headerName || col.field));
|
|
11742
|
+
lines.push(headers.join(delimiter));
|
|
11743
|
+
}
|
|
11744
|
+
for (const row of rows) {
|
|
11745
|
+
const cells = columns.map(col => escapeCsvCell(getDisplayValue$1(row, col)));
|
|
11746
|
+
lines.push(cells.join(delimiter));
|
|
11747
|
+
}
|
|
11748
|
+
let content = lines.join('\n');
|
|
11749
|
+
if (options.utf8WithBom !== false) {
|
|
11750
|
+
content = '\uFEFF' + content;
|
|
11751
|
+
}
|
|
11752
|
+
return content;
|
|
11753
|
+
}
|
|
11754
|
+
/**
|
|
11755
|
+
* Export rows to Excel-compatible format (tab-separated).
|
|
11756
|
+
*/
|
|
11757
|
+
function exportToExcel(rows, columns, options = {}) {
|
|
11758
|
+
return exportToCsv(rows, columns, { ...options, delimiter: '\t' });
|
|
11759
|
+
}
|
|
11760
|
+
/**
|
|
11761
|
+
* Trigger a file download in the browser.
|
|
11762
|
+
*/
|
|
11763
|
+
function downloadFile(content, fileName, mimeType) {
|
|
11764
|
+
const blob = new Blob([content], { type: mimeType });
|
|
11765
|
+
const url = URL.createObjectURL(blob);
|
|
11766
|
+
const link = document.createElement('a');
|
|
11767
|
+
link.href = url;
|
|
11768
|
+
link.download = fileName;
|
|
11769
|
+
link.style.display = 'none';
|
|
11770
|
+
document.body.appendChild(link);
|
|
11771
|
+
link.click();
|
|
11772
|
+
document.body.removeChild(link);
|
|
11773
|
+
URL.revokeObjectURL(url);
|
|
11774
|
+
}
|
|
11775
|
+
/**
|
|
11776
|
+
* Export and download as CSV.
|
|
11777
|
+
*/
|
|
11778
|
+
function downloadCsv(rows, columns, options = {}) {
|
|
11779
|
+
const csv = exportToCsv(rows, columns, options);
|
|
11780
|
+
const fileName = (options.fileName || 'data') + '.csv';
|
|
11781
|
+
downloadFile(csv, fileName, 'text/csv;charset=utf-8;');
|
|
11782
|
+
}
|
|
11783
|
+
/**
|
|
11784
|
+
* Export and download as Excel (tab-separated .xls).
|
|
11785
|
+
*/
|
|
11786
|
+
function downloadExcel(rows, columns, options = {}) {
|
|
11787
|
+
const tsv = exportToExcel(rows, columns, options);
|
|
11788
|
+
const fileName = (options.fileName || 'data') + '.xls';
|
|
11789
|
+
downloadFile(tsv, fileName, 'application/vnd.ms-excel;charset=utf-8;');
|
|
11790
|
+
}
|
|
11791
|
+
|
|
11792
|
+
/**
|
|
11793
|
+
* Get the display value for a cell.
|
|
11794
|
+
*/
|
|
11795
|
+
function getDisplayValue(row, colDef) {
|
|
11796
|
+
let value = colDef.valueGetter ? colDef.valueGetter(row) : row[colDef.field];
|
|
11797
|
+
if (colDef.valueFormatter) {
|
|
11798
|
+
value = colDef.valueFormatter(value, row);
|
|
11799
|
+
}
|
|
11800
|
+
if (value == null)
|
|
11801
|
+
return '';
|
|
11802
|
+
return String(value);
|
|
11803
|
+
}
|
|
11804
|
+
/**
|
|
11805
|
+
* Convert selected rows to tab-separated text for clipboard.
|
|
11806
|
+
* Compatible with Excel/Google Sheets paste.
|
|
11807
|
+
*/
|
|
11808
|
+
function rowsToClipboardText(rows, columns, includeHeaders = true) {
|
|
11809
|
+
const lines = [];
|
|
11810
|
+
if (includeHeaders) {
|
|
11811
|
+
lines.push(columns.map(col => col.headerName || col.field).join('\t'));
|
|
11812
|
+
}
|
|
11813
|
+
for (const row of rows) {
|
|
11814
|
+
const cells = columns.map(col => {
|
|
11815
|
+
const val = getDisplayValue(row, col);
|
|
11816
|
+
// Escape tabs and newlines
|
|
11817
|
+
return val.replace(/\t/g, ' ').replace(/\n/g, ' ');
|
|
11818
|
+
});
|
|
11819
|
+
lines.push(cells.join('\t'));
|
|
11820
|
+
}
|
|
11821
|
+
return lines.join('\n');
|
|
11822
|
+
}
|
|
11823
|
+
/**
|
|
11824
|
+
* Copy text to clipboard using the Clipboard API.
|
|
11825
|
+
*/
|
|
11826
|
+
async function copyToClipboard(text) {
|
|
11827
|
+
try {
|
|
11828
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
11829
|
+
await navigator.clipboard.writeText(text);
|
|
11830
|
+
return true;
|
|
11831
|
+
}
|
|
11832
|
+
// Fallback for older browsers
|
|
11833
|
+
const textarea = document.createElement('textarea');
|
|
11834
|
+
textarea.value = text;
|
|
11835
|
+
textarea.style.position = 'fixed';
|
|
11836
|
+
textarea.style.left = '-9999px';
|
|
11837
|
+
document.body.appendChild(textarea);
|
|
11838
|
+
textarea.select();
|
|
11839
|
+
const success = document.execCommand('copy');
|
|
11840
|
+
document.body.removeChild(textarea);
|
|
11841
|
+
return success;
|
|
11842
|
+
}
|
|
11843
|
+
catch {
|
|
11844
|
+
return false;
|
|
11845
|
+
}
|
|
11846
|
+
}
|
|
11847
|
+
|
|
11848
|
+
let ChefUiDataGrid = class ChefUiDataGrid extends i {
|
|
11849
|
+
constructor() {
|
|
11850
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11851
|
+
// PROPERTIES
|
|
11852
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11853
|
+
super(...arguments);
|
|
11854
|
+
this.rows = [];
|
|
11855
|
+
this.columns = [];
|
|
11856
|
+
this.pageSize = 10;
|
|
11857
|
+
this.pageSizeOptions = [10, 25, 50, 100];
|
|
11858
|
+
this.checkboxSelection = false;
|
|
11859
|
+
this.sortable = true;
|
|
11860
|
+
this.filterable = true;
|
|
11861
|
+
this.editable = false;
|
|
11862
|
+
this.resizableColumns = true;
|
|
11863
|
+
this.reorderableColumns = false;
|
|
11864
|
+
this.showToolbar = false;
|
|
11865
|
+
this.density = 'standard';
|
|
11866
|
+
this.loading = false;
|
|
11867
|
+
this.autoHeight = false;
|
|
11868
|
+
this.rowHeight = 0;
|
|
11869
|
+
this.headerHeight = 0;
|
|
11870
|
+
this.disableVirtualization = false;
|
|
11871
|
+
this.treeData = false;
|
|
11872
|
+
this.clipboardEnabled = true;
|
|
11873
|
+
this.pinnedColumns = {};
|
|
11874
|
+
this.rowGroupingModel = [];
|
|
11875
|
+
this.aggregationModel = {};
|
|
11876
|
+
this.exportOptions = {};
|
|
11877
|
+
this.noRowsMessage = 'No rows';
|
|
11878
|
+
this.loadingMessage = 'Loading...';
|
|
11879
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11880
|
+
// INTERNAL STATE
|
|
11881
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11882
|
+
this._currentPage = 0;
|
|
11883
|
+
this._sortModel = [];
|
|
11884
|
+
this._filterModel = { items: [], quickFilterValue: '' };
|
|
11885
|
+
this._selectedRows = new Set();
|
|
11886
|
+
this._editingCell = null;
|
|
11887
|
+
this._editValue = '';
|
|
11888
|
+
this._columnWidths = new Map();
|
|
11889
|
+
this._columnOrder = [];
|
|
11890
|
+
this._hiddenColumns = new Set();
|
|
11891
|
+
this._expandedRows = new Set();
|
|
11892
|
+
this._expandedGroups = new Set();
|
|
11893
|
+
this._scrollTop = 0;
|
|
11894
|
+
this._containerHeight = 400;
|
|
11895
|
+
// UI state
|
|
11896
|
+
this._activeFilterColumn = null;
|
|
11897
|
+
this._filterPopoverField = null;
|
|
11898
|
+
this._filterPopoverX = 0;
|
|
11899
|
+
this._filterPopoverY = 0;
|
|
11900
|
+
this._columnMenuField = null;
|
|
11901
|
+
this._columnMenuX = 0;
|
|
11902
|
+
this._columnMenuY = 0;
|
|
11903
|
+
this._densityMenuOpen = false;
|
|
11904
|
+
this._columnsMenuOpen = false;
|
|
11905
|
+
this._exportMenuOpen = false;
|
|
11906
|
+
this._contextMenuField = null;
|
|
11907
|
+
// Resize state
|
|
11908
|
+
this._resizing = false;
|
|
11909
|
+
this._resizeField = '';
|
|
11910
|
+
this._resizeStartX = 0;
|
|
11911
|
+
this._resizeStartWidth = 0;
|
|
11912
|
+
// Reorder state
|
|
11913
|
+
this._reorderField = '';
|
|
11914
|
+
this._reorderTargetField = '';
|
|
11915
|
+
}
|
|
11916
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11917
|
+
// COMPUTED
|
|
11918
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11919
|
+
get _effectiveRowHeight() {
|
|
11920
|
+
return this.rowHeight || DENSITY_ROW_HEIGHT[this.density];
|
|
11921
|
+
}
|
|
11922
|
+
get _effectiveHeaderHeight() {
|
|
11923
|
+
return this.headerHeight || DENSITY_HEADER_HEIGHT[this.density];
|
|
11924
|
+
}
|
|
11925
|
+
get _orderedColumns() {
|
|
11926
|
+
if (this._columnOrder.length === 0)
|
|
11927
|
+
return this.columns;
|
|
11928
|
+
const colMap = new Map(this.columns.map(c => [c.field, c]));
|
|
11929
|
+
const ordered = [];
|
|
11930
|
+
for (const field of this._columnOrder) {
|
|
11931
|
+
const col = colMap.get(field);
|
|
11932
|
+
if (col)
|
|
11933
|
+
ordered.push(col);
|
|
11934
|
+
}
|
|
11935
|
+
// Add any new columns not in order
|
|
11936
|
+
for (const col of this.columns) {
|
|
11937
|
+
if (!this._columnOrder.includes(col.field)) {
|
|
11938
|
+
ordered.push(col);
|
|
11939
|
+
}
|
|
11940
|
+
}
|
|
11941
|
+
return ordered;
|
|
11942
|
+
}
|
|
11943
|
+
get _visibleColumns() {
|
|
11944
|
+
return this._orderedColumns.filter(c => !this._hiddenColumns.has(c.field));
|
|
11945
|
+
}
|
|
11946
|
+
get _processedRows() {
|
|
11947
|
+
let result = [...this.rows];
|
|
11948
|
+
// Apply filters
|
|
11949
|
+
if (this._filterModel.items.length > 0 || this._filterModel.quickFilterValue) {
|
|
11950
|
+
result = filterRows(result, this._filterModel, this.columns);
|
|
11951
|
+
}
|
|
11952
|
+
// Apply sort
|
|
11953
|
+
if (this._sortModel.length > 0) {
|
|
11954
|
+
result = sortRows(result, this._sortModel, this.columns);
|
|
11955
|
+
}
|
|
11956
|
+
return result;
|
|
11957
|
+
}
|
|
11958
|
+
get _groupedRows() {
|
|
11959
|
+
const processed = this._processedRows;
|
|
11960
|
+
// If row grouping is active
|
|
11961
|
+
if (this.rowGroupingModel.length > 0) {
|
|
11962
|
+
return this._buildGroupedRows(processed);
|
|
11963
|
+
}
|
|
11964
|
+
// If tree data (handled by row data containing hierarchy)
|
|
11965
|
+
if (this.treeData) {
|
|
11966
|
+
return this._buildTreeRows(processed);
|
|
11967
|
+
}
|
|
11968
|
+
// Plain rows
|
|
11969
|
+
return processed.map(row => ({
|
|
11970
|
+
id: row.id ?? processed.indexOf(row),
|
|
11971
|
+
data: row,
|
|
11972
|
+
type: 'data',
|
|
11973
|
+
depth: 0,
|
|
11974
|
+
}));
|
|
11975
|
+
}
|
|
11976
|
+
get _flatRows() {
|
|
11977
|
+
const grouped = this._groupedRows;
|
|
11978
|
+
const result = [];
|
|
11979
|
+
const flatten = (rows) => {
|
|
11980
|
+
for (const row of rows) {
|
|
11981
|
+
result.push(row);
|
|
11982
|
+
}
|
|
11983
|
+
};
|
|
11984
|
+
flatten(grouped);
|
|
11985
|
+
return result;
|
|
11986
|
+
}
|
|
11987
|
+
get _paginatedRows() {
|
|
11988
|
+
const all = this._flatRows;
|
|
11989
|
+
const start = this._currentPage * this.pageSize;
|
|
11990
|
+
const end = start + this.pageSize;
|
|
11991
|
+
return all.slice(start, end);
|
|
11992
|
+
}
|
|
11993
|
+
get _totalRowCount() {
|
|
11994
|
+
return this._flatRows.length;
|
|
11995
|
+
}
|
|
11996
|
+
get _totalPages() {
|
|
11997
|
+
return Math.ceil(this._totalRowCount / this.pageSize);
|
|
11998
|
+
}
|
|
11999
|
+
get _visibleRows() {
|
|
12000
|
+
if (this.disableVirtualization || this.autoHeight) {
|
|
12001
|
+
return this._paginatedRows;
|
|
12002
|
+
}
|
|
12003
|
+
const rh = this._effectiveRowHeight;
|
|
12004
|
+
const start = Math.floor(this._scrollTop / rh);
|
|
12005
|
+
const visibleCount = Math.ceil(this._containerHeight / rh);
|
|
12006
|
+
const buffer = 5;
|
|
12007
|
+
const from = Math.max(0, start - buffer);
|
|
12008
|
+
const to = Math.min(this._paginatedRows.length, start + visibleCount + buffer);
|
|
12009
|
+
return this._paginatedRows.slice(from, to).map((row, i) => ({
|
|
12010
|
+
...row,
|
|
12011
|
+
_virtualIndex: from + i,
|
|
12012
|
+
}));
|
|
12013
|
+
}
|
|
12014
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12015
|
+
// LIFECYCLE
|
|
12016
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12017
|
+
firstUpdated(_changedProperties) {
|
|
12018
|
+
super.firstUpdated(_changedProperties);
|
|
12019
|
+
this._initColumnOrder();
|
|
12020
|
+
this._initColumnWidths();
|
|
12021
|
+
this._observeContainerResize();
|
|
12022
|
+
}
|
|
12023
|
+
updated(changedProperties) {
|
|
12024
|
+
super.updated(changedProperties);
|
|
12025
|
+
if (changedProperties.has('columns')) {
|
|
12026
|
+
this._initColumnOrder();
|
|
12027
|
+
this._initColumnWidths();
|
|
12028
|
+
}
|
|
12029
|
+
if (changedProperties.has('rows')) {
|
|
12030
|
+
// Reset page if rows change and current page is out of bounds
|
|
12031
|
+
if (this._currentPage >= this._totalPages && this._totalPages > 0) {
|
|
12032
|
+
this._currentPage = this._totalPages - 1;
|
|
12033
|
+
}
|
|
12034
|
+
}
|
|
12035
|
+
}
|
|
12036
|
+
_initColumnOrder() {
|
|
12037
|
+
if (this._columnOrder.length === 0 && this.columns.length > 0) {
|
|
12038
|
+
this._columnOrder = this.columns.map(c => c.field);
|
|
12039
|
+
}
|
|
12040
|
+
}
|
|
12041
|
+
_initColumnWidths() {
|
|
12042
|
+
for (const col of this.columns) {
|
|
12043
|
+
if (!this._columnWidths.has(col.field) && col.width) {
|
|
12044
|
+
this._columnWidths.set(col.field, col.width);
|
|
12045
|
+
}
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
_observeContainerResize() {
|
|
12049
|
+
if (typeof ResizeObserver !== 'undefined' && this._bodyEl) {
|
|
12050
|
+
const ro = new ResizeObserver(entries => {
|
|
12051
|
+
for (const entry of entries) {
|
|
12052
|
+
this._containerHeight = entry.contentRect.height;
|
|
12053
|
+
}
|
|
12054
|
+
});
|
|
12055
|
+
ro.observe(this._bodyEl);
|
|
12056
|
+
}
|
|
12057
|
+
}
|
|
12058
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12059
|
+
// GROUPING & TREE DATA
|
|
12060
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12061
|
+
_buildGroupedRows(rows) {
|
|
12062
|
+
if (this.rowGroupingModel.length === 0)
|
|
12063
|
+
return rows.map((r, i) => ({
|
|
12064
|
+
id: r.id ?? i, data: r, type: 'data', depth: 0,
|
|
12065
|
+
}));
|
|
12066
|
+
const result = [];
|
|
12067
|
+
const groupField = this.rowGroupingModel[0];
|
|
12068
|
+
// Group rows by field value
|
|
12069
|
+
const groups = new Map();
|
|
12070
|
+
for (const row of rows) {
|
|
12071
|
+
const val = row[groupField] ?? '(empty)';
|
|
12072
|
+
if (!groups.has(val))
|
|
12073
|
+
groups.set(val, []);
|
|
12074
|
+
groups.get(val).push(row);
|
|
12075
|
+
}
|
|
12076
|
+
for (const [groupValue, groupRows] of groups) {
|
|
12077
|
+
const groupKey = `${groupField}:${groupValue}`;
|
|
12078
|
+
const isExpanded = this._expandedGroups.has(groupKey);
|
|
12079
|
+
// Compute aggregations
|
|
12080
|
+
const aggregations = {};
|
|
12081
|
+
for (const [field, fn] of Object.entries(this.aggregationModel)) {
|
|
12082
|
+
aggregations[field] = this._computeAggregation(groupRows, field, fn);
|
|
12083
|
+
}
|
|
12084
|
+
result.push({
|
|
12085
|
+
id: groupKey,
|
|
12086
|
+
data: {},
|
|
12087
|
+
type: 'group',
|
|
12088
|
+
depth: 0,
|
|
12089
|
+
isExpanded,
|
|
12090
|
+
childCount: groupRows.length,
|
|
12091
|
+
groupField,
|
|
12092
|
+
groupValue,
|
|
12093
|
+
aggregations,
|
|
12094
|
+
});
|
|
12095
|
+
if (isExpanded) {
|
|
12096
|
+
for (const row of groupRows) {
|
|
12097
|
+
result.push({
|
|
12098
|
+
id: row.id ?? rows.indexOf(row),
|
|
12099
|
+
data: row,
|
|
12100
|
+
type: 'data',
|
|
12101
|
+
depth: 1,
|
|
12102
|
+
});
|
|
12103
|
+
}
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
return result;
|
|
12107
|
+
}
|
|
12108
|
+
_buildTreeRows(rows) {
|
|
12109
|
+
// Simple tree: rows with _parentId and _depth fields
|
|
12110
|
+
const result = [];
|
|
12111
|
+
const rootRows = rows.filter(r => !r._parentId);
|
|
12112
|
+
const childMap = new Map();
|
|
12113
|
+
for (const row of rows) {
|
|
12114
|
+
if (row._parentId) {
|
|
12115
|
+
if (!childMap.has(row._parentId))
|
|
12116
|
+
childMap.set(row._parentId, []);
|
|
12117
|
+
childMap.get(row._parentId).push(row);
|
|
12118
|
+
}
|
|
12119
|
+
}
|
|
12120
|
+
const addRow = (row, depth) => {
|
|
12121
|
+
const id = row.id;
|
|
12122
|
+
const children = childMap.get(id) || [];
|
|
12123
|
+
const isExpanded = this._expandedRows.has(id);
|
|
12124
|
+
result.push({
|
|
12125
|
+
id,
|
|
12126
|
+
data: row,
|
|
12127
|
+
type: 'tree',
|
|
12128
|
+
depth,
|
|
12129
|
+
isExpanded,
|
|
12130
|
+
childCount: children.length,
|
|
12131
|
+
});
|
|
12132
|
+
if (isExpanded) {
|
|
12133
|
+
for (const child of children) {
|
|
12134
|
+
addRow(child, depth + 1);
|
|
12135
|
+
}
|
|
12136
|
+
}
|
|
12137
|
+
};
|
|
12138
|
+
for (const row of rootRows) {
|
|
12139
|
+
addRow(row, 0);
|
|
12140
|
+
}
|
|
12141
|
+
return result;
|
|
12142
|
+
}
|
|
12143
|
+
_computeAggregation(rows, field, fn) {
|
|
12144
|
+
const values = rows.map(r => r[field]).filter(v => v != null && v !== '');
|
|
12145
|
+
if (values.length === 0)
|
|
12146
|
+
return null;
|
|
12147
|
+
switch (fn) {
|
|
12148
|
+
case 'sum': return values.reduce((a, b) => Number(a) + Number(b), 0);
|
|
12149
|
+
case 'avg': {
|
|
12150
|
+
const sum = values.reduce((a, b) => Number(a) + Number(b), 0);
|
|
12151
|
+
return Math.round((sum / values.length) * 100) / 100;
|
|
12152
|
+
}
|
|
12153
|
+
case 'min': return Math.min(...values.map(Number));
|
|
12154
|
+
case 'max': return Math.max(...values.map(Number));
|
|
12155
|
+
case 'count': return values.length;
|
|
12156
|
+
case 'size': return rows.length;
|
|
12157
|
+
default: return null;
|
|
12158
|
+
}
|
|
12159
|
+
}
|
|
12160
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12161
|
+
// EVENT HANDLERS
|
|
12162
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12163
|
+
// ── Sorting ──
|
|
12164
|
+
_handleSort(field, e) {
|
|
12165
|
+
const col = this.columns.find(c => c.field === field);
|
|
12166
|
+
if (!col || col.sortable === false || !this.sortable)
|
|
12167
|
+
return;
|
|
12168
|
+
this._sortModel = toggleSort(this._sortModel, field, e.shiftKey);
|
|
12169
|
+
this.dispatchEvent(new CustomEvent('sort-change', { detail: { sortModel: this._sortModel } }));
|
|
12170
|
+
}
|
|
12171
|
+
// ── Filtering ──
|
|
12172
|
+
_openFilterPopover(field, e) {
|
|
12173
|
+
// Close other menus first
|
|
12174
|
+
this._densityMenuOpen = false;
|
|
12175
|
+
this._columnsMenuOpen = false;
|
|
12176
|
+
this._exportMenuOpen = false;
|
|
12177
|
+
this._contextMenuField = null;
|
|
12178
|
+
if (this._filterPopoverField === field) {
|
|
12179
|
+
this._filterPopoverField = null;
|
|
12180
|
+
return;
|
|
12181
|
+
}
|
|
12182
|
+
// Calculate position relative to grid-container
|
|
12183
|
+
const icon = e.currentTarget;
|
|
12184
|
+
const gridContainer = this.shadowRoot.querySelector('.grid-container');
|
|
12185
|
+
if (icon && gridContainer) {
|
|
12186
|
+
const iconRect = icon.getBoundingClientRect();
|
|
12187
|
+
const gridRect = gridContainer.getBoundingClientRect();
|
|
12188
|
+
this._filterPopoverX = iconRect.left - gridRect.left;
|
|
12189
|
+
this._filterPopoverY = iconRect.bottom - gridRect.top + 4;
|
|
12190
|
+
}
|
|
12191
|
+
this._filterPopoverField = field;
|
|
12192
|
+
}
|
|
12193
|
+
// ── Column Header Menu ──
|
|
12194
|
+
_openColumnMenu(field, e) {
|
|
12195
|
+
e.stopPropagation();
|
|
12196
|
+
// Close other menus
|
|
12197
|
+
this._filterPopoverField = null;
|
|
12198
|
+
this._densityMenuOpen = false;
|
|
12199
|
+
this._columnsMenuOpen = false;
|
|
12200
|
+
this._exportMenuOpen = false;
|
|
12201
|
+
if (this._columnMenuField === field) {
|
|
12202
|
+
this._columnMenuField = null;
|
|
12203
|
+
return;
|
|
12204
|
+
}
|
|
12205
|
+
const btn = e.currentTarget;
|
|
12206
|
+
const gridContainer = this.shadowRoot.querySelector('.grid-container');
|
|
12207
|
+
if (btn && gridContainer) {
|
|
12208
|
+
const btnRect = btn.getBoundingClientRect();
|
|
12209
|
+
const gridRect = gridContainer.getBoundingClientRect();
|
|
12210
|
+
this._columnMenuX = btnRect.left - gridRect.left;
|
|
12211
|
+
this._columnMenuY = btnRect.bottom - gridRect.top + 4;
|
|
12212
|
+
}
|
|
12213
|
+
this._columnMenuField = field;
|
|
12214
|
+
}
|
|
12215
|
+
_closeColumnMenu() {
|
|
12216
|
+
this._columnMenuField = null;
|
|
12217
|
+
}
|
|
12218
|
+
_columnMenuSort(field, dir) {
|
|
12219
|
+
// Set sort to specific direction
|
|
12220
|
+
this._sortModel = [{ field, sort: dir }];
|
|
12221
|
+
this.dispatchEvent(new CustomEvent('sort-change', { detail: { sortModel: this._sortModel } }));
|
|
12222
|
+
this._closeColumnMenu();
|
|
12223
|
+
}
|
|
12224
|
+
_columnMenuUnsort(field) {
|
|
12225
|
+
this._sortModel = this._sortModel.filter(s => s.field !== field);
|
|
12226
|
+
this.dispatchEvent(new CustomEvent('sort-change', { detail: { sortModel: this._sortModel } }));
|
|
12227
|
+
this._closeColumnMenu();
|
|
12228
|
+
}
|
|
12229
|
+
_columnMenuFilter(field) {
|
|
12230
|
+
this._closeColumnMenu();
|
|
12231
|
+
// Open filter popover — we need to calculate position from the header cell
|
|
12232
|
+
const headerCells = this.shadowRoot.querySelectorAll('.header-cell');
|
|
12233
|
+
const gridContainer = this.shadowRoot.querySelector('.grid-container');
|
|
12234
|
+
const col = this._visibleColumns.find(c => c.field === field);
|
|
12235
|
+
if (!col || !gridContainer)
|
|
12236
|
+
return;
|
|
12237
|
+
const colIdx = this._visibleColumns.indexOf(col);
|
|
12238
|
+
const adjustedIdx = this.checkboxSelection ? colIdx + 1 : colIdx;
|
|
12239
|
+
const headerCell = headerCells[adjustedIdx];
|
|
12240
|
+
if (headerCell) {
|
|
12241
|
+
const cellRect = headerCell.getBoundingClientRect();
|
|
12242
|
+
const gridRect = gridContainer.getBoundingClientRect();
|
|
12243
|
+
this._filterPopoverX = cellRect.left - gridRect.left;
|
|
12244
|
+
this._filterPopoverY = cellRect.bottom - gridRect.top + 4;
|
|
12245
|
+
}
|
|
12246
|
+
this._filterPopoverField = field;
|
|
12247
|
+
}
|
|
12248
|
+
_columnMenuHide(field) {
|
|
12249
|
+
const newHidden = new Set(this._hiddenColumns);
|
|
12250
|
+
newHidden.add(field);
|
|
12251
|
+
this._hiddenColumns = newHidden;
|
|
12252
|
+
this.dispatchEvent(new CustomEvent('column-visibility-change', {
|
|
12253
|
+
detail: { hiddenColumns: Array.from(newHidden) },
|
|
12254
|
+
}));
|
|
12255
|
+
this._closeColumnMenu();
|
|
12256
|
+
}
|
|
12257
|
+
_columnMenuManageColumns() {
|
|
12258
|
+
this._closeColumnMenu();
|
|
12259
|
+
this._columnsMenuOpen = true;
|
|
12260
|
+
}
|
|
12261
|
+
_updateFilter(field, operator, value) {
|
|
12262
|
+
const items = this._filterModel.items.filter(i => i.field !== field);
|
|
12263
|
+
items.push({ field, operator, value });
|
|
12264
|
+
this._filterModel = { ...this._filterModel, items };
|
|
12265
|
+
this._currentPage = 0;
|
|
12266
|
+
this.dispatchEvent(new CustomEvent('filter-change', { detail: { filterModel: this._filterModel } }));
|
|
12267
|
+
}
|
|
12268
|
+
_removeFilter(field) {
|
|
12269
|
+
const items = this._filterModel.items.filter(i => i.field !== field);
|
|
12270
|
+
this._filterModel = { ...this._filterModel, items };
|
|
12271
|
+
this._filterPopoverField = null;
|
|
12272
|
+
this.dispatchEvent(new CustomEvent('filter-change', { detail: { filterModel: this._filterModel } }));
|
|
12273
|
+
}
|
|
12274
|
+
_handleQuickFilter(e) {
|
|
12275
|
+
const value = e.target.value;
|
|
12276
|
+
this._filterModel = { ...this._filterModel, quickFilterValue: value };
|
|
12277
|
+
this._currentPage = 0;
|
|
12278
|
+
}
|
|
12279
|
+
// ── Pagination ──
|
|
12280
|
+
_goToPage(page) {
|
|
12281
|
+
if (page < 0 || page >= this._totalPages)
|
|
12282
|
+
return;
|
|
12283
|
+
this._currentPage = page;
|
|
12284
|
+
this.dispatchEvent(new CustomEvent('page-change', { detail: { page } }));
|
|
12285
|
+
}
|
|
12286
|
+
_changePageSize(e) {
|
|
12287
|
+
this.pageSize = Number(e.target.value);
|
|
12288
|
+
this._currentPage = 0;
|
|
12289
|
+
}
|
|
12290
|
+
// ── Selection ──
|
|
12291
|
+
_toggleRowSelection(rowId, e) {
|
|
12292
|
+
const newSelection = new Set(this._selectedRows);
|
|
12293
|
+
if (newSelection.has(rowId)) {
|
|
12294
|
+
newSelection.delete(rowId);
|
|
12295
|
+
}
|
|
12296
|
+
else {
|
|
12297
|
+
newSelection.add(rowId);
|
|
12298
|
+
}
|
|
12299
|
+
this._selectedRows = newSelection;
|
|
12300
|
+
this.dispatchEvent(new CustomEvent('selection-change', {
|
|
12301
|
+
detail: { selectedRows: Array.from(newSelection) },
|
|
12302
|
+
}));
|
|
12303
|
+
}
|
|
12304
|
+
_toggleSelectAll() {
|
|
12305
|
+
const pageRowIds = this._paginatedRows
|
|
12306
|
+
.filter(r => r.type === 'data' || r.type === 'tree')
|
|
12307
|
+
.map(r => r.id);
|
|
12308
|
+
const allSelected = pageRowIds.every(id => this._selectedRows.has(id));
|
|
12309
|
+
if (allSelected) {
|
|
12310
|
+
this._selectedRows = new Set();
|
|
12311
|
+
}
|
|
12312
|
+
else {
|
|
12313
|
+
this._selectedRows = new Set(pageRowIds);
|
|
12314
|
+
}
|
|
12315
|
+
this.dispatchEvent(new CustomEvent('selection-change', {
|
|
12316
|
+
detail: { selectedRows: Array.from(this._selectedRows) },
|
|
12317
|
+
}));
|
|
12318
|
+
}
|
|
12319
|
+
// ── Cell Editing ──
|
|
12320
|
+
_startEditing(rowId, field) {
|
|
12321
|
+
const col = this.columns.find(c => c.field === field);
|
|
12322
|
+
if (!col || !col.editable || !this.editable)
|
|
12323
|
+
return;
|
|
12324
|
+
const row = this.rows.find(r => (r.id ?? this.rows.indexOf(r)) === rowId);
|
|
12325
|
+
if (!row)
|
|
12326
|
+
return;
|
|
12327
|
+
this._editingCell = { rowId, field };
|
|
12328
|
+
this._editValue = col.valueGetter ? col.valueGetter(row) : row[field];
|
|
12329
|
+
this.dispatchEvent(new CustomEvent('cell-edit-start', { detail: { rowId, field } }));
|
|
12330
|
+
}
|
|
12331
|
+
_commitEdit() {
|
|
12332
|
+
if (!this._editingCell)
|
|
12333
|
+
return;
|
|
12334
|
+
const { rowId, field } = this._editingCell;
|
|
12335
|
+
this.dispatchEvent(new CustomEvent('cell-edit-commit', {
|
|
12336
|
+
detail: { rowId, field, value: this._editValue },
|
|
12337
|
+
}));
|
|
12338
|
+
// Update the row in place
|
|
12339
|
+
const rowIndex = this.rows.findIndex(r => (r.id ?? this.rows.indexOf(r)) === rowId);
|
|
12340
|
+
if (rowIndex >= 0) {
|
|
12341
|
+
const col = this.columns.find(c => c.field === field);
|
|
12342
|
+
if (col?.valueSetter) {
|
|
12343
|
+
col.valueSetter(this.rows[rowIndex], this._editValue);
|
|
12344
|
+
}
|
|
12345
|
+
else {
|
|
12346
|
+
this.rows[rowIndex][field] = this._editValue;
|
|
12347
|
+
}
|
|
12348
|
+
this.requestUpdate();
|
|
12349
|
+
}
|
|
12350
|
+
this._editingCell = null;
|
|
12351
|
+
this._editValue = '';
|
|
12352
|
+
this.dispatchEvent(new CustomEvent('cell-edit-stop', { detail: { rowId, field } }));
|
|
12353
|
+
}
|
|
12354
|
+
_cancelEdit() {
|
|
12355
|
+
if (!this._editingCell)
|
|
12356
|
+
return;
|
|
12357
|
+
const { rowId, field } = this._editingCell;
|
|
12358
|
+
this._editingCell = null;
|
|
12359
|
+
this._editValue = '';
|
|
12360
|
+
this.dispatchEvent(new CustomEvent('cell-edit-stop', { detail: { rowId, field } }));
|
|
12361
|
+
}
|
|
12362
|
+
_handleEditKeydown(e) {
|
|
12363
|
+
if (e.key === 'Enter') {
|
|
12364
|
+
e.preventDefault();
|
|
12365
|
+
this._commitEdit();
|
|
12366
|
+
}
|
|
12367
|
+
else if (e.key === 'Escape') {
|
|
12368
|
+
e.preventDefault();
|
|
12369
|
+
this._cancelEdit();
|
|
12370
|
+
}
|
|
12371
|
+
else if (e.key === 'Tab') {
|
|
12372
|
+
e.preventDefault();
|
|
12373
|
+
this._commitEdit();
|
|
12374
|
+
// Move to next editable cell
|
|
12375
|
+
if (this._editingCell) {
|
|
12376
|
+
const cols = this._visibleColumns.filter(c => c.editable);
|
|
12377
|
+
const idx = cols.findIndex(c => c.field === this._editingCell.field);
|
|
12378
|
+
if (idx >= 0 && idx < cols.length - 1) {
|
|
12379
|
+
this._startEditing(this._editingCell.rowId, cols[idx + 1].field);
|
|
12380
|
+
}
|
|
12381
|
+
}
|
|
12382
|
+
}
|
|
12383
|
+
}
|
|
12384
|
+
// ── Column Resize ──
|
|
12385
|
+
_startResize(field, e) {
|
|
12386
|
+
e.preventDefault();
|
|
12387
|
+
e.stopPropagation();
|
|
12388
|
+
this._resizing = true;
|
|
12389
|
+
this._resizeField = field;
|
|
12390
|
+
this._resizeStartX = e.clientX;
|
|
12391
|
+
this._resizeStartWidth = this._getColumnWidth(field);
|
|
12392
|
+
const onMove = (ev) => {
|
|
12393
|
+
const diff = ev.clientX - this._resizeStartX;
|
|
12394
|
+
const col = this.columns.find(c => c.field === this._resizeField);
|
|
12395
|
+
const minW = col?.minWidth ?? 50;
|
|
12396
|
+
const maxW = col?.maxWidth ?? 800;
|
|
12397
|
+
const newWidth = Math.max(minW, Math.min(maxW, this._resizeStartWidth + diff));
|
|
12398
|
+
const newMap = new Map(this._columnWidths);
|
|
12399
|
+
newMap.set(this._resizeField, newWidth);
|
|
12400
|
+
this._columnWidths = newMap;
|
|
12401
|
+
};
|
|
12402
|
+
const onUp = () => {
|
|
12403
|
+
this._resizing = false;
|
|
12404
|
+
document.removeEventListener('mousemove', onMove);
|
|
12405
|
+
document.removeEventListener('mouseup', onUp);
|
|
12406
|
+
this.dispatchEvent(new CustomEvent('column-resize', {
|
|
12407
|
+
detail: { field: this._resizeField, width: this._columnWidths.get(this._resizeField) },
|
|
12408
|
+
}));
|
|
12409
|
+
};
|
|
12410
|
+
document.addEventListener('mousemove', onMove);
|
|
12411
|
+
document.addEventListener('mouseup', onUp);
|
|
12412
|
+
}
|
|
12413
|
+
// ── Column Reorder ──
|
|
12414
|
+
_handleDragStart(field, e) {
|
|
12415
|
+
if (!this.reorderableColumns)
|
|
12416
|
+
return;
|
|
12417
|
+
this._reorderField = field;
|
|
12418
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
12419
|
+
e.dataTransfer.setData('text/plain', field);
|
|
12420
|
+
}
|
|
12421
|
+
_handleDragOver(field, e) {
|
|
12422
|
+
if (!this.reorderableColumns || !this._reorderField)
|
|
12423
|
+
return;
|
|
12424
|
+
e.preventDefault();
|
|
12425
|
+
e.dataTransfer.dropEffect = 'move';
|
|
12426
|
+
this._reorderTargetField = field;
|
|
12427
|
+
}
|
|
12428
|
+
_handleDrop(field, e) {
|
|
12429
|
+
e.preventDefault();
|
|
12430
|
+
if (!this._reorderField || this._reorderField === field)
|
|
12431
|
+
return;
|
|
12432
|
+
const newOrder = [...this._columnOrder];
|
|
12433
|
+
const fromIdx = newOrder.indexOf(this._reorderField);
|
|
12434
|
+
const toIdx = newOrder.indexOf(field);
|
|
12435
|
+
if (fromIdx < 0 || toIdx < 0)
|
|
12436
|
+
return;
|
|
12437
|
+
newOrder.splice(fromIdx, 1);
|
|
12438
|
+
newOrder.splice(toIdx, 0, this._reorderField);
|
|
12439
|
+
this._columnOrder = newOrder;
|
|
12440
|
+
this._reorderField = '';
|
|
12441
|
+
this._reorderTargetField = '';
|
|
12442
|
+
this.dispatchEvent(new CustomEvent('column-reorder', { detail: { columnOrder: newOrder } }));
|
|
12443
|
+
}
|
|
12444
|
+
_handleDragEnd() {
|
|
12445
|
+
this._reorderField = '';
|
|
12446
|
+
this._reorderTargetField = '';
|
|
12447
|
+
}
|
|
12448
|
+
// ── Column Pinning ──
|
|
12449
|
+
_pinColumn(field, side) {
|
|
12450
|
+
const left = (this.pinnedColumns.left || []).filter(f => f !== field);
|
|
12451
|
+
const right = (this.pinnedColumns.right || []).filter(f => f !== field);
|
|
12452
|
+
if (side === 'left')
|
|
12453
|
+
left.push(field);
|
|
12454
|
+
if (side === 'right')
|
|
12455
|
+
right.push(field);
|
|
12456
|
+
this.pinnedColumns = { left, right };
|
|
12457
|
+
this._contextMenuField = null;
|
|
12458
|
+
}
|
|
12459
|
+
// ── Column Visibility ──
|
|
12460
|
+
_toggleColumnVisibility(field) {
|
|
12461
|
+
const newHidden = new Set(this._hiddenColumns);
|
|
12462
|
+
if (newHidden.has(field)) {
|
|
12463
|
+
newHidden.delete(field);
|
|
12464
|
+
}
|
|
12465
|
+
else {
|
|
12466
|
+
newHidden.add(field);
|
|
12467
|
+
}
|
|
12468
|
+
this._hiddenColumns = newHidden;
|
|
12469
|
+
this.dispatchEvent(new CustomEvent('column-visibility-change', {
|
|
12470
|
+
detail: { hiddenColumns: Array.from(newHidden) },
|
|
12471
|
+
}));
|
|
12472
|
+
}
|
|
12473
|
+
// ── Expand/Collapse ──
|
|
12474
|
+
_toggleRowExpand(rowId) {
|
|
12475
|
+
const newExpanded = new Set(this._expandedRows);
|
|
12476
|
+
if (newExpanded.has(rowId)) {
|
|
12477
|
+
newExpanded.delete(rowId);
|
|
12478
|
+
this.dispatchEvent(new CustomEvent('row-collapse', { detail: { rowId } }));
|
|
12479
|
+
}
|
|
12480
|
+
else {
|
|
12481
|
+
newExpanded.add(rowId);
|
|
12482
|
+
this.dispatchEvent(new CustomEvent('row-expand', { detail: { rowId } }));
|
|
12483
|
+
}
|
|
12484
|
+
this._expandedRows = newExpanded;
|
|
12485
|
+
}
|
|
12486
|
+
_toggleGroupExpand(groupKey) {
|
|
12487
|
+
const newExpanded = new Set(this._expandedGroups);
|
|
12488
|
+
if (newExpanded.has(groupKey)) {
|
|
12489
|
+
newExpanded.delete(groupKey);
|
|
12490
|
+
}
|
|
12491
|
+
else {
|
|
12492
|
+
newExpanded.add(groupKey);
|
|
12493
|
+
}
|
|
12494
|
+
this._expandedGroups = newExpanded;
|
|
12495
|
+
}
|
|
12496
|
+
// ── Scroll ──
|
|
12497
|
+
_handleScroll(e) {
|
|
12498
|
+
this._scrollTop = e.target.scrollTop;
|
|
12499
|
+
}
|
|
12500
|
+
// ── Keyboard ──
|
|
12501
|
+
_handleGridKeydown(e) {
|
|
12502
|
+
if (this._editingCell)
|
|
12503
|
+
return; // Let edit handlers take over
|
|
12504
|
+
// Copy
|
|
12505
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'c' && this.clipboardEnabled) {
|
|
12506
|
+
e.preventDefault();
|
|
12507
|
+
this._copySelection();
|
|
12508
|
+
}
|
|
12509
|
+
}
|
|
12510
|
+
async _copySelection() {
|
|
12511
|
+
const selectedData = this.rows.filter(r => {
|
|
12512
|
+
const id = r.id ?? this.rows.indexOf(r);
|
|
12513
|
+
return this._selectedRows.has(id);
|
|
12514
|
+
});
|
|
12515
|
+
if (selectedData.length === 0)
|
|
12516
|
+
return;
|
|
12517
|
+
const text = rowsToClipboardText(selectedData, this._visibleColumns);
|
|
12518
|
+
await copyToClipboard(text);
|
|
12519
|
+
}
|
|
12520
|
+
// ── Export ──
|
|
12521
|
+
_exportCsv() {
|
|
12522
|
+
const data = this._processedRows;
|
|
12523
|
+
downloadCsv(data, this._visibleColumns, this.exportOptions);
|
|
12524
|
+
this._exportMenuOpen = false;
|
|
12525
|
+
this.dispatchEvent(new CustomEvent('export', { detail: { format: 'csv' } }));
|
|
12526
|
+
}
|
|
12527
|
+
_exportExcel() {
|
|
12528
|
+
const data = this._processedRows;
|
|
12529
|
+
downloadExcel(data, this._visibleColumns, this.exportOptions);
|
|
12530
|
+
this._exportMenuOpen = false;
|
|
12531
|
+
this.dispatchEvent(new CustomEvent('export', { detail: { format: 'excel' } }));
|
|
12532
|
+
}
|
|
12533
|
+
// ── Density ──
|
|
12534
|
+
_changeDensity(d) {
|
|
12535
|
+
this.density = d;
|
|
12536
|
+
this._densityMenuOpen = false;
|
|
12537
|
+
}
|
|
12538
|
+
// ── Grid Click ──
|
|
12539
|
+
_handleGridClick(e) {
|
|
12540
|
+
const path = e.composedPath();
|
|
12541
|
+
// Don't close menus if user clicked inside an overlay/popover/dropdown
|
|
12542
|
+
const insideOverlay = path.some(el => el instanceof HTMLElement && (el.classList?.contains('filter-popover') ||
|
|
12543
|
+
el.classList?.contains('column-menu') ||
|
|
12544
|
+
el.classList?.contains('dropdown') ||
|
|
12545
|
+
el.classList?.contains('filter-icon') ||
|
|
12546
|
+
el.classList?.contains('toolbar-btn') ||
|
|
12547
|
+
el.classList?.contains('column-menu-btn')));
|
|
12548
|
+
if (!insideOverlay) {
|
|
12549
|
+
this._closeAllMenus();
|
|
12550
|
+
}
|
|
12551
|
+
}
|
|
12552
|
+
// ── Helpers ──
|
|
12553
|
+
_getColumnWidth(field) {
|
|
12554
|
+
if (this._columnWidths.has(field))
|
|
12555
|
+
return this._columnWidths.get(field);
|
|
12556
|
+
const col = this.columns.find(c => c.field === field);
|
|
12557
|
+
return col?.width ?? 150;
|
|
12558
|
+
}
|
|
12559
|
+
_getCellValue(row, col) {
|
|
12560
|
+
return col.valueGetter ? col.valueGetter(row) : row[col.field];
|
|
12561
|
+
}
|
|
12562
|
+
_getDisplayValue(row, col) {
|
|
12563
|
+
let val = this._getCellValue(row, col);
|
|
12564
|
+
if (col.valueFormatter)
|
|
12565
|
+
val = col.valueFormatter(val, row);
|
|
12566
|
+
if (val == null)
|
|
12567
|
+
return '';
|
|
12568
|
+
if (col.type === 'boolean')
|
|
12569
|
+
return val ? '✓' : '✗';
|
|
12570
|
+
if (col.type === 'date' && val instanceof Date)
|
|
12571
|
+
return val.toLocaleDateString();
|
|
12572
|
+
return String(val);
|
|
12573
|
+
}
|
|
12574
|
+
_closeAllMenus(except) {
|
|
12575
|
+
if (except !== 'filter')
|
|
12576
|
+
this._filterPopoverField = null;
|
|
12577
|
+
if (except !== 'density')
|
|
12578
|
+
this._densityMenuOpen = false;
|
|
12579
|
+
if (except !== 'columns')
|
|
12580
|
+
this._columnsMenuOpen = false;
|
|
12581
|
+
if (except !== 'export')
|
|
12582
|
+
this._exportMenuOpen = false;
|
|
12583
|
+
if (except !== 'context')
|
|
12584
|
+
this._contextMenuField = null;
|
|
12585
|
+
if (except !== 'columnMenu')
|
|
12586
|
+
this._columnMenuField = null;
|
|
12587
|
+
}
|
|
12588
|
+
_getSortDirection(field) {
|
|
12589
|
+
const s = this._sortModel.find(s => s.field === field);
|
|
12590
|
+
return s ? s.sort : null;
|
|
12591
|
+
}
|
|
12592
|
+
_getSortIndex(field) {
|
|
12593
|
+
return this._sortModel.findIndex(s => s.field === field);
|
|
12594
|
+
}
|
|
12595
|
+
_getFilterForField(field) {
|
|
12596
|
+
return this._filterModel.items.find(i => i.field === field);
|
|
12597
|
+
}
|
|
12598
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12599
|
+
// RENDER
|
|
12600
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12601
|
+
render() {
|
|
12602
|
+
this.autoHeight
|
|
12603
|
+
? 'auto'
|
|
12604
|
+
: `${Math.min(this._paginatedRows.length * this._effectiveRowHeight, this._containerHeight || 400)}px`;
|
|
12605
|
+
return b `
|
|
12606
|
+
<div class="grid-container"
|
|
12607
|
+
tabindex="0"
|
|
12608
|
+
role="grid"
|
|
12609
|
+
aria-rowcount="${this._totalRowCount}"
|
|
12610
|
+
aria-colcount="${this._visibleColumns.length}"
|
|
12611
|
+
@keydown="${this._handleGridKeydown}"
|
|
12612
|
+
@click="${(e) => this._handleGridClick(e)}"
|
|
12613
|
+
>
|
|
12614
|
+
${this.showToolbar ? this._renderToolbar() : A}
|
|
12615
|
+
${this._renderHeader()}
|
|
12616
|
+
<div class="grid-body"
|
|
12617
|
+
style="height: ${this.autoHeight ? 'auto' : (this._containerHeight || 400) + 'px'}; max-height: ${this.autoHeight ? 'none' : '600px'}"
|
|
12618
|
+
@scroll="${this._handleScroll}"
|
|
12619
|
+
>
|
|
12620
|
+
${this.loading ? b `
|
|
12621
|
+
<div class="loading-overlay">
|
|
12622
|
+
<div class="loading-spinner"></div>
|
|
12623
|
+
${this.loadingMessage}
|
|
12624
|
+
</div>
|
|
12625
|
+
` : A}
|
|
12626
|
+
${this._paginatedRows.length === 0 && !this.loading ? b `
|
|
12627
|
+
<div class="no-rows">${this.noRowsMessage}</div>
|
|
12628
|
+
` : this._renderBody()}
|
|
12629
|
+
</div>
|
|
12630
|
+
${this._renderFooter()}
|
|
12631
|
+
${this._filterPopoverField ? this._renderFilterPopoverOverlay() : A}
|
|
12632
|
+
${this._columnMenuField ? this._renderColumnMenu() : A}
|
|
12633
|
+
${this._columnsMenuOpen && !this.showToolbar ? this._renderColumnsPanel() : A}
|
|
12634
|
+
</div>
|
|
12635
|
+
`;
|
|
12636
|
+
}
|
|
12637
|
+
// ── Filter Popover Overlay ──
|
|
12638
|
+
_renderFilterPopoverOverlay() {
|
|
12639
|
+
const col = this.columns.find(c => c.field === this._filterPopoverField);
|
|
12640
|
+
if (!col)
|
|
12641
|
+
return A;
|
|
12642
|
+
return this._renderFilterPopover(col);
|
|
12643
|
+
}
|
|
12644
|
+
// ── Column Header Menu ──
|
|
12645
|
+
_renderColumnMenu() {
|
|
12646
|
+
const field = this._columnMenuField;
|
|
12647
|
+
const col = this.columns.find(c => c.field === field);
|
|
12648
|
+
if (!col)
|
|
12649
|
+
return A;
|
|
12650
|
+
const sortDir = this._getSortDirection(field);
|
|
12651
|
+
const isSortable = this.sortable && col.sortable !== false;
|
|
12652
|
+
const isFilterable = this.filterable && col.filterable !== false;
|
|
12653
|
+
const isPinnable = col.pinnable !== false;
|
|
12654
|
+
const isHideable = col.hideable !== false;
|
|
12655
|
+
const isPinnedLeft = (this.pinnedColumns.left || []).includes(field);
|
|
12656
|
+
const isPinnedRight = (this.pinnedColumns.right || []).includes(field);
|
|
12657
|
+
return b `
|
|
12658
|
+
<div class="column-menu"
|
|
12659
|
+
style="left:${this._columnMenuX}px; top:${this._columnMenuY}px"
|
|
12660
|
+
@click="${(e) => e.stopPropagation()}">
|
|
12661
|
+
|
|
12662
|
+
${isSortable ? b `
|
|
12663
|
+
<button class="column-menu-item" @click="${() => this._columnMenuSort(field, 'asc')}">
|
|
12664
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
|
|
12665
|
+
Sort by ASC
|
|
12666
|
+
</button>
|
|
12667
|
+
<button class="column-menu-item" @click="${() => this._columnMenuSort(field, 'desc')}">
|
|
12668
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M19 12l-7 7-7-7"/></svg>
|
|
12669
|
+
Sort by DESC
|
|
12670
|
+
</button>
|
|
12671
|
+
${sortDir ? b `
|
|
12672
|
+
<button class="column-menu-item" @click="${() => this._columnMenuUnsort(field)}">
|
|
12673
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
12674
|
+
Unsort
|
|
12675
|
+
</button>
|
|
12676
|
+
` : A}
|
|
12677
|
+
<div class="column-menu-divider"></div>
|
|
12678
|
+
` : A}
|
|
12679
|
+
|
|
12680
|
+
${isPinnable ? b `
|
|
12681
|
+
<button class="column-menu-item" @click="${() => { this._pinColumn(field, isPinnedLeft ? 'none' : 'left'); this._closeColumnMenu(); }}">
|
|
12682
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 4.5L7.5 12l3 3L18 7.5"/><path d="M2 22l5.5-5.5"/></svg>
|
|
12683
|
+
${isPinnedLeft ? 'Unpin from left' : 'Pin to left'}
|
|
12684
|
+
</button>
|
|
12685
|
+
<button class="column-menu-item" @click="${() => { this._pinColumn(field, isPinnedRight ? 'none' : 'right'); this._closeColumnMenu(); }}">
|
|
12686
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 4.5L16.5 12l-3 3L6 7.5"/><path d="M22 22l-5.5-5.5"/></svg>
|
|
12687
|
+
${isPinnedRight ? 'Unpin from right' : 'Pin to right'}
|
|
12688
|
+
</button>
|
|
12689
|
+
<div class="column-menu-divider"></div>
|
|
12690
|
+
` : A}
|
|
12691
|
+
|
|
12692
|
+
${isFilterable ? b `
|
|
12693
|
+
<button class="column-menu-item" @click="${() => this._columnMenuFilter(field)}">
|
|
12694
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
|
|
12695
|
+
Filter
|
|
12696
|
+
</button>
|
|
12697
|
+
<div class="column-menu-divider"></div>
|
|
12698
|
+
` : A}
|
|
12699
|
+
|
|
12700
|
+
${isHideable ? b `
|
|
12701
|
+
<button class="column-menu-item" @click="${() => this._columnMenuHide(field)}">
|
|
12702
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
|
|
12703
|
+
Hide column
|
|
12704
|
+
</button>
|
|
12705
|
+
` : A}
|
|
12706
|
+
|
|
12707
|
+
<button class="column-menu-item" @click="${() => this._columnMenuManageColumns()}">
|
|
12708
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
|
12709
|
+
Manage columns
|
|
12710
|
+
</button>
|
|
12711
|
+
</div>
|
|
12712
|
+
`;
|
|
12713
|
+
}
|
|
12714
|
+
// ── Toolbar ──
|
|
12715
|
+
_renderToolbar() {
|
|
12716
|
+
return b `
|
|
12717
|
+
<div class="toolbar" @click="${(e) => e.stopPropagation()}">
|
|
12718
|
+
<div class="toolbar-search">
|
|
12719
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
12720
|
+
<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
12721
|
+
</svg>
|
|
12722
|
+
<input type="text" placeholder="Search..."
|
|
12723
|
+
.value="${this._filterModel.quickFilterValue || ''}"
|
|
12724
|
+
@input="${this._handleQuickFilter}" />
|
|
12725
|
+
</div>
|
|
12726
|
+
<div class="toolbar-spacer"></div>
|
|
12727
|
+
|
|
12728
|
+
<!-- Density -->
|
|
12729
|
+
<div style="position:relative">
|
|
12730
|
+
<button class="toolbar-btn" @click="${(e) => { e.stopPropagation(); this._closeAllMenus('density'); this._densityMenuOpen = !this._densityMenuOpen; }}">
|
|
12731
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
12732
|
+
<line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="18" x2="21" y2="18"></line>
|
|
12733
|
+
</svg>
|
|
12734
|
+
Density
|
|
12735
|
+
</button>
|
|
12736
|
+
${this._densityMenuOpen ? b `
|
|
12737
|
+
<div class="dropdown" @click="${(e) => e.stopPropagation()}">
|
|
12738
|
+
<div class="dropdown-item ${this.density === 'compact' ? 'active' : ''}" @click="${() => this._changeDensity('compact')}">Compact</div>
|
|
12739
|
+
<div class="dropdown-item ${this.density === 'standard' ? 'active' : ''}" @click="${() => this._changeDensity('standard')}">Standard</div>
|
|
12740
|
+
<div class="dropdown-item ${this.density === 'comfortable' ? 'active' : ''}" @click="${() => this._changeDensity('comfortable')}">Comfortable</div>
|
|
12741
|
+
</div>
|
|
12742
|
+
` : A}
|
|
12743
|
+
</div>
|
|
12744
|
+
|
|
12745
|
+
<!-- Columns -->
|
|
12746
|
+
<div style="position:relative">
|
|
12747
|
+
<button class="toolbar-btn" @click="${(e) => { e.stopPropagation(); this._closeAllMenus('columns'); this._columnsMenuOpen = !this._columnsMenuOpen; }}">
|
|
12748
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
12749
|
+
<rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect>
|
|
12750
|
+
<rect x="3" y="14" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect>
|
|
12751
|
+
</svg>
|
|
12752
|
+
Columns
|
|
12753
|
+
</button>
|
|
12754
|
+
${this._columnsMenuOpen ? b `
|
|
12755
|
+
<div class="dropdown" style="max-height:300px;overflow-y:auto" @click="${(e) => e.stopPropagation()}">
|
|
12756
|
+
${this.columns.filter(c => c.hideable !== false).map(col => b `
|
|
12757
|
+
<label class="dropdown-item">
|
|
12758
|
+
<input type="checkbox"
|
|
12759
|
+
?checked="${!this._hiddenColumns.has(col.field)}"
|
|
12760
|
+
@change="${() => this._toggleColumnVisibility(col.field)}" />
|
|
12761
|
+
${col.headerName || col.field}
|
|
12762
|
+
</label>
|
|
12763
|
+
`)}
|
|
12764
|
+
</div>
|
|
12765
|
+
` : A}
|
|
12766
|
+
</div>
|
|
12767
|
+
|
|
12768
|
+
<!-- Export -->
|
|
12769
|
+
<div style="position:relative">
|
|
12770
|
+
<button class="toolbar-btn" @click="${(e) => { e.stopPropagation(); this._closeAllMenus('export'); this._exportMenuOpen = !this._exportMenuOpen; }}">
|
|
12771
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
12772
|
+
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"></path>
|
|
12773
|
+
<polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line>
|
|
12774
|
+
</svg>
|
|
12775
|
+
Export
|
|
12776
|
+
</button>
|
|
12777
|
+
${this._exportMenuOpen ? b `
|
|
12778
|
+
<div class="dropdown" @click="${(e) => e.stopPropagation()}">
|
|
12779
|
+
<div class="dropdown-item" @click="${this._exportCsv}">Download as CSV</div>
|
|
12780
|
+
<div class="dropdown-item" @click="${this._exportExcel}">Download as Excel</div>
|
|
12781
|
+
</div>
|
|
12782
|
+
` : A}
|
|
12783
|
+
</div>
|
|
12784
|
+
</div>
|
|
12785
|
+
`;
|
|
12786
|
+
}
|
|
12787
|
+
// ── Header ──
|
|
12788
|
+
_renderHeader() {
|
|
12789
|
+
return b `
|
|
12790
|
+
<div class="grid-header" style="height:${this._effectiveHeaderHeight}px" role="row">
|
|
12791
|
+
${this.checkboxSelection ? b `
|
|
12792
|
+
<div class="checkbox-cell" style="height:${this._effectiveHeaderHeight}px">
|
|
12793
|
+
<input type="checkbox"
|
|
12794
|
+
@change="${this._toggleSelectAll}"
|
|
12795
|
+
.checked="${this._paginatedRows.filter(r => r.type !== 'group').every(r => this._selectedRows.has(r.id))
|
|
12796
|
+
&& this._paginatedRows.filter(r => r.type !== 'group').length > 0}"
|
|
12797
|
+
.indeterminate="${this._selectedRows.size > 0
|
|
12798
|
+
&& !this._paginatedRows.filter(r => r.type !== 'group').every(r => this._selectedRows.has(r.id))}"
|
|
12799
|
+
/>
|
|
12800
|
+
</div>
|
|
12801
|
+
` : A}
|
|
12802
|
+
${this._visibleColumns.map(col => this._renderHeaderCell(col))}
|
|
12803
|
+
</div>
|
|
12804
|
+
`;
|
|
12805
|
+
}
|
|
12806
|
+
_renderHeaderCell(col) {
|
|
12807
|
+
const width = this._getColumnWidth(col.field);
|
|
12808
|
+
const sortDir = this._getSortDirection(col.field);
|
|
12809
|
+
const sortIdx = this._getSortIndex(col.field);
|
|
12810
|
+
const isSortable = this.sortable && col.sortable !== false;
|
|
12811
|
+
const hasFilter = this._getFilterForField(col.field) !== undefined;
|
|
12812
|
+
const isReorderable = this.reorderableColumns;
|
|
12813
|
+
const isResizable = this.resizableColumns && col.resizable !== false;
|
|
12814
|
+
const isMenuOpen = this._columnMenuField === col.field;
|
|
12815
|
+
return b `
|
|
12816
|
+
<div class="header-cell ${isSortable ? 'sortable' : ''}"
|
|
12817
|
+
style="width:${width}px; height:${this._effectiveHeaderHeight}px; text-align:${col.headerAlign || col.align || 'left'}"
|
|
12818
|
+
role="columnheader"
|
|
12819
|
+
aria-sort="${sortDir === 'asc' ? 'ascending' : sortDir === 'desc' ? 'descending' : 'none'}"
|
|
12820
|
+
@click="${(e) => isSortable && this._handleSort(col.field, e)}"
|
|
12821
|
+
draggable="${isReorderable ? 'true' : 'false'}"
|
|
12822
|
+
@dragstart="${(e) => this._handleDragStart(col.field, e)}"
|
|
12823
|
+
@dragover="${(e) => this._handleDragOver(col.field, e)}"
|
|
12824
|
+
@drop="${(e) => this._handleDrop(col.field, e)}"
|
|
12825
|
+
@dragend="${this._handleDragEnd}"
|
|
12826
|
+
>
|
|
12827
|
+
<span class="header-text">${col.headerName || col.field}</span>
|
|
12828
|
+
${sortDir ? b `
|
|
12829
|
+
<span class="sort-icon">${sortDir === 'asc' ? '↑' : '↓'}</span>
|
|
12830
|
+
${this._sortModel.length > 1 ? b `<span class="sort-index">${sortIdx + 1}</span>` : A}
|
|
12831
|
+
` : A}
|
|
12832
|
+
${hasFilter ? b `<span class="sort-icon" style="color:var(--dg-primary);font-size:10px">⧫</span>` : A}
|
|
12833
|
+
|
|
12834
|
+
<button class="column-menu-btn ${isMenuOpen ? 'active' : ''}"
|
|
12835
|
+
@click="${(e) => this._openColumnMenu(col.field, e)}">⋮</button>
|
|
12836
|
+
|
|
12837
|
+
${isResizable ? b `
|
|
12838
|
+
<div class="resize-handle" @mousedown="${(e) => this._startResize(col.field, e)}"></div>
|
|
12839
|
+
` : A}
|
|
12840
|
+
</div>
|
|
12841
|
+
`;
|
|
12842
|
+
}
|
|
12843
|
+
// ── Standalone Columns Panel ──
|
|
12844
|
+
_renderColumnsPanel() {
|
|
12845
|
+
return b `
|
|
12846
|
+
<div class="column-menu"
|
|
12847
|
+
style="right:8px; top:${this._effectiveHeaderHeight + 8}px; left:auto; max-height:300px; overflow-y:auto"
|
|
12848
|
+
@click="${(e) => e.stopPropagation()}">
|
|
12849
|
+
${this.columns.filter(c => c.hideable !== false).map(col => b `
|
|
12850
|
+
<label class="column-menu-item" style="cursor:pointer">
|
|
12851
|
+
<input type="checkbox" style="cursor:pointer"
|
|
12852
|
+
?checked="${!this._hiddenColumns.has(col.field)}"
|
|
12853
|
+
@change="${() => this._toggleColumnVisibility(col.field)}" />
|
|
12854
|
+
${col.headerName || col.field}
|
|
12855
|
+
</label>
|
|
12856
|
+
`)}
|
|
12857
|
+
</div>
|
|
12858
|
+
`;
|
|
12859
|
+
}
|
|
12860
|
+
// ── Filter Popover ──
|
|
12861
|
+
_renderFilterPopover(col) {
|
|
12862
|
+
const type = col.type || 'string';
|
|
12863
|
+
const operators = FILTER_OPERATORS[type] || FILTER_OPERATORS.string;
|
|
12864
|
+
const existing = this._getFilterForField(col.field);
|
|
12865
|
+
const currentOp = existing?.operator || getDefaultOperator(type);
|
|
12866
|
+
const currentVal = existing?.value ?? '';
|
|
12867
|
+
return b `
|
|
12868
|
+
<div class="filter-popover"
|
|
12869
|
+
style="left:${this._filterPopoverX}px; top:${this._filterPopoverY}px"
|
|
12870
|
+
@click="${(e) => e.stopPropagation()}">
|
|
12871
|
+
<div class="filter-popover-title">Filter: ${col.headerName || col.field}</div>
|
|
12872
|
+
<select
|
|
12873
|
+
.value="${currentOp}"
|
|
12874
|
+
@change="${(e) => {
|
|
12875
|
+
const op = e.target.value;
|
|
12876
|
+
const valInput = this.shadowRoot.querySelector(`#filter-val-${col.field}`);
|
|
12877
|
+
this._updateFilter(col.field, op, valInput?.value ?? currentVal);
|
|
12878
|
+
}}"
|
|
12879
|
+
>
|
|
12880
|
+
${operators.map(op => b `
|
|
12881
|
+
<option value="${op.value}" ?selected="${op.value === currentOp}">${op.label}</option>
|
|
12882
|
+
`)}
|
|
12883
|
+
</select>
|
|
12884
|
+
|
|
12885
|
+
${!['isEmpty', 'isNotEmpty', 'isTrue', 'isFalse'].includes(currentOp) ? b `
|
|
12886
|
+
${type === 'boolean' ? A :
|
|
12887
|
+
type === 'singleSelect' && col.valueOptions ? b `
|
|
12888
|
+
<select id="filter-val-${col.field}" .value="${currentVal}"
|
|
12889
|
+
@change="${(e) => this._updateFilter(col.field, currentOp, e.target.value)}">
|
|
12890
|
+
<option value="">-- Select --</option>
|
|
12891
|
+
${col.valueOptions.map(opt => {
|
|
12892
|
+
const val = typeof opt === 'string' ? opt : opt.value;
|
|
12893
|
+
const label = typeof opt === 'string' ? opt : opt.label;
|
|
12894
|
+
return b `<option value="${val}" ?selected="${val === currentVal}">${label}</option>`;
|
|
12895
|
+
})}
|
|
12896
|
+
</select>
|
|
12897
|
+
` : b `
|
|
12898
|
+
<input id="filter-val-${col.field}"
|
|
12899
|
+
type="${type === 'number' ? 'number' : type === 'date' ? 'date' : 'text'}"
|
|
12900
|
+
.value="${currentVal}"
|
|
12901
|
+
placeholder="Value..."
|
|
12902
|
+
@keydown="${(e) => {
|
|
12903
|
+
if (e.key === 'Enter') {
|
|
12904
|
+
this._updateFilter(col.field, currentOp, e.target.value);
|
|
12905
|
+
}
|
|
12906
|
+
}}" />
|
|
12907
|
+
`}
|
|
12908
|
+
` : A}
|
|
12909
|
+
|
|
12910
|
+
<div class="filter-popover-actions">
|
|
12911
|
+
<button class="filter-clear-btn" @click="${() => this._removeFilter(col.field)}">Clear</button>
|
|
12912
|
+
<button class="filter-apply-btn" @click="${() => {
|
|
12913
|
+
const valInput = this.shadowRoot.querySelector(`#filter-val-${col.field}`);
|
|
12914
|
+
this._updateFilter(col.field, currentOp, valInput?.value ?? '');
|
|
12915
|
+
this._filterPopoverField = null;
|
|
12916
|
+
}}">Apply</button>
|
|
12917
|
+
</div>
|
|
12918
|
+
</div>
|
|
12919
|
+
`;
|
|
12920
|
+
}
|
|
12921
|
+
// ── Body ──
|
|
12922
|
+
_renderBody() {
|
|
12923
|
+
const rows = this.disableVirtualization || this.autoHeight
|
|
12924
|
+
? this._paginatedRows
|
|
12925
|
+
: this._visibleRows;
|
|
12926
|
+
const rh = this._effectiveRowHeight;
|
|
12927
|
+
const totalHeight = this._paginatedRows.length * rh;
|
|
12928
|
+
if (this.disableVirtualization || this.autoHeight) {
|
|
12929
|
+
return b `
|
|
12930
|
+
<div class="grid-body-inner">
|
|
12931
|
+
${rows.map(row => this._renderRow(row))}
|
|
12932
|
+
</div>
|
|
12933
|
+
`;
|
|
12934
|
+
}
|
|
12935
|
+
// Virtual scrolling
|
|
12936
|
+
return b `
|
|
12937
|
+
<div class="grid-body-inner" style="height:${totalHeight}px">
|
|
12938
|
+
${rows.map(row => {
|
|
12939
|
+
const vIdx = row._virtualIndex ?? 0;
|
|
12940
|
+
return b `
|
|
12941
|
+
<div style="position:absolute; top:${vIdx * rh}px; width:100%">
|
|
12942
|
+
${this._renderRow(row)}
|
|
12943
|
+
</div>
|
|
12944
|
+
`;
|
|
12945
|
+
})}
|
|
12946
|
+
</div>
|
|
12947
|
+
`;
|
|
12948
|
+
}
|
|
12949
|
+
_renderRow(row) {
|
|
12950
|
+
if (row.type === 'group') {
|
|
12951
|
+
return this._renderGroupRow(row);
|
|
12952
|
+
}
|
|
12953
|
+
const isSelected = this._selectedRows.has(row.id);
|
|
12954
|
+
const rh = this._effectiveRowHeight;
|
|
12955
|
+
return b `
|
|
12956
|
+
<div class="grid-row ${isSelected ? 'selected' : ''}"
|
|
12957
|
+
style="height:${rh}px"
|
|
12958
|
+
role="row"
|
|
12959
|
+
aria-selected="${isSelected}"
|
|
12960
|
+
@click="${(e) => {
|
|
12961
|
+
this.dispatchEvent(new CustomEvent('row-click', { detail: { row: row.data, rowId: row.id } }));
|
|
12962
|
+
}}"
|
|
12963
|
+
>
|
|
12964
|
+
${this.checkboxSelection ? b `
|
|
12965
|
+
<div class="checkbox-cell" style="height:${rh}px">
|
|
12966
|
+
<input type="checkbox"
|
|
12967
|
+
.checked="${isSelected}"
|
|
12968
|
+
@change="${() => this._toggleRowSelection(row.id)}"
|
|
12969
|
+
@click="${(e) => e.stopPropagation()}" />
|
|
12970
|
+
</div>
|
|
12971
|
+
` : A}
|
|
12972
|
+
|
|
12973
|
+
${this._visibleColumns.map((col, colIdx) => this._renderCell(row, col, colIdx))}
|
|
12974
|
+
</div>
|
|
12975
|
+
`;
|
|
12976
|
+
}
|
|
12977
|
+
_renderGroupRow(row) {
|
|
12978
|
+
const groupKey = `${row.groupField}:${row.groupValue}`;
|
|
12979
|
+
const rh = this._effectiveRowHeight;
|
|
12980
|
+
this._visibleColumns.length + (this.checkboxSelection ? 1 : 0);
|
|
12981
|
+
return b `
|
|
12982
|
+
<div class="grid-row group-row" style="height:${rh}px"
|
|
12983
|
+
@click="${() => this._toggleGroupExpand(groupKey)}">
|
|
12984
|
+
<div class="grid-cell" style="width:100%; height:${rh}px">
|
|
12985
|
+
<span class="expand-toggle">${row.isExpanded ? '▾' : '▸'}</span>
|
|
12986
|
+
<span style="font-weight:600">${row.groupField}: ${row.groupValue}</span>
|
|
12987
|
+
<span style="margin-left:8px;color:var(--dg-text-secondary)">(${row.childCount})</span>
|
|
12988
|
+
${row.aggregations ? Object.entries(row.aggregations).map(([field, val]) => b `
|
|
12989
|
+
<span style="margin-left:16px;font-weight:400;color:var(--dg-text-secondary)">
|
|
12990
|
+
${field}: ${val}
|
|
12991
|
+
</span>
|
|
12992
|
+
`) : A}
|
|
12993
|
+
</div>
|
|
12994
|
+
</div>
|
|
12995
|
+
`;
|
|
12996
|
+
}
|
|
12997
|
+
_renderCell(row, col, colIdx) {
|
|
12998
|
+
const width = this._getColumnWidth(col.field);
|
|
12999
|
+
const rh = this._effectiveRowHeight;
|
|
13000
|
+
const isEditing = this._editingCell?.rowId === row.id && this._editingCell?.field === col.field;
|
|
13001
|
+
const value = this._getCellValue(row.data, col);
|
|
13002
|
+
// Tree indent
|
|
13003
|
+
const indent = (row.type === 'tree' || row.type === 'data') && row.depth ? row.depth * 24 : 0;
|
|
13004
|
+
const showExpand = row.type === 'tree' && (row.childCount ?? 0) > 0 && colIdx === 0;
|
|
13005
|
+
return b `
|
|
13006
|
+
<div class="grid-cell"
|
|
13007
|
+
style="width:${width}px; height:${rh}px"
|
|
13008
|
+
role="gridcell"
|
|
13009
|
+
@dblclick="${() => this._startEditing(row.id, col.field)}"
|
|
13010
|
+
@click="${(e) => {
|
|
13011
|
+
this.dispatchEvent(new CustomEvent('cell-click', { detail: { row: row.data, field: col.field, value } }));
|
|
13012
|
+
}}"
|
|
13013
|
+
>
|
|
13014
|
+
${colIdx === 0 && indent > 0 ? b `<span class="tree-indent" style="width:${indent}px"></span>` : A}
|
|
13015
|
+
${showExpand ? b `
|
|
13016
|
+
<span class="expand-toggle" @click="${(e) => { e.stopPropagation(); this._toggleRowExpand(row.id); }}">
|
|
13017
|
+
${row.isExpanded ? '▾' : '▸'}
|
|
13018
|
+
</span>
|
|
13019
|
+
` : A}
|
|
13020
|
+
|
|
13021
|
+
${isEditing ? this._renderEditCell(row, col, value) :
|
|
13022
|
+
col.renderCell ? col.renderCell({ value, row: row.data, field: col.field, colDef: col, rowIndex: 0, isEditing: false }) :
|
|
13023
|
+
b `<span class="cell-content ${col.align || ''}">${this._getDisplayValue(row.data, col)}</span>`}
|
|
13024
|
+
</div>
|
|
13025
|
+
`;
|
|
13026
|
+
}
|
|
13027
|
+
_renderEditCell(row, col, value) {
|
|
13028
|
+
const type = col.type || 'string';
|
|
13029
|
+
if (col.renderEditCell) {
|
|
13030
|
+
return col.renderEditCell({ value, row: row.data, field: col.field, colDef: col });
|
|
13031
|
+
}
|
|
13032
|
+
if (type === 'boolean') {
|
|
13033
|
+
return b `
|
|
13034
|
+
<input type="checkbox"
|
|
13035
|
+
.checked="${!!this._editValue}"
|
|
13036
|
+
@change="${(e) => { this._editValue = e.target.checked; this._commitEdit(); }}"
|
|
13037
|
+
@keydown="${this._handleEditKeydown}" />
|
|
13038
|
+
`;
|
|
13039
|
+
}
|
|
13040
|
+
if (type === 'singleSelect' && col.valueOptions) {
|
|
13041
|
+
return b `
|
|
13042
|
+
<select class="cell-edit-select"
|
|
13043
|
+
.value="${this._editValue ?? ''}"
|
|
13044
|
+
@change="${(e) => { this._editValue = e.target.value; this._commitEdit(); }}"
|
|
13045
|
+
@keydown="${this._handleEditKeydown}">
|
|
13046
|
+
${col.valueOptions.map(opt => {
|
|
13047
|
+
const val = typeof opt === 'string' ? opt : opt.value;
|
|
13048
|
+
const label = typeof opt === 'string' ? opt : opt.label;
|
|
13049
|
+
return b `<option value="${val}" ?selected="${val === this._editValue}">${label}</option>`;
|
|
13050
|
+
})}
|
|
13051
|
+
</select>
|
|
13052
|
+
`;
|
|
13053
|
+
}
|
|
13054
|
+
return b `
|
|
13055
|
+
<input class="cell-edit-input"
|
|
13056
|
+
type="${type === 'number' ? 'number' : type === 'date' ? 'date' : 'text'}"
|
|
13057
|
+
.value="${this._editValue ?? ''}"
|
|
13058
|
+
@input="${(e) => { this._editValue = e.target.value; }}"
|
|
13059
|
+
@keydown="${this._handleEditKeydown}"
|
|
13060
|
+
@blur="${this._commitEdit}" />
|
|
13061
|
+
`;
|
|
13062
|
+
}
|
|
13063
|
+
// ── Footer ──
|
|
13064
|
+
_renderFooter() {
|
|
13065
|
+
const start = this._currentPage * this.pageSize + 1;
|
|
13066
|
+
const end = Math.min(start + this.pageSize - 1, this._totalRowCount);
|
|
13067
|
+
return b `
|
|
13068
|
+
<div class="grid-footer">
|
|
13069
|
+
<div class="footer-info">
|
|
13070
|
+
${this._selectedRows.size > 0 ? b `
|
|
13071
|
+
<span>${this._selectedRows.size} row(s) selected</span>
|
|
13072
|
+
` : A}
|
|
13073
|
+
<span>Rows per page:</span>
|
|
13074
|
+
<select class="page-size-select" @change="${this._changePageSize}">
|
|
13075
|
+
${this.pageSizeOptions.map(s => b `
|
|
13076
|
+
<option value="${s}" ?selected="${s === this.pageSize}">${s}</option>
|
|
13077
|
+
`)}
|
|
13078
|
+
</select>
|
|
13079
|
+
<span>${this._totalRowCount > 0 ? `${start}–${end} of ${this._totalRowCount}` : '0 rows'}</span>
|
|
13080
|
+
</div>
|
|
13081
|
+
<div class="footer-pagination">
|
|
13082
|
+
<button class="page-btn" ?disabled="${this._currentPage === 0}" @click="${() => this._goToPage(0)}">
|
|
13083
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg>
|
|
13084
|
+
</button>
|
|
13085
|
+
<button class="page-btn" ?disabled="${this._currentPage === 0}" @click="${() => this._goToPage(this._currentPage - 1)}">
|
|
13086
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
|
13087
|
+
</button>
|
|
13088
|
+
${this._renderPageButtons()}
|
|
13089
|
+
<button class="page-btn" ?disabled="${this._currentPage >= this._totalPages - 1}" @click="${() => this._goToPage(this._currentPage + 1)}">
|
|
13090
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
|
13091
|
+
</button>
|
|
13092
|
+
<button class="page-btn" ?disabled="${this._currentPage >= this._totalPages - 1}" @click="${() => this._goToPage(this._totalPages - 1)}">
|
|
13093
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg>
|
|
13094
|
+
</button>
|
|
13095
|
+
</div>
|
|
13096
|
+
</div>
|
|
13097
|
+
`;
|
|
13098
|
+
}
|
|
13099
|
+
_renderPageButtons() {
|
|
13100
|
+
const total = this._totalPages;
|
|
13101
|
+
if (total <= 7) {
|
|
13102
|
+
return Array.from({ length: total }, (_, i) => b `
|
|
13103
|
+
<button class="page-btn ${i === this._currentPage ? 'active' : ''}" @click="${() => this._goToPage(i)}">${i + 1}</button>
|
|
13104
|
+
`);
|
|
13105
|
+
}
|
|
13106
|
+
const pages = [];
|
|
13107
|
+
const current = this._currentPage;
|
|
13108
|
+
pages.push(0);
|
|
13109
|
+
if (current > 2)
|
|
13110
|
+
pages.push('...');
|
|
13111
|
+
for (let i = Math.max(1, current - 1); i <= Math.min(total - 2, current + 1); i++) {
|
|
13112
|
+
pages.push(i);
|
|
13113
|
+
}
|
|
13114
|
+
if (current < total - 3)
|
|
13115
|
+
pages.push('...');
|
|
13116
|
+
pages.push(total - 1);
|
|
13117
|
+
return pages.map(p => typeof p === 'string'
|
|
13118
|
+
? b `<span style="padding:0 4px">…</span>`
|
|
13119
|
+
: b `<button class="page-btn ${p === current ? 'active' : ''}" @click="${() => this._goToPage(p)}">${p + 1}</button>`);
|
|
13120
|
+
}
|
|
13121
|
+
};
|
|
13122
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
13123
|
+
// STYLES
|
|
13124
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
13125
|
+
ChefUiDataGrid.styles = i$3 `
|
|
13126
|
+
:host {
|
|
13127
|
+
display: block;
|
|
13128
|
+
--dg-border: #e0e0e0;
|
|
13129
|
+
--dg-header-bg: #fafafa;
|
|
13130
|
+
--dg-row-hover: #f5f5f5;
|
|
13131
|
+
--dg-row-selected: #e3f2fd;
|
|
13132
|
+
--dg-primary: #1976d2;
|
|
13133
|
+
--dg-text: #000000;
|
|
13134
|
+
--dg-text-secondary: #555;
|
|
13135
|
+
--dg-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
13136
|
+
font-family: var(--dg-font);
|
|
13137
|
+
color: var(--dg-text);
|
|
13138
|
+
}
|
|
13139
|
+
|
|
13140
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
13141
|
+
|
|
13142
|
+
.grid-container {
|
|
13143
|
+
border: 1px solid var(--dg-border);
|
|
13144
|
+
border-radius: 4px;
|
|
13145
|
+
overflow: visible;
|
|
13146
|
+
position: relative;
|
|
13147
|
+
background: #fff;
|
|
13148
|
+
}
|
|
13149
|
+
|
|
13150
|
+
/* ── Toolbar ── */
|
|
13151
|
+
.toolbar {
|
|
13152
|
+
display: flex;
|
|
13153
|
+
align-items: center;
|
|
13154
|
+
gap: 8px;
|
|
13155
|
+
padding: 8px 16px;
|
|
13156
|
+
border-bottom: 1px solid var(--dg-border);
|
|
13157
|
+
background: var(--dg-header-bg);
|
|
13158
|
+
flex-wrap: wrap;
|
|
13159
|
+
}
|
|
13160
|
+
|
|
13161
|
+
.toolbar-search {
|
|
13162
|
+
display: flex;
|
|
13163
|
+
align-items: center;
|
|
13164
|
+
gap: 6px;
|
|
13165
|
+
background: #fff;
|
|
13166
|
+
border: 1px solid var(--dg-border);
|
|
13167
|
+
border-radius: 4px;
|
|
13168
|
+
padding: 4px 10px;
|
|
13169
|
+
flex: 1;
|
|
13170
|
+
max-width: 300px;
|
|
13171
|
+
}
|
|
13172
|
+
|
|
13173
|
+
.toolbar-search svg { width: 16px; height: 16px; color: #999; flex-shrink: 0; }
|
|
13174
|
+
|
|
13175
|
+
.toolbar-search input {
|
|
13176
|
+
border: none;
|
|
13177
|
+
outline: none;
|
|
13178
|
+
font-size: 13px;
|
|
13179
|
+
flex: 1;
|
|
13180
|
+
min-width: 0;
|
|
13181
|
+
background: transparent;
|
|
13182
|
+
font-family: var(--dg-font);
|
|
13183
|
+
}
|
|
13184
|
+
|
|
13185
|
+
.toolbar-btn {
|
|
13186
|
+
display: flex;
|
|
13187
|
+
align-items: center;
|
|
13188
|
+
gap: 4px;
|
|
13189
|
+
padding: 6px 12px;
|
|
13190
|
+
border: 1px solid var(--dg-border);
|
|
13191
|
+
border-radius: 4px;
|
|
13192
|
+
background: #fff;
|
|
13193
|
+
cursor: pointer;
|
|
13194
|
+
font-size: 13px;
|
|
13195
|
+
color: var(--dg-text);
|
|
13196
|
+
transition: background 0.15s;
|
|
13197
|
+
position: relative;
|
|
13198
|
+
font-family: var(--dg-font);
|
|
13199
|
+
}
|
|
13200
|
+
|
|
13201
|
+
.toolbar-btn:hover { background: #f0f0f0; }
|
|
13202
|
+
.toolbar-btn svg { width: 16px; height: 16px; }
|
|
13203
|
+
|
|
13204
|
+
.toolbar-spacer { flex: 1; }
|
|
13205
|
+
|
|
13206
|
+
/* ── Dropdown menus ── */
|
|
13207
|
+
.dropdown {
|
|
13208
|
+
position: absolute;
|
|
13209
|
+
top: 100%;
|
|
13210
|
+
right: 0;
|
|
13211
|
+
margin-top: 4px;
|
|
13212
|
+
background: #fff;
|
|
13213
|
+
border: 1px solid var(--dg-border);
|
|
13214
|
+
border-radius: 6px;
|
|
13215
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
|
13216
|
+
z-index: 100;
|
|
13217
|
+
min-width: 160px;
|
|
13218
|
+
padding: 4px 0;
|
|
13219
|
+
}
|
|
13220
|
+
|
|
13221
|
+
.dropdown-item {
|
|
13222
|
+
display: flex;
|
|
13223
|
+
align-items: center;
|
|
13224
|
+
gap: 8px;
|
|
13225
|
+
padding: 8px 16px;
|
|
13226
|
+
cursor: pointer;
|
|
13227
|
+
font-size: 13px;
|
|
13228
|
+
transition: background 0.15s;
|
|
13229
|
+
white-space: nowrap;
|
|
13230
|
+
}
|
|
13231
|
+
|
|
13232
|
+
.dropdown-item:hover { background: #f5f5f5; }
|
|
13233
|
+
.dropdown-item.active { color: var(--dg-primary); font-weight: 500; }
|
|
13234
|
+
|
|
13235
|
+
.dropdown-item input[type="checkbox"] {
|
|
13236
|
+
margin: 0;
|
|
13237
|
+
cursor: pointer;
|
|
13238
|
+
}
|
|
13239
|
+
|
|
13240
|
+
/* ── Header ── */
|
|
13241
|
+
.grid-header {
|
|
13242
|
+
display: flex;
|
|
13243
|
+
background: var(--dg-header-bg);
|
|
13244
|
+
border-bottom: 2px solid var(--dg-border);
|
|
13245
|
+
position: sticky;
|
|
13246
|
+
top: 0;
|
|
13247
|
+
z-index: 10;
|
|
13248
|
+
overflow: visible;
|
|
13249
|
+
}
|
|
13250
|
+
|
|
13251
|
+
.header-cell {
|
|
13252
|
+
display: flex;
|
|
13253
|
+
align-items: center;
|
|
13254
|
+
padding: 0 12px;
|
|
13255
|
+
font-size: 13px;
|
|
13256
|
+
font-weight: 600;
|
|
13257
|
+
color: var(--dg-text);
|
|
13258
|
+
border-right: 1px solid var(--dg-border);
|
|
13259
|
+
position: relative;
|
|
13260
|
+
user-select: none;
|
|
13261
|
+
overflow: visible;
|
|
13262
|
+
flex-shrink: 0;
|
|
13263
|
+
}
|
|
13264
|
+
|
|
13265
|
+
.header-cell:last-child { border-right: none; }
|
|
13266
|
+
|
|
13267
|
+
.header-cell.sortable { cursor: pointer; }
|
|
13268
|
+
.header-cell.sortable:hover { background: #f0f0f0; }
|
|
13269
|
+
|
|
13270
|
+
.header-text {
|
|
13271
|
+
flex: 1;
|
|
13272
|
+
overflow: hidden;
|
|
13273
|
+
text-overflow: ellipsis;
|
|
13274
|
+
white-space: nowrap;
|
|
13275
|
+
}
|
|
13276
|
+
|
|
13277
|
+
.sort-icon { margin-left: 4px; font-size: 11px; color: var(--dg-primary); }
|
|
13278
|
+
.sort-index { font-size: 9px; color: var(--dg-primary); margin-left: 2px; }
|
|
13279
|
+
|
|
13280
|
+
.filter-icon {
|
|
13281
|
+
margin-left: 4px;
|
|
13282
|
+
cursor: pointer;
|
|
13283
|
+
opacity: 0.4;
|
|
13284
|
+
transition: opacity 0.15s;
|
|
13285
|
+
width: 14px;
|
|
13286
|
+
height: 14px;
|
|
13287
|
+
}
|
|
13288
|
+
|
|
13289
|
+
.filter-icon:hover, .filter-icon.active { opacity: 1; color: var(--dg-primary); }
|
|
13290
|
+
|
|
13291
|
+
.resize-handle {
|
|
13292
|
+
position: absolute;
|
|
13293
|
+
right: 0;
|
|
13294
|
+
top: 0;
|
|
13295
|
+
bottom: 0;
|
|
13296
|
+
width: 6px;
|
|
13297
|
+
cursor: col-resize;
|
|
13298
|
+
z-index: 2;
|
|
13299
|
+
}
|
|
13300
|
+
|
|
13301
|
+
.resize-handle:hover { background: var(--dg-primary); opacity: 0.3; }
|
|
13302
|
+
|
|
13303
|
+
.checkbox-cell {
|
|
13304
|
+
width: 48px;
|
|
13305
|
+
min-width: 48px;
|
|
13306
|
+
max-width: 48px;
|
|
13307
|
+
display: flex;
|
|
13308
|
+
align-items: center;
|
|
13309
|
+
justify-content: center;
|
|
13310
|
+
flex-shrink: 0;
|
|
13311
|
+
border-right: 1px solid var(--dg-border);
|
|
13312
|
+
}
|
|
13313
|
+
|
|
13314
|
+
.checkbox-cell input { cursor: pointer; width: 16px; height: 16px; }
|
|
13315
|
+
|
|
13316
|
+
/* ── Body ── */
|
|
13317
|
+
.grid-body {
|
|
13318
|
+
overflow-y: auto;
|
|
13319
|
+
overflow-x: auto;
|
|
13320
|
+
position: relative;
|
|
13321
|
+
}
|
|
13322
|
+
|
|
13323
|
+
.grid-body-inner {
|
|
13324
|
+
position: relative;
|
|
13325
|
+
}
|
|
13326
|
+
|
|
13327
|
+
.grid-row {
|
|
13328
|
+
display: flex;
|
|
13329
|
+
border-bottom: 1px solid #f0f0f0;
|
|
13330
|
+
transition: background 0.1s;
|
|
13331
|
+
position: relative;
|
|
13332
|
+
}
|
|
13333
|
+
|
|
13334
|
+
.grid-row:hover { background: var(--dg-row-hover); }
|
|
13335
|
+
.grid-row.selected { background: var(--dg-row-selected); }
|
|
13336
|
+
|
|
13337
|
+
.grid-row.group-row {
|
|
13338
|
+
background: #f8f8f8;
|
|
13339
|
+
font-weight: 600;
|
|
13340
|
+
cursor: pointer;
|
|
13341
|
+
}
|
|
13342
|
+
|
|
13343
|
+
.grid-row.group-row:hover { background: #f0f0f0; }
|
|
13344
|
+
|
|
13345
|
+
.grid-cell {
|
|
13346
|
+
display: flex;
|
|
13347
|
+
align-items: center;
|
|
13348
|
+
padding: 0 12px;
|
|
13349
|
+
font-size: 13px;
|
|
13350
|
+
overflow: hidden;
|
|
13351
|
+
flex-shrink: 0;
|
|
13352
|
+
border-right: 1px solid transparent;
|
|
13353
|
+
}
|
|
13354
|
+
|
|
13355
|
+
.grid-cell:last-child { border-right: none; }
|
|
13356
|
+
|
|
13357
|
+
.cell-content {
|
|
13358
|
+
overflow: hidden;
|
|
13359
|
+
text-overflow: ellipsis;
|
|
13360
|
+
white-space: nowrap;
|
|
13361
|
+
flex: 1;
|
|
13362
|
+
}
|
|
13363
|
+
|
|
13364
|
+
.cell-content.right { text-align: right; }
|
|
13365
|
+
.cell-content.center { text-align: center; }
|
|
13366
|
+
|
|
13367
|
+
.cell-edit-input {
|
|
13368
|
+
width: 100%;
|
|
13369
|
+
border: 2px solid var(--dg-primary);
|
|
13370
|
+
border-radius: 3px;
|
|
13371
|
+
padding: 4px 8px;
|
|
13372
|
+
font-size: 13px;
|
|
13373
|
+
font-family: var(--dg-font);
|
|
13374
|
+
outline: none;
|
|
13375
|
+
background: #fff;
|
|
13376
|
+
}
|
|
13377
|
+
|
|
13378
|
+
.cell-edit-select {
|
|
13379
|
+
width: 100%;
|
|
13380
|
+
border: 2px solid var(--dg-primary);
|
|
13381
|
+
border-radius: 3px;
|
|
13382
|
+
padding: 4px 8px;
|
|
13383
|
+
font-size: 13px;
|
|
13384
|
+
font-family: var(--dg-font);
|
|
13385
|
+
outline: none;
|
|
13386
|
+
background: #fff;
|
|
13387
|
+
}
|
|
13388
|
+
|
|
13389
|
+
.expand-toggle {
|
|
13390
|
+
cursor: pointer;
|
|
13391
|
+
margin-right: 4px;
|
|
13392
|
+
user-select: none;
|
|
13393
|
+
font-size: 12px;
|
|
13394
|
+
width: 20px;
|
|
13395
|
+
text-align: center;
|
|
13396
|
+
flex-shrink: 0;
|
|
13397
|
+
}
|
|
13398
|
+
|
|
13399
|
+
.tree-indent {
|
|
13400
|
+
display: inline-block;
|
|
13401
|
+
flex-shrink: 0;
|
|
13402
|
+
}
|
|
13403
|
+
|
|
13404
|
+
/* ── Footer ── */
|
|
13405
|
+
.grid-footer {
|
|
13406
|
+
display: flex;
|
|
13407
|
+
align-items: center;
|
|
13408
|
+
justify-content: space-between;
|
|
13409
|
+
padding: 8px 16px;
|
|
13410
|
+
border-top: 1px solid var(--dg-border);
|
|
13411
|
+
background: var(--dg-header-bg);
|
|
13412
|
+
font-size: 13px;
|
|
13413
|
+
color: var(--dg-text-secondary);
|
|
13414
|
+
flex-wrap: wrap;
|
|
13415
|
+
gap: 8px;
|
|
13416
|
+
}
|
|
13417
|
+
|
|
13418
|
+
.footer-info { display: flex; align-items: center; gap: 16px; }
|
|
13419
|
+
.footer-pagination { display: flex; align-items: center; gap: 4px; }
|
|
13420
|
+
|
|
13421
|
+
.page-btn {
|
|
13422
|
+
width: 32px;
|
|
13423
|
+
height: 32px;
|
|
13424
|
+
display: flex;
|
|
13425
|
+
align-items: center;
|
|
13426
|
+
justify-content: center;
|
|
13427
|
+
border: 1px solid var(--dg-border);
|
|
13428
|
+
border-radius: 4px;
|
|
13429
|
+
background: #fff;
|
|
13430
|
+
cursor: pointer;
|
|
13431
|
+
font-size: 13px;
|
|
13432
|
+
transition: background 0.15s;
|
|
13433
|
+
color: var(--dg-text);
|
|
13434
|
+
font-family: var(--dg-font);
|
|
13435
|
+
}
|
|
13436
|
+
|
|
13437
|
+
.page-btn:hover:not(:disabled) { background: #f0f0f0; }
|
|
13438
|
+
.page-btn:disabled { opacity: 0.4; cursor: default; }
|
|
13439
|
+
.page-btn.active { background: var(--dg-primary); color: #fff; border-color: var(--dg-primary); }
|
|
13440
|
+
.page-btn svg { width: 14px; height: 14px; }
|
|
13441
|
+
|
|
13442
|
+
.page-size-select {
|
|
13443
|
+
border: 1px solid var(--dg-border);
|
|
13444
|
+
border-radius: 4px;
|
|
13445
|
+
padding: 4px 8px;
|
|
13446
|
+
font-size: 13px;
|
|
13447
|
+
background: #fff;
|
|
13448
|
+
cursor: pointer;
|
|
13449
|
+
font-family: var(--dg-font);
|
|
13450
|
+
}
|
|
13451
|
+
|
|
13452
|
+
/* ── Filter Popover ── */
|
|
13453
|
+
.filter-popover {
|
|
13454
|
+
position: absolute;
|
|
13455
|
+
background: #fff;
|
|
13456
|
+
border: 1px solid var(--dg-border);
|
|
13457
|
+
border-radius: 8px;
|
|
13458
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.18);
|
|
13459
|
+
z-index: 1000;
|
|
13460
|
+
padding: 16px;
|
|
13461
|
+
min-width: 280px;
|
|
13462
|
+
max-width: 340px;
|
|
13463
|
+
}
|
|
13464
|
+
|
|
13465
|
+
.filter-popover-title {
|
|
13466
|
+
font-size: 14px;
|
|
13467
|
+
font-weight: 600;
|
|
13468
|
+
margin-bottom: 12px;
|
|
13469
|
+
}
|
|
13470
|
+
|
|
13471
|
+
.filter-popover select,
|
|
13472
|
+
.filter-popover input {
|
|
13473
|
+
width: 100%;
|
|
13474
|
+
padding: 8px 10px;
|
|
13475
|
+
border: 1px solid var(--dg-border);
|
|
13476
|
+
border-radius: 4px;
|
|
13477
|
+
font-size: 13px;
|
|
13478
|
+
margin-bottom: 8px;
|
|
13479
|
+
font-family: var(--dg-font);
|
|
13480
|
+
outline: none;
|
|
13481
|
+
}
|
|
13482
|
+
|
|
13483
|
+
.filter-popover select:focus,
|
|
13484
|
+
.filter-popover input:focus { border-color: var(--dg-primary); }
|
|
13485
|
+
|
|
13486
|
+
.filter-popover-actions {
|
|
13487
|
+
display: flex;
|
|
13488
|
+
gap: 8px;
|
|
13489
|
+
margin-top: 8px;
|
|
13490
|
+
}
|
|
13491
|
+
|
|
13492
|
+
.filter-apply-btn, .filter-clear-btn {
|
|
13493
|
+
flex: 1;
|
|
13494
|
+
padding: 8px;
|
|
13495
|
+
border-radius: 4px;
|
|
13496
|
+
font-size: 13px;
|
|
13497
|
+
cursor: pointer;
|
|
13498
|
+
font-family: var(--dg-font);
|
|
13499
|
+
border: 1px solid var(--dg-border);
|
|
13500
|
+
}
|
|
13501
|
+
|
|
13502
|
+
.filter-apply-btn {
|
|
13503
|
+
background: var(--dg-primary);
|
|
13504
|
+
color: #fff;
|
|
13505
|
+
border-color: var(--dg-primary);
|
|
13506
|
+
}
|
|
13507
|
+
|
|
13508
|
+
.filter-clear-btn { background: #fff; }
|
|
13509
|
+
.filter-apply-btn:hover { opacity: 0.9; }
|
|
13510
|
+
.filter-clear-btn:hover { background: #f5f5f5; }
|
|
13511
|
+
|
|
13512
|
+
/* ── Loading ── */
|
|
13513
|
+
.loading-overlay {
|
|
13514
|
+
position: absolute;
|
|
13515
|
+
inset: 0;
|
|
13516
|
+
background: rgba(255,255,255,0.7);
|
|
13517
|
+
display: flex;
|
|
13518
|
+
align-items: center;
|
|
13519
|
+
justify-content: center;
|
|
13520
|
+
z-index: 50;
|
|
13521
|
+
font-size: 14px;
|
|
13522
|
+
color: var(--dg-text-secondary);
|
|
13523
|
+
}
|
|
13524
|
+
|
|
13525
|
+
.loading-spinner {
|
|
13526
|
+
width: 24px;
|
|
13527
|
+
height: 24px;
|
|
13528
|
+
border: 3px solid #e0e0e0;
|
|
13529
|
+
border-top-color: var(--dg-primary);
|
|
13530
|
+
border-radius: 50%;
|
|
13531
|
+
animation: dg-spin 0.8s linear infinite;
|
|
13532
|
+
margin-right: 8px;
|
|
13533
|
+
}
|
|
13534
|
+
|
|
13535
|
+
@keyframes dg-spin { to { transform: rotate(360deg); } }
|
|
13536
|
+
|
|
13537
|
+
/* ── No Rows ── */
|
|
13538
|
+
.no-rows {
|
|
13539
|
+
display: flex;
|
|
13540
|
+
align-items: center;
|
|
13541
|
+
justify-content: center;
|
|
13542
|
+
padding: 48px 16px;
|
|
13543
|
+
color: var(--dg-text-secondary);
|
|
13544
|
+
font-size: 14px;
|
|
13545
|
+
}
|
|
13546
|
+
|
|
13547
|
+
/* ── Column Menu Button ── */
|
|
13548
|
+
.column-menu-btn {
|
|
13549
|
+
display: flex;
|
|
13550
|
+
align-items: center;
|
|
13551
|
+
justify-content: center;
|
|
13552
|
+
width: 24px;
|
|
13553
|
+
height: 24px;
|
|
13554
|
+
border: none;
|
|
13555
|
+
background: none;
|
|
13556
|
+
cursor: pointer;
|
|
13557
|
+
border-radius: 4px;
|
|
13558
|
+
color: #999;
|
|
13559
|
+
font-size: 16px;
|
|
13560
|
+
flex-shrink: 0;
|
|
13561
|
+
margin-left: 2px;
|
|
13562
|
+
padding: 0;
|
|
13563
|
+
transition: background 0.15s, color 0.15s;
|
|
13564
|
+
font-family: var(--dg-font);
|
|
13565
|
+
line-height: 1;
|
|
13566
|
+
}
|
|
13567
|
+
|
|
13568
|
+
.column-menu-btn:hover { background: rgba(0,0,0,0.08); color: var(--dg-text); }
|
|
13569
|
+
.column-menu-btn.active { background: rgba(0,0,0,0.08); color: var(--dg-primary); }
|
|
13570
|
+
|
|
13571
|
+
/* ── Column Menu ── */
|
|
13572
|
+
.column-menu {
|
|
13573
|
+
position: absolute;
|
|
13574
|
+
background: #fff;
|
|
13575
|
+
border: 1px solid var(--dg-border);
|
|
13576
|
+
border-radius: 8px;
|
|
13577
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.18);
|
|
13578
|
+
z-index: 1000;
|
|
13579
|
+
padding: 6px 0;
|
|
13580
|
+
min-width: 200px;
|
|
13581
|
+
}
|
|
13582
|
+
|
|
13583
|
+
.column-menu-item {
|
|
13584
|
+
display: flex;
|
|
13585
|
+
align-items: center;
|
|
13586
|
+
gap: 10px;
|
|
13587
|
+
width: 100%;
|
|
13588
|
+
padding: 9px 16px;
|
|
13589
|
+
border: none;
|
|
13590
|
+
background: none;
|
|
13591
|
+
cursor: pointer;
|
|
13592
|
+
font-size: 13px;
|
|
13593
|
+
text-align: left;
|
|
13594
|
+
transition: background 0.15s;
|
|
13595
|
+
font-family: var(--dg-font);
|
|
13596
|
+
color: var(--dg-text);
|
|
13597
|
+
white-space: nowrap;
|
|
13598
|
+
}
|
|
13599
|
+
|
|
13600
|
+
.column-menu-item:hover { background: #f5f5f5; }
|
|
13601
|
+
|
|
13602
|
+
.column-menu-item svg {
|
|
13603
|
+
width: 18px;
|
|
13604
|
+
height: 18px;
|
|
13605
|
+
flex-shrink: 0;
|
|
13606
|
+
color: #666;
|
|
13607
|
+
}
|
|
13608
|
+
|
|
13609
|
+
.column-menu-item:hover svg { color: var(--dg-text); }
|
|
13610
|
+
|
|
13611
|
+
.column-menu-divider {
|
|
13612
|
+
height: 1px;
|
|
13613
|
+
background: var(--dg-border);
|
|
13614
|
+
margin: 4px 0;
|
|
13615
|
+
}
|
|
13616
|
+
|
|
13617
|
+
/* ── Responsive ── */
|
|
13618
|
+
@media (max-width: 600px) {
|
|
13619
|
+
.toolbar { padding: 6px 8px; }
|
|
13620
|
+
.toolbar-search { max-width: 100%; }
|
|
13621
|
+
.grid-footer { font-size: 12px; padding: 6px 8px; }
|
|
13622
|
+
.page-btn { width: 28px; height: 28px; font-size: 12px; }
|
|
13623
|
+
.header-cell { padding: 0 8px; font-size: 12px; }
|
|
13624
|
+
.grid-cell { padding: 0 8px; font-size: 12px; }
|
|
13625
|
+
}
|
|
13626
|
+
`;
|
|
13627
|
+
__decorate([
|
|
13628
|
+
n({ type: Array })
|
|
13629
|
+
], ChefUiDataGrid.prototype, "rows", void 0);
|
|
13630
|
+
__decorate([
|
|
13631
|
+
n({ type: Array })
|
|
13632
|
+
], ChefUiDataGrid.prototype, "columns", void 0);
|
|
13633
|
+
__decorate([
|
|
13634
|
+
n({ type: Number })
|
|
13635
|
+
], ChefUiDataGrid.prototype, "pageSize", void 0);
|
|
13636
|
+
__decorate([
|
|
13637
|
+
n({ type: Array })
|
|
13638
|
+
], ChefUiDataGrid.prototype, "pageSizeOptions", void 0);
|
|
13639
|
+
__decorate([
|
|
13640
|
+
n({ type: Boolean })
|
|
13641
|
+
], ChefUiDataGrid.prototype, "checkboxSelection", void 0);
|
|
13642
|
+
__decorate([
|
|
13643
|
+
n({ type: Boolean })
|
|
13644
|
+
], ChefUiDataGrid.prototype, "sortable", void 0);
|
|
13645
|
+
__decorate([
|
|
13646
|
+
n({ type: Boolean })
|
|
13647
|
+
], ChefUiDataGrid.prototype, "filterable", void 0);
|
|
13648
|
+
__decorate([
|
|
13649
|
+
n({ type: Boolean })
|
|
13650
|
+
], ChefUiDataGrid.prototype, "editable", void 0);
|
|
13651
|
+
__decorate([
|
|
13652
|
+
n({ type: Boolean })
|
|
13653
|
+
], ChefUiDataGrid.prototype, "resizableColumns", void 0);
|
|
13654
|
+
__decorate([
|
|
13655
|
+
n({ type: Boolean })
|
|
13656
|
+
], ChefUiDataGrid.prototype, "reorderableColumns", void 0);
|
|
13657
|
+
__decorate([
|
|
13658
|
+
n({ type: Boolean })
|
|
13659
|
+
], ChefUiDataGrid.prototype, "showToolbar", void 0);
|
|
13660
|
+
__decorate([
|
|
13661
|
+
n({ type: String })
|
|
13662
|
+
], ChefUiDataGrid.prototype, "density", void 0);
|
|
13663
|
+
__decorate([
|
|
13664
|
+
n({ type: Boolean })
|
|
13665
|
+
], ChefUiDataGrid.prototype, "loading", void 0);
|
|
13666
|
+
__decorate([
|
|
13667
|
+
n({ type: Boolean })
|
|
13668
|
+
], ChefUiDataGrid.prototype, "autoHeight", void 0);
|
|
13669
|
+
__decorate([
|
|
13670
|
+
n({ type: Number })
|
|
13671
|
+
], ChefUiDataGrid.prototype, "rowHeight", void 0);
|
|
13672
|
+
__decorate([
|
|
13673
|
+
n({ type: Number })
|
|
13674
|
+
], ChefUiDataGrid.prototype, "headerHeight", void 0);
|
|
13675
|
+
__decorate([
|
|
13676
|
+
n({ type: Boolean })
|
|
13677
|
+
], ChefUiDataGrid.prototype, "disableVirtualization", void 0);
|
|
13678
|
+
__decorate([
|
|
13679
|
+
n({ type: Boolean })
|
|
13680
|
+
], ChefUiDataGrid.prototype, "treeData", void 0);
|
|
13681
|
+
__decorate([
|
|
13682
|
+
n({ type: Boolean })
|
|
13683
|
+
], ChefUiDataGrid.prototype, "clipboardEnabled", void 0);
|
|
13684
|
+
__decorate([
|
|
13685
|
+
n({ type: Object })
|
|
13686
|
+
], ChefUiDataGrid.prototype, "pinnedColumns", void 0);
|
|
13687
|
+
__decorate([
|
|
13688
|
+
n({ type: Array })
|
|
13689
|
+
], ChefUiDataGrid.prototype, "rowGroupingModel", void 0);
|
|
13690
|
+
__decorate([
|
|
13691
|
+
n({ type: Object })
|
|
13692
|
+
], ChefUiDataGrid.prototype, "aggregationModel", void 0);
|
|
13693
|
+
__decorate([
|
|
13694
|
+
n({ type: Object })
|
|
13695
|
+
], ChefUiDataGrid.prototype, "exportOptions", void 0);
|
|
13696
|
+
__decorate([
|
|
13697
|
+
n({ type: String })
|
|
13698
|
+
], ChefUiDataGrid.prototype, "noRowsMessage", void 0);
|
|
13699
|
+
__decorate([
|
|
13700
|
+
n({ type: String })
|
|
13701
|
+
], ChefUiDataGrid.prototype, "loadingMessage", void 0);
|
|
13702
|
+
__decorate([
|
|
13703
|
+
r()
|
|
13704
|
+
], ChefUiDataGrid.prototype, "_currentPage", void 0);
|
|
13705
|
+
__decorate([
|
|
13706
|
+
r()
|
|
13707
|
+
], ChefUiDataGrid.prototype, "_sortModel", void 0);
|
|
13708
|
+
__decorate([
|
|
13709
|
+
r()
|
|
13710
|
+
], ChefUiDataGrid.prototype, "_filterModel", void 0);
|
|
13711
|
+
__decorate([
|
|
13712
|
+
r()
|
|
13713
|
+
], ChefUiDataGrid.prototype, "_selectedRows", void 0);
|
|
13714
|
+
__decorate([
|
|
13715
|
+
r()
|
|
13716
|
+
], ChefUiDataGrid.prototype, "_editingCell", void 0);
|
|
13717
|
+
__decorate([
|
|
13718
|
+
r()
|
|
13719
|
+
], ChefUiDataGrid.prototype, "_editValue", void 0);
|
|
13720
|
+
__decorate([
|
|
13721
|
+
r()
|
|
13722
|
+
], ChefUiDataGrid.prototype, "_columnWidths", void 0);
|
|
13723
|
+
__decorate([
|
|
13724
|
+
r()
|
|
13725
|
+
], ChefUiDataGrid.prototype, "_columnOrder", void 0);
|
|
13726
|
+
__decorate([
|
|
13727
|
+
r()
|
|
13728
|
+
], ChefUiDataGrid.prototype, "_hiddenColumns", void 0);
|
|
13729
|
+
__decorate([
|
|
13730
|
+
r()
|
|
13731
|
+
], ChefUiDataGrid.prototype, "_expandedRows", void 0);
|
|
13732
|
+
__decorate([
|
|
13733
|
+
r()
|
|
13734
|
+
], ChefUiDataGrid.prototype, "_expandedGroups", void 0);
|
|
13735
|
+
__decorate([
|
|
13736
|
+
r()
|
|
13737
|
+
], ChefUiDataGrid.prototype, "_scrollTop", void 0);
|
|
13738
|
+
__decorate([
|
|
13739
|
+
r()
|
|
13740
|
+
], ChefUiDataGrid.prototype, "_containerHeight", void 0);
|
|
13741
|
+
__decorate([
|
|
13742
|
+
r()
|
|
13743
|
+
], ChefUiDataGrid.prototype, "_activeFilterColumn", void 0);
|
|
13744
|
+
__decorate([
|
|
13745
|
+
r()
|
|
13746
|
+
], ChefUiDataGrid.prototype, "_filterPopoverField", void 0);
|
|
13747
|
+
__decorate([
|
|
13748
|
+
r()
|
|
13749
|
+
], ChefUiDataGrid.prototype, "_filterPopoverX", void 0);
|
|
13750
|
+
__decorate([
|
|
13751
|
+
r()
|
|
13752
|
+
], ChefUiDataGrid.prototype, "_filterPopoverY", void 0);
|
|
13753
|
+
__decorate([
|
|
13754
|
+
r()
|
|
13755
|
+
], ChefUiDataGrid.prototype, "_columnMenuField", void 0);
|
|
13756
|
+
__decorate([
|
|
13757
|
+
r()
|
|
13758
|
+
], ChefUiDataGrid.prototype, "_columnMenuX", void 0);
|
|
13759
|
+
__decorate([
|
|
13760
|
+
r()
|
|
13761
|
+
], ChefUiDataGrid.prototype, "_columnMenuY", void 0);
|
|
13762
|
+
__decorate([
|
|
13763
|
+
r()
|
|
13764
|
+
], ChefUiDataGrid.prototype, "_densityMenuOpen", void 0);
|
|
13765
|
+
__decorate([
|
|
13766
|
+
r()
|
|
13767
|
+
], ChefUiDataGrid.prototype, "_columnsMenuOpen", void 0);
|
|
13768
|
+
__decorate([
|
|
13769
|
+
r()
|
|
13770
|
+
], ChefUiDataGrid.prototype, "_exportMenuOpen", void 0);
|
|
13771
|
+
__decorate([
|
|
13772
|
+
r()
|
|
13773
|
+
], ChefUiDataGrid.prototype, "_contextMenuField", void 0);
|
|
13774
|
+
__decorate([
|
|
13775
|
+
e('.grid-body')
|
|
13776
|
+
], ChefUiDataGrid.prototype, "_bodyEl", void 0);
|
|
13777
|
+
ChefUiDataGrid = __decorate([
|
|
13778
|
+
t('chef-ui-data-grid')
|
|
13779
|
+
], ChefUiDataGrid);
|
|
13780
|
+
|
|
13781
|
+
export { ChefButton, ChefIcon, ChefUILicense, ChefUiAccordion, ChefUiAccordionItem, ChefUiActionButton, ChefUiAlert, ChefUiAppBar, ChefUiAvatar, ChefUiAvatarGroup, ChefUiBadge, ChefUiBreadcrumbItem, ChefUiBreadcrumbs, ChefUiCalendar, ChefUiCard, ChefUiCardHeader, ChefUiCardMedia, ChefUiCheckbox, ChefUiChip, ChefUiCircularProgress, ChefUiDataGrid, ChefUiDialog, ChefUiDivider, ChefUiDrawer, ChefUiLinearProgress, ChefUiList, ChefUiListItem, ChefUiListSubheader, ChefUiMenu, ChefUiMenuDivider, ChefUiMenuItem, ChefUiPagination, ChefUiRadio, ChefUiRadioGroup, ChefUiRating, ChefUiReaction, ChefUiSelect, ChefUiSkeleton, ChefUiSlider, ChefUiSnackbar, ChefUiStepper, ChefUiSwitch, ChefUiTab, ChefUiTabPanel, ChefUiTabs, ChefUiTextField, ChefUiToolbar, ChefUiTooltip, DENSITY_HEADER_HEIGHT, DENSITY_ROW_HEIGHT, FILTER_OPERATORS, IconResolver, generateLicenseToken, iconResolver };
|
|
11382
13782
|
//# sourceMappingURL=index.js.map
|