@dile/ui 3.9.4 → 3.11.0
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '@dile/iconlib/dile-iconlib.js';
|
|
2
3
|
|
|
3
4
|
export class DileCard extends LitElement {
|
|
4
5
|
static styles = [
|
|
@@ -21,6 +22,18 @@ export class DileCard extends LitElement {
|
|
|
21
22
|
background-color: var(--dile-card-grey-background-color, #f4f4f4);
|
|
22
23
|
color: var(--dile-card-grey-text-color, #303030);
|
|
23
24
|
}
|
|
25
|
+
.card-icon {
|
|
26
|
+
display: flex;
|
|
27
|
+
padding-right: var(--dile-card-padding-x, 1rem);
|
|
28
|
+
padding-left: var(--dile-card-padding-x, 1rem);
|
|
29
|
+
padding-top: var(--dile-card-padding-y, 1rem);
|
|
30
|
+
--dile-icon-color: var(--dile-card-icon-color, var(--dile-on-background-color, #888));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
:host([grey]) .card-icon {
|
|
34
|
+
--dile-icon-color: var(--dile-card-icon-color, var(--dile-card-grey-text-color, #303030));
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
.card-title, main, footer {
|
|
25
38
|
padding-right: var(--dile-card-padding-x, 1rem);
|
|
26
39
|
padding-left: var(--dile-card-padding-x, 1rem);
|
|
@@ -33,6 +46,9 @@ export class DileCard extends LitElement {
|
|
|
33
46
|
margin: 0;
|
|
34
47
|
margin-bottom: var(--dile-card-title-margin-bottom, 0);
|
|
35
48
|
}
|
|
49
|
+
.card-title-with-icon {
|
|
50
|
+
padding-top: var(--dile-card-title-padding-top-with-icon, 0.75rem);
|
|
51
|
+
}
|
|
36
52
|
main {
|
|
37
53
|
padding-top: var(--dile-card-padding-y, 1rem);
|
|
38
54
|
padding-bottom: var(--dile-card-padding-y, 1rem);
|
|
@@ -72,7 +88,8 @@ export class DileCard extends LitElement {
|
|
|
72
88
|
static get properties() {
|
|
73
89
|
return {
|
|
74
90
|
title: { type: String },
|
|
75
|
-
titleLevel: { type: Number }
|
|
91
|
+
titleLevel: { type: Number },
|
|
92
|
+
icon: { type: String }
|
|
76
93
|
};
|
|
77
94
|
}
|
|
78
95
|
|
|
@@ -83,6 +100,7 @@ export class DileCard extends LitElement {
|
|
|
83
100
|
|
|
84
101
|
render() {
|
|
85
102
|
return html`
|
|
103
|
+
${this.iconTemplate}
|
|
86
104
|
${this.titleTemplate}
|
|
87
105
|
<main>
|
|
88
106
|
<slot></slot>
|
|
@@ -91,15 +109,24 @@ export class DileCard extends LitElement {
|
|
|
91
109
|
`;
|
|
92
110
|
}
|
|
93
111
|
|
|
112
|
+
get iconTemplate() {
|
|
113
|
+
return this.icon
|
|
114
|
+
? html`<div class="card-icon"><dile-iconlib icon="${this.icon}"></dile-iconlib></div>`
|
|
115
|
+
: '';
|
|
116
|
+
}
|
|
117
|
+
|
|
94
118
|
get titleTemplate() {
|
|
95
119
|
// Allow consumers to provide a custom title slot (e.g. an h2/h3) to control heading semantics
|
|
96
120
|
if (this.hasSlot('title')) {
|
|
97
|
-
|
|
121
|
+
const titleClass = this.icon ? 'card-title card-title-with-icon' : 'card-title';
|
|
122
|
+
return html`<div class="${titleClass}"><slot name="title"></slot></div>`;
|
|
98
123
|
}
|
|
99
124
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
if (this.title) {
|
|
126
|
+
const titleClass = this.icon ? 'card-title card-title-with-icon' : 'card-title';
|
|
127
|
+
return html`<div class="${titleClass}" role="heading" aria-level="${this.titleLevel}">${this.title}</div>`;
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
103
130
|
}
|
|
104
131
|
|
|
105
132
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
import './data-grid.js';
|
|
4
|
+
|
|
5
|
+
describe('dile-data-grid', () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
document.body.innerHTML = '';
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
async function createDataGrid(props = {}) {
|
|
11
|
+
const el = document.createElement('dile-data-grid');
|
|
12
|
+
Object.assign(el, props);
|
|
13
|
+
document.body.appendChild(el);
|
|
14
|
+
await el.updateComplete;
|
|
15
|
+
return el;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sampleColumns = [
|
|
19
|
+
{ field: 'id', header: 'ID', sortable: true, width: '80px', align: 'center' },
|
|
20
|
+
{ field: 'name', header: 'Name', sortable: true },
|
|
21
|
+
{ field: 'age', header: 'Age', sortable: true, align: 'right' },
|
|
22
|
+
{
|
|
23
|
+
header: 'Custom',
|
|
24
|
+
render: (row) => html`<span class="custom-badge">${row.name} (${row.age})</span>`,
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const sampleItems = [
|
|
29
|
+
{ id: 1, name: 'Carlos', age: 30 },
|
|
30
|
+
{ id: 2, name: 'Ana', age: 25 },
|
|
31
|
+
{ id: 3, name: 'Beatriz', age: 35 },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
it('renders default empty message when items is empty', async () => {
|
|
35
|
+
const el = await createDataGrid({ columns: sampleColumns, items: [] });
|
|
36
|
+
const emptyCell = el.shadowRoot.querySelector('.empty-cell');
|
|
37
|
+
expect(emptyCell).toBeTruthy();
|
|
38
|
+
expect(emptyCell.textContent.trim()).toBe('No data available');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('renders customized empty message', async () => {
|
|
42
|
+
const el = await createDataGrid({
|
|
43
|
+
columns: sampleColumns,
|
|
44
|
+
items: [],
|
|
45
|
+
emptyMessage: 'Nothing found',
|
|
46
|
+
});
|
|
47
|
+
const emptyCell = el.shadowRoot.querySelector('.empty-cell');
|
|
48
|
+
expect(emptyCell.textContent.trim()).toBe('Nothing found');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders column headers and data rows correctly', async () => {
|
|
52
|
+
const el = await createDataGrid({
|
|
53
|
+
columns: sampleColumns,
|
|
54
|
+
items: sampleItems,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const headers = el.shadowRoot.querySelectorAll('th');
|
|
58
|
+
expect(headers.length).toBe(4);
|
|
59
|
+
expect(headers[0].textContent).toContain('ID');
|
|
60
|
+
expect(headers[1].textContent).toContain('Name');
|
|
61
|
+
expect(headers[2].textContent).toContain('Age');
|
|
62
|
+
expect(headers[3].textContent).toContain('Custom');
|
|
63
|
+
|
|
64
|
+
const rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
65
|
+
expect(rows.length).toBe(3);
|
|
66
|
+
|
|
67
|
+
const firstRowCells = rows[0].querySelectorAll('td');
|
|
68
|
+
expect(firstRowCells[0].textContent.trim()).toBe('1');
|
|
69
|
+
expect(firstRowCells[1].textContent.trim()).toBe('Carlos');
|
|
70
|
+
expect(firstRowCells[2].textContent.trim()).toBe('30');
|
|
71
|
+
expect(firstRowCells[3].querySelector('.custom-badge')).toBeTruthy();
|
|
72
|
+
expect(firstRowCells[3].textContent.trim()).toBe('Carlos (30)');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('sorts strings ascending and descending on client sortMode', async () => {
|
|
76
|
+
const el = await createDataGrid({
|
|
77
|
+
columns: sampleColumns,
|
|
78
|
+
items: sampleItems,
|
|
79
|
+
sortMode: 'client',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const nameHeader = el.shadowRoot.querySelectorAll('th')[1];
|
|
83
|
+
|
|
84
|
+
// Click 1: sort asc by name
|
|
85
|
+
nameHeader.click();
|
|
86
|
+
await el.updateComplete;
|
|
87
|
+
|
|
88
|
+
let rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
89
|
+
expect(rows[0].querySelectorAll('td')[1].textContent.trim()).toBe('Ana');
|
|
90
|
+
expect(rows[1].querySelectorAll('td')[1].textContent.trim()).toBe('Beatriz');
|
|
91
|
+
expect(rows[2].querySelectorAll('td')[1].textContent.trim()).toBe('Carlos');
|
|
92
|
+
|
|
93
|
+
// Click 2: sort desc by name
|
|
94
|
+
nameHeader.click();
|
|
95
|
+
await el.updateComplete;
|
|
96
|
+
|
|
97
|
+
rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
98
|
+
expect(rows[0].querySelectorAll('td')[1].textContent.trim()).toBe('Carlos');
|
|
99
|
+
expect(rows[1].querySelectorAll('td')[1].textContent.trim()).toBe('Beatriz');
|
|
100
|
+
expect(rows[2].querySelectorAll('td')[1].textContent.trim()).toBe('Ana');
|
|
101
|
+
|
|
102
|
+
// Click 3: reset sort
|
|
103
|
+
nameHeader.click();
|
|
104
|
+
await el.updateComplete;
|
|
105
|
+
|
|
106
|
+
rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
107
|
+
expect(rows[0].querySelectorAll('td')[1].textContent.trim()).toBe('Carlos');
|
|
108
|
+
expect(rows[1].querySelectorAll('td')[1].textContent.trim()).toBe('Ana');
|
|
109
|
+
expect(rows[2].querySelectorAll('td')[1].textContent.trim()).toBe('Beatriz');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('sorts numbers properly in client mode', async () => {
|
|
113
|
+
const el = await createDataGrid({
|
|
114
|
+
columns: sampleColumns,
|
|
115
|
+
items: sampleItems,
|
|
116
|
+
sortMode: 'client',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const ageHeader = el.shadowRoot.querySelectorAll('th')[2];
|
|
120
|
+
|
|
121
|
+
// Click: sort asc by age
|
|
122
|
+
ageHeader.click();
|
|
123
|
+
await el.updateComplete;
|
|
124
|
+
|
|
125
|
+
let rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
126
|
+
expect(rows[0].querySelectorAll('td')[2].textContent.trim()).toBe('25');
|
|
127
|
+
expect(rows[1].querySelectorAll('td')[2].textContent.trim()).toBe('30');
|
|
128
|
+
expect(rows[2].querySelectorAll('td')[2].textContent.trim()).toBe('35');
|
|
129
|
+
|
|
130
|
+
// Click: sort desc by age
|
|
131
|
+
ageHeader.click();
|
|
132
|
+
await el.updateComplete;
|
|
133
|
+
|
|
134
|
+
rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
135
|
+
expect(rows[0].querySelectorAll('td')[2].textContent.trim()).toBe('35');
|
|
136
|
+
expect(rows[1].querySelectorAll('td')[2].textContent.trim()).toBe('30');
|
|
137
|
+
expect(rows[2].querySelectorAll('td')[2].textContent.trim()).toBe('25');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('supports custom column comparators', async () => {
|
|
141
|
+
const customColumns = [
|
|
142
|
+
{
|
|
143
|
+
field: 'date',
|
|
144
|
+
header: 'Date',
|
|
145
|
+
sortable: true,
|
|
146
|
+
comparator: (valA, valB) => new Date(valA).getTime() - new Date(valB).getTime(),
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
const items = [
|
|
150
|
+
{ date: '2026-05-10' },
|
|
151
|
+
{ date: '2025-01-01' },
|
|
152
|
+
{ date: '2026-01-15' },
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const el = await createDataGrid({
|
|
156
|
+
columns: customColumns,
|
|
157
|
+
items,
|
|
158
|
+
sortMode: 'client',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const dateHeader = el.shadowRoot.querySelector('th');
|
|
162
|
+
dateHeader.click();
|
|
163
|
+
await el.updateComplete;
|
|
164
|
+
|
|
165
|
+
let rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
166
|
+
expect(rows[0].querySelector('td').textContent.trim()).toBe('2025-01-01');
|
|
167
|
+
expect(rows[1].querySelector('td').textContent.trim()).toBe('2026-01-15');
|
|
168
|
+
expect(rows[2].querySelector('td').textContent.trim()).toBe('2026-05-10');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('dispatches dile-data-grid-sort event on header click', async () => {
|
|
172
|
+
const el = await createDataGrid({
|
|
173
|
+
columns: sampleColumns,
|
|
174
|
+
items: sampleItems,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
let sortEventDetail = null;
|
|
178
|
+
el.addEventListener('dile-data-grid-sort', (e) => {
|
|
179
|
+
sortEventDetail = e.detail;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const nameHeader = el.shadowRoot.querySelectorAll('th')[1];
|
|
183
|
+
nameHeader.click();
|
|
184
|
+
|
|
185
|
+
expect(sortEventDetail).toBeTruthy();
|
|
186
|
+
expect(sortEventDetail.field).toBe('name');
|
|
187
|
+
expect(sortEventDetail.direction).toBe('asc');
|
|
188
|
+
expect(sortEventDetail.column.field).toBe('name');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('dispatches dile-data-grid-row-click event on row click', async () => {
|
|
192
|
+
const el = await createDataGrid({
|
|
193
|
+
columns: sampleColumns,
|
|
194
|
+
items: sampleItems,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
let rowClickDetail = null;
|
|
198
|
+
el.addEventListener('dile-data-grid-row-click', (e) => {
|
|
199
|
+
rowClickDetail = e.detail;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const secondRow = el.shadowRoot.querySelectorAll('tbody tr')[1];
|
|
203
|
+
secondRow.click();
|
|
204
|
+
|
|
205
|
+
expect(rowClickDetail).toBeTruthy();
|
|
206
|
+
expect(rowClickDetail.row.id).toBe(2);
|
|
207
|
+
expect(rowClickDetail.row.name).toBe('Ana');
|
|
208
|
+
expect(rowClickDetail.index).toBe(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('does not re-order displayed items internally when sortMode is external', async () => {
|
|
212
|
+
const el = await createDataGrid({
|
|
213
|
+
columns: sampleColumns,
|
|
214
|
+
items: sampleItems,
|
|
215
|
+
sortMode: 'external',
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const nameHeader = el.shadowRoot.querySelectorAll('th')[1];
|
|
219
|
+
nameHeader.click();
|
|
220
|
+
await el.updateComplete;
|
|
221
|
+
|
|
222
|
+
// Items order should remain unchanged
|
|
223
|
+
const rows = el.shadowRoot.querySelectorAll('tbody tr');
|
|
224
|
+
expect(rows[0].querySelectorAll('td')[1].textContent.trim()).toBe('Carlos');
|
|
225
|
+
expect(rows[1].querySelectorAll('td')[1].textContent.trim()).toBe('Ana');
|
|
226
|
+
expect(rows[2].querySelectorAll('td')[1].textContent.trim()).toBe('Beatriz');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('sets data-label attribute for responsive card mode', async () => {
|
|
230
|
+
const el = await createDataGrid({
|
|
231
|
+
columns: [
|
|
232
|
+
{ field: 'name', header: 'Full Name' },
|
|
233
|
+
{ field: 'role', header: 'Role', hideCardLabel: true },
|
|
234
|
+
{ field: 'notes', header: 'Notes', hideOnCard: true },
|
|
235
|
+
],
|
|
236
|
+
items: [{ name: 'Elena', role: 'Dev', notes: 'Secret' }],
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const cells = el.shadowRoot.querySelectorAll('tbody tr td');
|
|
240
|
+
expect(cells[0].getAttribute('data-label')).toBe('Full Name');
|
|
241
|
+
expect(cells[1].getAttribute('data-label')).toBe('');
|
|
242
|
+
expect(cells[1].classList.contains('no-card-label')).toBe(true);
|
|
243
|
+
expect(cells[2].classList.contains('hide-on-card')).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('reflects responsiveMode attribute', async () => {
|
|
247
|
+
const el = await createDataGrid({
|
|
248
|
+
columns: sampleColumns,
|
|
249
|
+
items: sampleItems,
|
|
250
|
+
responsiveMode: 'cards',
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(el.getAttribute('responsive-mode')).toBe('cards');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('applies sticky-left class when stickyFirstColumn is true or column.sticky is set', async () => {
|
|
257
|
+
const el = await createDataGrid({
|
|
258
|
+
columns: [
|
|
259
|
+
{ field: 'name', header: 'Name', sticky: true },
|
|
260
|
+
{ field: 'city', header: 'City' },
|
|
261
|
+
{ field: 'action', header: 'Action', sticky: 'right' },
|
|
262
|
+
],
|
|
263
|
+
items: [{ name: 'Carlos', city: 'Madrid', action: 'Edit' }],
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const ths = el.shadowRoot.querySelectorAll('th');
|
|
267
|
+
const tds = el.shadowRoot.querySelectorAll('tbody tr td');
|
|
268
|
+
|
|
269
|
+
expect(ths[0].classList.contains('sticky-left')).toBe(true);
|
|
270
|
+
expect(tds[0].classList.contains('sticky-left')).toBe(true);
|
|
271
|
+
|
|
272
|
+
expect(ths[1].classList.contains('sticky-left')).toBe(false);
|
|
273
|
+
expect(tds[1].classList.contains('sticky-left')).toBe(false);
|
|
274
|
+
|
|
275
|
+
expect(ths[2].classList.contains('sticky-right')).toBe(true);
|
|
276
|
+
expect(tds[2].classList.contains('sticky-right')).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('applies sticky-left to the first column when stickyFirstColumn property is set', async () => {
|
|
280
|
+
const el = await createDataGrid({
|
|
281
|
+
columns: sampleColumns,
|
|
282
|
+
items: sampleItems,
|
|
283
|
+
stickyFirstColumn: true,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const ths = el.shadowRoot.querySelectorAll('th');
|
|
287
|
+
const tds = el.shadowRoot.querySelectorAll('tbody tr td');
|
|
288
|
+
|
|
289
|
+
expect(ths[0].classList.contains('sticky-left')).toBe(true);
|
|
290
|
+
expect(tds[0].classList.contains('sticky-left')).toBe(true);
|
|
291
|
+
expect(ths[1].classList.contains('sticky-left')).toBe(false);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DileDataGrid } from './src/DileDataGrid.js';
|
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
import { html, css, LitElement } from 'lit';
|
|
2
|
+
import { arrowDropUpIcon, arrowDropDownIcon } from '@dile/icons';
|
|
3
|
+
|
|
4
|
+
export class DileDataGrid extends LitElement {
|
|
5
|
+
static get properties() {
|
|
6
|
+
return {
|
|
7
|
+
/** Array of row objects to display in the grid */
|
|
8
|
+
items: { type: Array },
|
|
9
|
+
|
|
10
|
+
/** Column configurations: { field, header, sortable, width, align, render, comparator, hideOnCard, hideCardLabel, sticky } */
|
|
11
|
+
columns: { type: Array },
|
|
12
|
+
|
|
13
|
+
/** Currently sorted column field */
|
|
14
|
+
sortField: { type: String },
|
|
15
|
+
|
|
16
|
+
/** Sort direction: 'asc', 'desc', or '' */
|
|
17
|
+
sortDirection: { type: String },
|
|
18
|
+
|
|
19
|
+
/** Mode of sorting: 'client' (sorts data internally) or 'external' (only emits event) */
|
|
20
|
+
sortMode: { type: String },
|
|
21
|
+
|
|
22
|
+
/** Responsive mode: 'auto' (container query card switch), 'cards' (always card), or 'scroll' (table scroll) */
|
|
23
|
+
responsiveMode: { type: String, attribute: 'responsive-mode', reflect: true },
|
|
24
|
+
|
|
25
|
+
/** Makes the first column sticky to the left */
|
|
26
|
+
stickyFirstColumn: { type: Boolean, attribute: 'sticky-first-column' },
|
|
27
|
+
|
|
28
|
+
/** Message to display when there are no items */
|
|
29
|
+
emptyMessage: { type: String },
|
|
30
|
+
|
|
31
|
+
/** Unique property name for identifying rows */
|
|
32
|
+
rowIdField: { type: String },
|
|
33
|
+
|
|
34
|
+
/** Enables striped rows styling */
|
|
35
|
+
striped: { type: Boolean },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
super();
|
|
41
|
+
this.items = [];
|
|
42
|
+
this.columns = [];
|
|
43
|
+
this.sortField = '';
|
|
44
|
+
this.sortDirection = '';
|
|
45
|
+
this.sortMode = 'client';
|
|
46
|
+
this.responsiveMode = 'auto';
|
|
47
|
+
this.stickyFirstColumn = false;
|
|
48
|
+
this.emptyMessage = 'No data available';
|
|
49
|
+
this.rowIdField = 'id';
|
|
50
|
+
this.striped = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static get styles() {
|
|
54
|
+
return css`
|
|
55
|
+
:host {
|
|
56
|
+
display: block;
|
|
57
|
+
width: 100%;
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
container-type: inline-size;
|
|
60
|
+
font-family: var(--dile-data-grid-font-family, inherit);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
*, *::before, *::after {
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.table-container {
|
|
68
|
+
width: 100%;
|
|
69
|
+
overflow-x: auto;
|
|
70
|
+
background-color: var(--dile-data-grid-background-color, #ffffff);
|
|
71
|
+
border: var(--dile-data-grid-border, 1px solid #e2e8f0);
|
|
72
|
+
border-radius: var(--dile-data-grid-border-radius, 6px);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
table {
|
|
76
|
+
width: 100%;
|
|
77
|
+
border-collapse: collapse;
|
|
78
|
+
text-align: left;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
thead {
|
|
82
|
+
background-color: var(--dile-data-grid-header-background-color, #f8fafc);
|
|
83
|
+
border-bottom: var(--dile-data-grid-header-border-bottom, 2px solid #e2e8f0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
th {
|
|
87
|
+
padding: var(--dile-data-grid-header-padding, 0.75rem 1rem);
|
|
88
|
+
color: var(--dile-data-grid-header-color, #334155);
|
|
89
|
+
font-weight: var(--dile-data-grid-header-font-weight, 600);
|
|
90
|
+
font-size: var(--dile-data-grid-header-font-size, 0.875rem);
|
|
91
|
+
user-select: none;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
th.sortable {
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
transition: background-color 0.15s ease-in-out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
th.sortable:hover {
|
|
101
|
+
background-color: var(--dile-data-grid-header-hover-background-color, #f1f5f9);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.header-content {
|
|
105
|
+
display: inline-flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 0.35rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.sort-icon {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
width: 1.1rem;
|
|
115
|
+
height: 1.1rem;
|
|
116
|
+
color: var(--dile-data-grid-sort-icon-color, #94a3b8);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.sort-icon.active {
|
|
120
|
+
color: var(--dile-data-grid-sort-icon-active-color, #2563eb);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.sort-icon svg {
|
|
124
|
+
width: 100%;
|
|
125
|
+
height: 100%;
|
|
126
|
+
fill: currentColor;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
tbody tr {
|
|
130
|
+
border-bottom: var(--dile-data-grid-row-border-bottom, 1px solid #f1f5f9);
|
|
131
|
+
transition: background-color 0.15s ease-in-out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
tbody tr:last-child {
|
|
135
|
+
border-bottom: none;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
tbody tr:hover {
|
|
139
|
+
background-color: var(--dile-data-grid-row-hover-background-color, #f8fafc);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
:host([striped]) tbody tr:nth-child(even),
|
|
143
|
+
.striped tbody tr:nth-child(even) {
|
|
144
|
+
background-color: var(--dile-data-grid-row-striped-background-color, #fcfdfe);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
:host([striped]) tbody tr:nth-child(even):hover,
|
|
148
|
+
.striped tbody tr:nth-child(even):hover {
|
|
149
|
+
background-color: var(--dile-data-grid-row-hover-background-color, #f8fafc);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
td {
|
|
153
|
+
padding: var(--dile-data-grid-row-padding, 0.75rem 1rem);
|
|
154
|
+
color: var(--dile-data-grid-row-color, #1e293b);
|
|
155
|
+
font-size: var(--dile-data-grid-row-font-size, 0.875rem);
|
|
156
|
+
vertical-align: middle;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.align-left {
|
|
160
|
+
text-align: left;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.align-center {
|
|
164
|
+
text-align: center;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.align-right {
|
|
168
|
+
text-align: right;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.empty-cell {
|
|
172
|
+
text-align: center;
|
|
173
|
+
padding: var(--dile-data-grid-empty-padding, 2.5rem 1rem);
|
|
174
|
+
color: var(--dile-data-grid-empty-color, #64748b);
|
|
175
|
+
font-size: var(--dile-data-grid-empty-font-size, 0.875rem);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Sticky Columns */
|
|
179
|
+
th.sticky-left,
|
|
180
|
+
td.sticky-left {
|
|
181
|
+
position: sticky;
|
|
182
|
+
left: 0;
|
|
183
|
+
z-index: 2;
|
|
184
|
+
background-color: var(--dile-data-grid-sticky-background-color, var(--dile-data-grid-background-color, #ffffff));
|
|
185
|
+
box-shadow: var(--dile-data-grid-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, 0.12));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
th.sticky-left {
|
|
189
|
+
z-index: 3;
|
|
190
|
+
background-color: var(--dile-data-grid-sticky-header-background-color, var(--dile-data-grid-header-background-color, #f8fafc));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
th.sticky-right,
|
|
194
|
+
td.sticky-right {
|
|
195
|
+
position: sticky;
|
|
196
|
+
right: 0;
|
|
197
|
+
z-index: 2;
|
|
198
|
+
background-color: var(--dile-data-grid-sticky-background-color, var(--dile-data-grid-background-color, #ffffff));
|
|
199
|
+
box-shadow: var(--dile-data-grid-sticky-right-shadow, -2px 0 5px -2px rgba(0, 0, 0, 0.12));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
th.sticky-right {
|
|
203
|
+
z-index: 3;
|
|
204
|
+
background-color: var(--dile-data-grid-sticky-header-background-color, var(--dile-data-grid-header-background-color, #f8fafc));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
:host([striped]) tbody tr:nth-child(even) td.sticky-left,
|
|
208
|
+
.striped tbody tr:nth-child(even) td.sticky-left,
|
|
209
|
+
:host([striped]) tbody tr:nth-child(even) td.sticky-right,
|
|
210
|
+
.striped tbody tr:nth-child(even) td.sticky-right {
|
|
211
|
+
background-color: var(--dile-data-grid-sticky-striped-background-color, var(--dile-data-grid-row-striped-background-color, #fcfdfe));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
tbody tr:hover td.sticky-left,
|
|
215
|
+
tbody tr:hover td.sticky-right {
|
|
216
|
+
background-color: var(--dile-data-grid-row-hover-background-color, #f8fafc);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Explicit Cards layout (responsive-mode="cards" / "card") */
|
|
220
|
+
:host([responsive-mode="cards"]) .table-container,
|
|
221
|
+
:host([responsive-mode="card"]) .table-container {
|
|
222
|
+
background-color: transparent;
|
|
223
|
+
border: none;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
:host([responsive-mode="cards"]) thead,
|
|
227
|
+
:host([responsive-mode="card"]) thead {
|
|
228
|
+
display: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
:host([responsive-mode="cards"]) table,
|
|
232
|
+
:host([responsive-mode="cards"]) tbody,
|
|
233
|
+
:host([responsive-mode="card"]) table,
|
|
234
|
+
:host([responsive-mode="card"]) tbody {
|
|
235
|
+
display: block;
|
|
236
|
+
width: 100%;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
:host([responsive-mode="cards"]) tbody tr,
|
|
240
|
+
:host([responsive-mode="card"]) tbody tr {
|
|
241
|
+
display: block;
|
|
242
|
+
background-color: var(--dile-data-grid-card-background-color, #ffffff);
|
|
243
|
+
border: var(--dile-data-grid-card-border, 1px solid #e2e8f0);
|
|
244
|
+
border-radius: var(--dile-data-grid-card-border-radius, 8px);
|
|
245
|
+
margin-bottom: var(--dile-data-grid-card-gap, 0.75rem);
|
|
246
|
+
padding: var(--dile-data-grid-card-padding, 0.5rem 0);
|
|
247
|
+
box-shadow: var(--dile-data-grid-card-box-shadow, 0 1px 3px rgba(0, 0, 0, 0.05));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
:host([responsive-mode="cards"]) tbody tr:last-child,
|
|
251
|
+
:host([responsive-mode="card"]) tbody tr:last-child {
|
|
252
|
+
margin-bottom: 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
:host([responsive-mode="cards"]) td,
|
|
256
|
+
:host([responsive-mode="card"]) td {
|
|
257
|
+
display: flex;
|
|
258
|
+
justify-content: space-between;
|
|
259
|
+
align-items: center;
|
|
260
|
+
padding: var(--dile-data-grid-card-cell-padding, 0.5rem 1rem);
|
|
261
|
+
border-bottom: var(--dile-data-grid-card-cell-border-bottom, 1px solid #f1f5f9);
|
|
262
|
+
text-align: right;
|
|
263
|
+
position: static;
|
|
264
|
+
box-shadow: none;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
:host([responsive-mode="cards"]) td:last-child,
|
|
268
|
+
:host([responsive-mode="card"]) td:last-child {
|
|
269
|
+
border-bottom: none;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
:host([responsive-mode="cards"]) td.hide-on-card,
|
|
273
|
+
:host([responsive-mode="card"]) td.hide-on-card {
|
|
274
|
+
display: none !important;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
:host([responsive-mode="cards"]) td::before,
|
|
278
|
+
:host([responsive-mode="card"]) td::before {
|
|
279
|
+
content: attr(data-label);
|
|
280
|
+
font-weight: var(--dile-data-grid-card-label-font-weight, 600);
|
|
281
|
+
color: var(--dile-data-grid-card-label-color, #64748b);
|
|
282
|
+
font-size: var(--dile-data-grid-card-label-font-size, 0.8rem);
|
|
283
|
+
text-align: left;
|
|
284
|
+
margin-right: 1rem;
|
|
285
|
+
flex-shrink: 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
:host([responsive-mode="cards"]) td[data-label=""]::before,
|
|
289
|
+
:host([responsive-mode="cards"]) td.no-card-label::before,
|
|
290
|
+
:host([responsive-mode="card"]) td[data-label=""]::before,
|
|
291
|
+
:host([responsive-mode="card"]) td.no-card-label::before {
|
|
292
|
+
display: none;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
:host([responsive-mode="cards"]) td[data-label=""],
|
|
296
|
+
:host([responsive-mode="cards"]) td.no-card-label,
|
|
297
|
+
:host([responsive-mode="card"]) td[data-label=""],
|
|
298
|
+
:host([responsive-mode="card"]) td.no-card-label {
|
|
299
|
+
justify-content: flex-end;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
:host([responsive-mode="cards"]) .empty-cell,
|
|
303
|
+
:host([responsive-mode="card"]) .empty-cell {
|
|
304
|
+
display: block;
|
|
305
|
+
text-align: center;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
:host([responsive-mode="cards"]) .empty-cell::before,
|
|
309
|
+
:host([responsive-mode="card"]) .empty-cell::before {
|
|
310
|
+
display: none;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* Container Query: responsive card transformation when container width <= 600px */
|
|
314
|
+
@container (max-width: 600px) {
|
|
315
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) .table-container {
|
|
316
|
+
background-color: transparent;
|
|
317
|
+
border: none;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) thead {
|
|
321
|
+
display: none;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) table,
|
|
325
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) tbody {
|
|
326
|
+
display: block;
|
|
327
|
+
width: 100%;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) tbody tr {
|
|
331
|
+
display: block;
|
|
332
|
+
background-color: var(--dile-data-grid-card-background-color, #ffffff);
|
|
333
|
+
border: var(--dile-data-grid-card-border, 1px solid #e2e8f0);
|
|
334
|
+
border-radius: var(--dile-data-grid-card-border-radius, 8px);
|
|
335
|
+
margin-bottom: var(--dile-data-grid-card-gap, 0.75rem);
|
|
336
|
+
padding: var(--dile-data-grid-card-padding, 0.5rem 0);
|
|
337
|
+
box-shadow: var(--dile-data-grid-card-box-shadow, 0 1px 3px rgba(0, 0, 0, 0.05));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) tbody tr:last-child {
|
|
341
|
+
margin-bottom: 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td {
|
|
345
|
+
display: flex;
|
|
346
|
+
justify-content: space-between;
|
|
347
|
+
align-items: center;
|
|
348
|
+
padding: var(--dile-data-grid-card-cell-padding, 0.5rem 1rem);
|
|
349
|
+
border-bottom: var(--dile-data-grid-card-cell-border-bottom, 1px solid #f1f5f9);
|
|
350
|
+
text-align: right;
|
|
351
|
+
position: static;
|
|
352
|
+
box-shadow: none;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td:last-child {
|
|
356
|
+
border-bottom: none;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td.hide-on-card {
|
|
360
|
+
display: none !important;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td::before {
|
|
364
|
+
content: attr(data-label);
|
|
365
|
+
font-weight: var(--dile-data-grid-card-label-font-weight, 600);
|
|
366
|
+
color: var(--dile-data-grid-card-label-color, #64748b);
|
|
367
|
+
font-size: var(--dile-data-grid-card-label-font-size, 0.8rem);
|
|
368
|
+
text-align: left;
|
|
369
|
+
margin-right: 1rem;
|
|
370
|
+
flex-shrink: 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td[data-label=""]::before,
|
|
374
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td.no-card-label::before {
|
|
375
|
+
display: none;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td[data-label=""],
|
|
379
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) td.no-card-label {
|
|
380
|
+
justify-content: flex-end;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) .empty-cell {
|
|
384
|
+
display: block;
|
|
385
|
+
text-align: center;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
:host(:not([responsive-mode="scroll"]):not([responsive-mode="cards"]):not([responsive-mode="card"])) .empty-cell::before {
|
|
389
|
+
display: none;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
get displayedItems() {
|
|
396
|
+
if (!this.items || !Array.isArray(this.items)) {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (this.sortMode !== 'client' || !this.sortField || !this.sortDirection) {
|
|
401
|
+
return this.items;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const column = (this.columns || []).find((col) => col.field === this.sortField);
|
|
405
|
+
const directionFactor = this.sortDirection === 'desc' ? -1 : 1;
|
|
406
|
+
|
|
407
|
+
return [...this.items].sort((a, b) => {
|
|
408
|
+
const valA = a ? a[this.sortField] : undefined;
|
|
409
|
+
const valB = b ? b[this.sortField] : undefined;
|
|
410
|
+
|
|
411
|
+
if (column && typeof column.comparator === 'function') {
|
|
412
|
+
return directionFactor * column.comparator(valA, valB, a, b);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Safe fallback comparisons
|
|
416
|
+
if (valA === undefined || valA === null) {
|
|
417
|
+
return valB === undefined || valB === null ? 0 : 1;
|
|
418
|
+
}
|
|
419
|
+
if (valB === undefined || valB === null) {
|
|
420
|
+
return -1;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (typeof valA === 'string' && typeof valB === 'string') {
|
|
424
|
+
return directionFactor * valA.localeCompare(valB, undefined, { numeric: true, sensitivity: 'base' });
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (valA < valB) return -1 * directionFactor;
|
|
428
|
+
if (valA > valB) return 1 * directionFactor;
|
|
429
|
+
return 0;
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
_isStickyLeft(column, colIndex) {
|
|
434
|
+
return column.sticky === true || column.sticky === 'left' || (this.stickyFirstColumn && colIndex === 0);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
_isStickyRight(column) {
|
|
438
|
+
return column.sticky === 'right';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
render() {
|
|
442
|
+
const items = this.displayedItems;
|
|
443
|
+
const cols = this.columns || [];
|
|
444
|
+
|
|
445
|
+
return html`
|
|
446
|
+
<div class="table-container ${this.striped ? 'striped' : ''}">
|
|
447
|
+
<table>
|
|
448
|
+
<thead>
|
|
449
|
+
<tr>
|
|
450
|
+
${cols.map((col, colIndex) => this._renderHeaderCell(col, colIndex))}
|
|
451
|
+
</tr>
|
|
452
|
+
</thead>
|
|
453
|
+
<tbody>
|
|
454
|
+
${items.length === 0
|
|
455
|
+
? html`
|
|
456
|
+
<tr class="empty-row">
|
|
457
|
+
<td class="empty-cell" colspan="${cols.length || 1}" data-label="">
|
|
458
|
+
${this.emptyMessage}
|
|
459
|
+
</td>
|
|
460
|
+
</tr>
|
|
461
|
+
`
|
|
462
|
+
: items.map((row, rowIndex) => this._renderRow(row, rowIndex, cols))}
|
|
463
|
+
</tbody>
|
|
464
|
+
</table>
|
|
465
|
+
</div>
|
|
466
|
+
`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
_renderHeaderCell(column, colIndex) {
|
|
470
|
+
const isSorted = this.sortField === column.field && !!this.sortDirection;
|
|
471
|
+
const alignClass = column.align ? `align-${column.align}` : 'align-left';
|
|
472
|
+
const sortableClass = column.sortable ? 'sortable' : '';
|
|
473
|
+
const stickyClass = this._isStickyLeft(column, colIndex)
|
|
474
|
+
? 'sticky-left'
|
|
475
|
+
: this._isStickyRight(column)
|
|
476
|
+
? 'sticky-right'
|
|
477
|
+
: '';
|
|
478
|
+
const styleWidth = column.width ? `width: ${column.width};` : '';
|
|
479
|
+
|
|
480
|
+
return html`
|
|
481
|
+
<th
|
|
482
|
+
class="${alignClass} ${sortableClass} ${stickyClass}"
|
|
483
|
+
style="${styleWidth}"
|
|
484
|
+
@click=${() => this._handleHeaderClick(column)}
|
|
485
|
+
aria-sort="${isSorted ? (this.sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}"
|
|
486
|
+
>
|
|
487
|
+
<div class="header-content">
|
|
488
|
+
<span>${column.header || column.field || ''}</span>
|
|
489
|
+
${column.sortable ? this._renderSortIcon(column) : ''}
|
|
490
|
+
</div>
|
|
491
|
+
</th>
|
|
492
|
+
`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
_renderSortIcon(column) {
|
|
496
|
+
const isSorted = this.sortField === column.field && !!this.sortDirection;
|
|
497
|
+
if (!isSorted) {
|
|
498
|
+
return html`<span class="sort-icon" aria-hidden="true"></span>`;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const icon = this.sortDirection === 'asc' ? arrowDropUpIcon : arrowDropDownIcon;
|
|
502
|
+
|
|
503
|
+
return html`
|
|
504
|
+
<span class="sort-icon active" aria-hidden="true">
|
|
505
|
+
${icon}
|
|
506
|
+
</span>
|
|
507
|
+
`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
_renderRow(row, rowIndex, columns) {
|
|
511
|
+
return html`
|
|
512
|
+
<tr @click=${(e) => this._handleRowClick(row, rowIndex, e)}>
|
|
513
|
+
${columns.map((col, colIndex) => this._renderCell(row, rowIndex, col, colIndex))}
|
|
514
|
+
</tr>
|
|
515
|
+
`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
_renderCell(row, rowIndex, column, colIndex) {
|
|
519
|
+
const alignClass = column.align ? `align-${column.align}` : 'align-left';
|
|
520
|
+
const hideOnCardClass = column.hideOnCard ? 'hide-on-card' : '';
|
|
521
|
+
const noCardLabelClass = column.hideCardLabel ? 'no-card-label' : '';
|
|
522
|
+
const stickyClass = this._isStickyLeft(column, colIndex)
|
|
523
|
+
? 'sticky-left'
|
|
524
|
+
: this._isStickyRight(column)
|
|
525
|
+
? 'sticky-right'
|
|
526
|
+
: '';
|
|
527
|
+
const label = column.hideCardLabel ? '' : (column.header || column.field || '');
|
|
528
|
+
|
|
529
|
+
let content;
|
|
530
|
+
if (typeof column.render === 'function') {
|
|
531
|
+
content = column.render(row, rowIndex, column);
|
|
532
|
+
} else if (column.field && row && row[column.field] !== undefined) {
|
|
533
|
+
content = row[column.field];
|
|
534
|
+
} else {
|
|
535
|
+
content = '';
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return html`
|
|
539
|
+
<td
|
|
540
|
+
class="${alignClass} ${hideOnCardClass} ${noCardLabelClass} ${stickyClass}"
|
|
541
|
+
data-label="${label}"
|
|
542
|
+
>
|
|
543
|
+
${content}
|
|
544
|
+
</td>
|
|
545
|
+
`;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
_handleHeaderClick(column) {
|
|
549
|
+
if (!column.sortable || !column.field) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
let nextDirection = 'asc';
|
|
554
|
+
if (this.sortField === column.field) {
|
|
555
|
+
if (this.sortDirection === 'asc') {
|
|
556
|
+
nextDirection = 'desc';
|
|
557
|
+
} else if (this.sortDirection === 'desc') {
|
|
558
|
+
nextDirection = '';
|
|
559
|
+
} else {
|
|
560
|
+
nextDirection = 'asc';
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this.sortField = nextDirection ? column.field : '';
|
|
565
|
+
this.sortDirection = nextDirection;
|
|
566
|
+
|
|
567
|
+
this.dispatchEvent(
|
|
568
|
+
new CustomEvent('dile-data-grid-sort', {
|
|
569
|
+
bubbles: true,
|
|
570
|
+
composed: true,
|
|
571
|
+
detail: {
|
|
572
|
+
field: this.sortField,
|
|
573
|
+
direction: this.sortDirection,
|
|
574
|
+
column,
|
|
575
|
+
},
|
|
576
|
+
})
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
_handleRowClick(row, index, event) {
|
|
581
|
+
this.dispatchEvent(
|
|
582
|
+
new CustomEvent('dile-data-grid-row-click', {
|
|
583
|
+
bubbles: true,
|
|
584
|
+
composed: true,
|
|
585
|
+
detail: {
|
|
586
|
+
row,
|
|
587
|
+
index,
|
|
588
|
+
event,
|
|
589
|
+
},
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.0",
|
|
4
4
|
"description": "UI Core components from dile-components.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "5ebe41acdb9345d9f416a10c7e2819ac02e054c1"
|
|
32
32
|
}
|