@dile/crud 2.6.4 → 2.7.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/components/crud/crud.spec.js +142 -0
- package/components/crud/src/DileCrud.js +23 -3
- package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-always-forces-sort-mode-to-external-regardless-of-config-1.png +0 -0
- package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-forwards-stickyFirstColumn--striped--responsiveMode-and-emptyMessage-with-sensible-defaults-1.png +0 -0
- package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-leaves-computeRowId-unset-when-config-computeItemId-is-not-a-function-1.png +0 -0
- package/components/list/crud-data-grid.js +5 -0
- package/components/list/crud-data-grid.spec.js +207 -0
- package/components/list/crud-item-actions.js +5 -0
- package/components/list/crud-item-actions.spec.js +122 -0
- package/components/list/crud-list.spec.js +319 -0
- package/components/list/index.js +4 -2
- package/components/list/src/DileCrudDataGrid.js +93 -0
- package/components/list/src/DileCrudItemActions.js +191 -0
- package/components/list/src/DileCrudList.js +62 -11
- package/components/list/src/DileCrudListItem.js +78 -124
- package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-syncSort-suppresses-exactly-one-radio-group-echo--preserving-the-synced-direction-instead-of-falling-back-to-the-option-static-default-1.png +0 -0
- package/components/ui/crud-sort-form.spec.js +132 -0
- package/components/ui/src/DileCrudSortForm.js +24 -5
- package/lib/CrudConfigBuilder.spec.js +32 -0
- package/lib/deepMerge.spec.js +45 -0
- package/lib/defaultConfig.js +4 -1
- package/package.json +3 -3
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
import { CrudConfigBuilder } from '../../lib/CrudConfigBuilder.js';
|
|
4
|
+
import './crud-list.js';
|
|
5
|
+
import './crud-data-grid.js';
|
|
6
|
+
import '@dile/ui/components/data-grid/data-grid.js';
|
|
7
|
+
|
|
8
|
+
describe('dile-crud-list grid template integration', () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
document.body.innerHTML = '';
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const sampleElements = [
|
|
14
|
+
{ id: 1, name: 'Customer A', email: 'a@example.com' },
|
|
15
|
+
{ id: 2, name: 'Customer B', email: 'b@example.com' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
it('renders default dile-crud-list-item when templates.grid is not provided', async () => {
|
|
19
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
20
|
+
templates: {
|
|
21
|
+
item: (item) => html`<span class="item-name">${item.name}</span>`,
|
|
22
|
+
},
|
|
23
|
+
}).getConfig();
|
|
24
|
+
|
|
25
|
+
const el = document.createElement('dile-crud-list');
|
|
26
|
+
el.disableLoadOnStart = true;
|
|
27
|
+
el.config = config;
|
|
28
|
+
el.elements = sampleElements;
|
|
29
|
+
el.loading = false;
|
|
30
|
+
document.body.appendChild(el);
|
|
31
|
+
await el.updateComplete;
|
|
32
|
+
|
|
33
|
+
const listItems = el.shadowRoot.querySelectorAll('dile-crud-list-item');
|
|
34
|
+
expect(listItems.length).toBe(2);
|
|
35
|
+
|
|
36
|
+
const gridContainer = el.shadowRoot.querySelector('.grid-container');
|
|
37
|
+
expect(gridContainer).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('renders generic dile-crud-data-grid when config.grid.columns is defined', async () => {
|
|
41
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
42
|
+
grid: {
|
|
43
|
+
columns: [
|
|
44
|
+
{ field: 'id', header: 'ID' },
|
|
45
|
+
{ field: 'name', header: 'Customer Name' },
|
|
46
|
+
],
|
|
47
|
+
striped: true,
|
|
48
|
+
},
|
|
49
|
+
}).getConfig();
|
|
50
|
+
|
|
51
|
+
const el = document.createElement('dile-crud-list');
|
|
52
|
+
el.disableLoadOnStart = true;
|
|
53
|
+
el.config = config;
|
|
54
|
+
el.elements = sampleElements;
|
|
55
|
+
el.sort = { sortField: 'name', sortDirection: 'desc' };
|
|
56
|
+
el.loading = false;
|
|
57
|
+
document.body.appendChild(el);
|
|
58
|
+
await el.updateComplete;
|
|
59
|
+
|
|
60
|
+
const listItems = el.shadowRoot.querySelectorAll('dile-crud-list-item');
|
|
61
|
+
expect(listItems.length).toBe(0);
|
|
62
|
+
|
|
63
|
+
const crudDataGrid = el.shadowRoot.querySelector('dile-crud-data-grid');
|
|
64
|
+
expect(crudDataGrid).toBeTruthy();
|
|
65
|
+
expect(crudDataGrid.items).toEqual(sampleElements);
|
|
66
|
+
expect(crudDataGrid.columns.length).toBe(3); // id, name + automatically injected Actions
|
|
67
|
+
expect(crudDataGrid.columns[2].header).toBe('Actions');
|
|
68
|
+
expect(crudDataGrid.sort).toEqual({ sortField: 'name', sortDirection: 'desc' });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('hides actions in traditional list items when disableListActions is true', async () => {
|
|
72
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
73
|
+
customization: {
|
|
74
|
+
disableListActions: true,
|
|
75
|
+
},
|
|
76
|
+
templates: {
|
|
77
|
+
item: (item) => html`<span class="item-name">${item.name}</span>`,
|
|
78
|
+
},
|
|
79
|
+
}).getConfig();
|
|
80
|
+
|
|
81
|
+
const el = document.createElement('dile-crud-list');
|
|
82
|
+
el.disableLoadOnStart = true;
|
|
83
|
+
el.config = config;
|
|
84
|
+
el.elements = sampleElements;
|
|
85
|
+
el.loading = false;
|
|
86
|
+
document.body.appendChild(el);
|
|
87
|
+
await el.updateComplete;
|
|
88
|
+
|
|
89
|
+
const listItems = el.shadowRoot.querySelectorAll('dile-crud-list-item');
|
|
90
|
+
expect(listItems.length).toBe(2);
|
|
91
|
+
|
|
92
|
+
for (const item of listItems) {
|
|
93
|
+
const actionsDiv = item.shadowRoot.querySelector('.actions');
|
|
94
|
+
expect(actionsDiv).toBeNull();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders custom grid template when templates.grid is configured', async () => {
|
|
99
|
+
let receivedElements = null;
|
|
100
|
+
let receivedActionIds = null;
|
|
101
|
+
let receivedSort = null;
|
|
102
|
+
|
|
103
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
104
|
+
templates: {
|
|
105
|
+
grid: (elements, actionIds, config, sort) => {
|
|
106
|
+
receivedElements = elements;
|
|
107
|
+
receivedActionIds = actionIds;
|
|
108
|
+
receivedSort = sort;
|
|
109
|
+
return html`
|
|
110
|
+
<dile-data-grid
|
|
111
|
+
.items="${elements}"
|
|
112
|
+
.selectedIds="${actionIds}"
|
|
113
|
+
selectable
|
|
114
|
+
.columns="${[
|
|
115
|
+
{ field: 'id', header: 'ID' },
|
|
116
|
+
{ field: 'name', header: 'Name' },
|
|
117
|
+
]}"
|
|
118
|
+
></dile-data-grid>
|
|
119
|
+
`;
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
}).getConfig();
|
|
123
|
+
|
|
124
|
+
const el = document.createElement('dile-crud-list');
|
|
125
|
+
el.disableLoadOnStart = true;
|
|
126
|
+
el.config = config;
|
|
127
|
+
el.elements = sampleElements;
|
|
128
|
+
el.actionIds = [2];
|
|
129
|
+
el.sort = { sortField: 'name', sortDirection: 'asc' };
|
|
130
|
+
el.loading = false;
|
|
131
|
+
document.body.appendChild(el);
|
|
132
|
+
await el.updateComplete;
|
|
133
|
+
|
|
134
|
+
const listItems = el.shadowRoot.querySelectorAll('dile-crud-list-item');
|
|
135
|
+
expect(listItems.length).toBe(0);
|
|
136
|
+
|
|
137
|
+
const gridContainer = el.shadowRoot.querySelector('.grid-container');
|
|
138
|
+
expect(gridContainer).toBeTruthy();
|
|
139
|
+
|
|
140
|
+
const dataGrid = el.shadowRoot.querySelector('dile-data-grid');
|
|
141
|
+
expect(dataGrid).toBeTruthy();
|
|
142
|
+
expect(receivedElements).toEqual(sampleElements);
|
|
143
|
+
expect(receivedActionIds).toEqual([2]);
|
|
144
|
+
expect(receivedSort).toEqual({ sortField: 'name', sortDirection: 'asc' });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('updates sort when dile-data-grid-sort is dispatched', async () => {
|
|
148
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
149
|
+
templates: {
|
|
150
|
+
grid: (elements, actionIds) => html`
|
|
151
|
+
<dile-data-grid
|
|
152
|
+
.items="${elements}"
|
|
153
|
+
sort-mode="external"
|
|
154
|
+
.columns="${[
|
|
155
|
+
{ field: 'name', header: 'Name', sortable: true },
|
|
156
|
+
]}"
|
|
157
|
+
></dile-data-grid>
|
|
158
|
+
`,
|
|
159
|
+
},
|
|
160
|
+
}).getConfig();
|
|
161
|
+
|
|
162
|
+
const el = document.createElement('dile-crud-list');
|
|
163
|
+
el.disableLoadOnStart = true;
|
|
164
|
+
el.config = config;
|
|
165
|
+
el.elements = sampleElements;
|
|
166
|
+
el.loading = false;
|
|
167
|
+
document.body.appendChild(el);
|
|
168
|
+
await el.updateComplete;
|
|
169
|
+
|
|
170
|
+
let sortApplied = null;
|
|
171
|
+
el.setSort = (sortObj) => {
|
|
172
|
+
sortApplied = sortObj;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const gridContainer = el.shadowRoot.querySelector('.grid-container');
|
|
176
|
+
gridContainer.dispatchEvent(
|
|
177
|
+
new CustomEvent('dile-data-grid-sort', {
|
|
178
|
+
bubbles: true,
|
|
179
|
+
composed: true,
|
|
180
|
+
detail: {
|
|
181
|
+
field: 'name',
|
|
182
|
+
direction: 'asc',
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(sortApplied).toEqual({
|
|
188
|
+
sortField: 'name',
|
|
189
|
+
sortDirection: 'asc',
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('updates sort when dile-data-grid-sort is dispatched from the config.grid.columns branch', async () => {
|
|
194
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
195
|
+
grid: {
|
|
196
|
+
columns: [
|
|
197
|
+
{ field: 'name', header: 'Name', sortable: true },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
}).getConfig();
|
|
201
|
+
|
|
202
|
+
const el = document.createElement('dile-crud-list');
|
|
203
|
+
el.disableLoadOnStart = true;
|
|
204
|
+
el.config = config;
|
|
205
|
+
el.elements = sampleElements;
|
|
206
|
+
el.loading = false;
|
|
207
|
+
document.body.appendChild(el);
|
|
208
|
+
await el.updateComplete;
|
|
209
|
+
|
|
210
|
+
let sortApplied = null;
|
|
211
|
+
el.setSort = (sortObj) => {
|
|
212
|
+
sortApplied = sortObj;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const gridContainer = el.shadowRoot.querySelector('.grid-container');
|
|
216
|
+
gridContainer.dispatchEvent(
|
|
217
|
+
new CustomEvent('dile-data-grid-sort', {
|
|
218
|
+
bubbles: true,
|
|
219
|
+
composed: true,
|
|
220
|
+
detail: {
|
|
221
|
+
field: 'name',
|
|
222
|
+
direction: 'desc',
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(sortApplied).toEqual({
|
|
228
|
+
sortField: 'name',
|
|
229
|
+
sortDirection: 'desc',
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('prefers templates.grid over config.grid.columns when both are configured', async () => {
|
|
234
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
235
|
+
grid: {
|
|
236
|
+
columns: [
|
|
237
|
+
{ field: 'id', header: 'ID' },
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
templates: {
|
|
241
|
+
grid: (elements, actionIds) => html`
|
|
242
|
+
<dile-data-grid
|
|
243
|
+
.items="${elements}"
|
|
244
|
+
.columns="${[{ field: 'name', header: 'Name' }]}"
|
|
245
|
+
></dile-data-grid>
|
|
246
|
+
`,
|
|
247
|
+
},
|
|
248
|
+
}).getConfig();
|
|
249
|
+
|
|
250
|
+
const el = document.createElement('dile-crud-list');
|
|
251
|
+
el.disableLoadOnStart = true;
|
|
252
|
+
el.config = config;
|
|
253
|
+
el.elements = sampleElements;
|
|
254
|
+
el.loading = false;
|
|
255
|
+
document.body.appendChild(el);
|
|
256
|
+
await el.updateComplete;
|
|
257
|
+
|
|
258
|
+
expect(el.shadowRoot.querySelector('dile-crud-data-grid')).toBeNull();
|
|
259
|
+
expect(el.shadowRoot.querySelector('dile-data-grid')).toBeTruthy();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('dispatches crud-list-sort-changed with the new sort whenever setSort is called', async () => {
|
|
263
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
264
|
+
grid: { columns: [{ field: 'name', header: 'Name', sortable: true }] },
|
|
265
|
+
}).getConfig();
|
|
266
|
+
|
|
267
|
+
const el = document.createElement('dile-crud-list');
|
|
268
|
+
el.disableLoadOnStart = true;
|
|
269
|
+
el.config = config;
|
|
270
|
+
el.elements = sampleElements;
|
|
271
|
+
el.loading = false;
|
|
272
|
+
document.body.appendChild(el);
|
|
273
|
+
await el.updateComplete;
|
|
274
|
+
|
|
275
|
+
let receivedDetail = null;
|
|
276
|
+
el.addEventListener('crud-list-sort-changed', (e) => {
|
|
277
|
+
receivedDetail = e.detail;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
el.setSort({ sortField: 'name', sortDirection: 'desc' });
|
|
281
|
+
|
|
282
|
+
expect(receivedDetail).toEqual({ sortField: 'name', sortDirection: 'desc' });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('keeps reporting the correct sort state to a freshly remounted grid after a loading round-trip', async () => {
|
|
286
|
+
// Reproduces the real scenario: setSort() sets loading=true, which unmounts
|
|
287
|
+
// elementsTemplate (and therefore <dile-crud-data-grid>/<dile-data-grid>) while
|
|
288
|
+
// the request is in flight, then getSuccess() remounts it from scratch.
|
|
289
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
290
|
+
grid: { columns: [{ field: 'name', header: 'Name', sortable: true }] },
|
|
291
|
+
}).getConfig();
|
|
292
|
+
|
|
293
|
+
const el = document.createElement('dile-crud-list');
|
|
294
|
+
el.disableLoadOnStart = true;
|
|
295
|
+
el.config = config;
|
|
296
|
+
el.elements = sampleElements;
|
|
297
|
+
el.loading = false;
|
|
298
|
+
document.body.appendChild(el);
|
|
299
|
+
await el.updateComplete;
|
|
300
|
+
|
|
301
|
+
el.elservice.setSort = () => {}; // avoid a real network round-trip
|
|
302
|
+
el.setSort({ sortField: 'name', sortDirection: 'asc' });
|
|
303
|
+
await el.updateComplete;
|
|
304
|
+
|
|
305
|
+
expect(el.shadowRoot.querySelector('.grid-container')).toBeNull(); // unmounted while loading
|
|
306
|
+
|
|
307
|
+
el.getSuccess({ detail: { elements: sampleElements, numItems: 2, paginationData: {} } });
|
|
308
|
+
await el.updateComplete;
|
|
309
|
+
|
|
310
|
+
const freshDataGrid = el.shadowRoot.querySelector('dile-crud-data-grid');
|
|
311
|
+
expect(freshDataGrid).toBeTruthy();
|
|
312
|
+
expect(freshDataGrid.sort).toEqual({ sortField: 'name', sortDirection: 'asc' });
|
|
313
|
+
|
|
314
|
+
const innerGrid = freshDataGrid.shadowRoot.querySelector('dile-data-grid');
|
|
315
|
+
await innerGrid.updateComplete;
|
|
316
|
+
expect(innerGrid.sortField).toBe('name');
|
|
317
|
+
expect(innerGrid.sortDirection).toBe('asc');
|
|
318
|
+
});
|
|
319
|
+
});
|
package/components/list/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { DileCrudList } from "./src/DileCrudList.js";
|
|
2
|
-
export { DileCrudListItem } from "
|
|
3
|
-
export {
|
|
2
|
+
export { DileCrudListItem } from "./src/DileCrudListItem.js";
|
|
3
|
+
export { DileCrudItemActions } from "./src/DileCrudItemActions.js";
|
|
4
|
+
export { DileCrudDataGrid } from "./src/DileCrudDataGrid.js";
|
|
5
|
+
export { DileCrudListPaginationLinks } from "./src/DileCrudListPaginationLinks.js";
|
|
4
6
|
export { DileCrudListService } from "./src/DileCrudListService.js";
|
|
5
7
|
export { DileCrudSelectAll } from "./src/DileCrudSelectAll.js";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '@dile/ui/components/data-grid/data-grid.js';
|
|
3
|
+
import { DileCrudItemActions } from './DileCrudItemActions.js';
|
|
4
|
+
import '../crud-item-actions.js';
|
|
5
|
+
|
|
6
|
+
export class DileCrudDataGrid extends LitElement {
|
|
7
|
+
static styles = [
|
|
8
|
+
css`
|
|
9
|
+
:host {
|
|
10
|
+
display: block;
|
|
11
|
+
}
|
|
12
|
+
`
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
static get properties() {
|
|
16
|
+
return {
|
|
17
|
+
items: { type: Array },
|
|
18
|
+
selectedIds: { type: Array },
|
|
19
|
+
config: { type: Object },
|
|
20
|
+
sort: { type: Object },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.items = [];
|
|
27
|
+
this.selectedIds = [];
|
|
28
|
+
this.config = null;
|
|
29
|
+
this.sort = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get columns() {
|
|
33
|
+
const configuredCols = this.config?.grid?.columns || [];
|
|
34
|
+
const cols = [...configuredCols];
|
|
35
|
+
|
|
36
|
+
const hideActions = Boolean(this.config?.grid?.hideActionsColumn);
|
|
37
|
+
|
|
38
|
+
if (!hideActions && DileCrudItemActions.hasActions(this.config)) {
|
|
39
|
+
const customActionsHeader = this.config?.grid?.actionsColumnHeader || this.config?.labels?.actionsHeader || 'Actions';
|
|
40
|
+
const customActionsWidth = this.config?.grid?.actionsColumnWidth || '110px';
|
|
41
|
+
const customActionsAlign = this.config?.grid?.actionsColumnAlign || 'right';
|
|
42
|
+
|
|
43
|
+
cols.push({
|
|
44
|
+
header: customActionsHeader,
|
|
45
|
+
align: customActionsAlign,
|
|
46
|
+
width: customActionsWidth,
|
|
47
|
+
hideCardLabel: true,
|
|
48
|
+
render: (item) => html`
|
|
49
|
+
<dile-crud-item-actions
|
|
50
|
+
.item=${item}
|
|
51
|
+
.config=${this.config}
|
|
52
|
+
></dile-crud-item-actions>
|
|
53
|
+
`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return cols;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get computeRowId() {
|
|
61
|
+
const computeItemId = this.config?.computeItemId;
|
|
62
|
+
if (typeof computeItemId !== 'function') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return (row) => computeItemId(row);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get rowClass() {
|
|
69
|
+
return (row) => (row?.deleted_at ? 'is-deleted' : '');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
render() {
|
|
73
|
+
const gridConfig = this.config?.grid || {};
|
|
74
|
+
|
|
75
|
+
return html`
|
|
76
|
+
<dile-data-grid
|
|
77
|
+
.items=${this.items}
|
|
78
|
+
.columns=${this.columns}
|
|
79
|
+
.selectedIds=${this.selectedIds}
|
|
80
|
+
.computeRowId=${this.computeRowId}
|
|
81
|
+
.rowClass=${this.rowClass}
|
|
82
|
+
.sortField=${this.sort?.sortField || ''}
|
|
83
|
+
.sortDirection=${this.sort?.sortDirection || ''}
|
|
84
|
+
?selectable=${gridConfig.selectable !== undefined ? gridConfig.selectable : !this.config?.customization?.hideCheckboxSelection}
|
|
85
|
+
?sticky-first-column=${gridConfig.stickyFirstColumn !== undefined ? gridConfig.stickyFirstColumn : true}
|
|
86
|
+
?striped=${gridConfig.striped !== undefined ? gridConfig.striped : true}
|
|
87
|
+
responsive-mode=${gridConfig.responsiveMode || 'auto'}
|
|
88
|
+
empty-message=${gridConfig.emptyMessage || 'No data available'}
|
|
89
|
+
sort-mode="external"
|
|
90
|
+
></dile-data-grid>
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
2
|
+
import '@dile/ui/components/button/button.js';
|
|
3
|
+
import '@dile/iconlib/material-icons/edit.js';
|
|
4
|
+
import '@dile/iconlib/material-icons/delete.js';
|
|
5
|
+
import '@dile/iconlib/material-icons/restore-from-trash.js';
|
|
6
|
+
|
|
7
|
+
export class DileCrudItemActions extends LitElement {
|
|
8
|
+
static styles = [
|
|
9
|
+
css`
|
|
10
|
+
:host {
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: var(--dile-crud-item-actions-gap, 0.25rem);
|
|
14
|
+
}
|
|
15
|
+
dile-button.action-button {
|
|
16
|
+
--dile-button-background-color: var(--dile-crud-list-item-action-button-background-color, transparent);
|
|
17
|
+
--dile-button-border-color: var(--dile-crud-list-item-action-button-border-color, transparent);
|
|
18
|
+
--dile-button-hover-background-color: var(--dile-crud-list-item-action-button-hover-background-color, var(--dile-neutral-color, #f1f5f9));
|
|
19
|
+
--dile-button-hover-border-color: var(--dile-crud-list-item-action-button-hover-border-color, transparent);
|
|
20
|
+
--dile-button-padding-y: var(--dile-crud-list-item-action-button-padding-y, 0.25rem);
|
|
21
|
+
--dile-button-padding-x: var(--dile-crud-list-item-action-button-padding-x, 0.25rem);
|
|
22
|
+
--dile-button-icon-size: var(--dile-crud-list-item-action-button-icon-size, 24px);
|
|
23
|
+
--dile-button-border-radius: var(--dile-crud-list-item-action-button-border-radius, 2rem);
|
|
24
|
+
}
|
|
25
|
+
dile-button.action-button.edit {
|
|
26
|
+
--dile-button-icon-color: var(--edit-icon-color, var(--dile-alert-neutral-color, #0c7595));
|
|
27
|
+
--dile-button-icon-hover-color: var(--dile-crud-list-item-edit-icon-hover-color, var(--edit-icon-color, var(--dile-alert-neutral-color, #2889a7)));
|
|
28
|
+
}
|
|
29
|
+
dile-button.action-button.delete {
|
|
30
|
+
--dile-button-icon-color: var(--delete-icon-color, var(--dile-danger-color, #e33));
|
|
31
|
+
--dile-button-icon-hover-color: var(--dile-crud-list-item-delete-icon-hover-color, var(--delete-icon-color, var(--dile-danger-color, #e33)));
|
|
32
|
+
}
|
|
33
|
+
dile-button.action-button.restore {
|
|
34
|
+
--dile-button-icon-color: var(--restore-icon-color, var(--dile-alert-success-color, #00900f));
|
|
35
|
+
--dile-button-icon-hover-color: var(--dile-crud-list-item-restore-icon-hover-color, var(--restore-icon-color, var(--dile-alert-success-color, #00900f)));
|
|
36
|
+
}
|
|
37
|
+
`
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
static get properties() {
|
|
41
|
+
return {
|
|
42
|
+
item: { type: Object },
|
|
43
|
+
itemId: { type: String },
|
|
44
|
+
config: { type: Object },
|
|
45
|
+
disableEdit: { type: Boolean },
|
|
46
|
+
disableDelete: { type: Boolean },
|
|
47
|
+
disableRestore: { type: Boolean },
|
|
48
|
+
isDeleted: { type: Boolean },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
super();
|
|
54
|
+
this.config = null;
|
|
55
|
+
this.disableEdit = false;
|
|
56
|
+
this.disableDelete = false;
|
|
57
|
+
this.disableRestore = false;
|
|
58
|
+
this.isDeleted = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static hasActions(config) {
|
|
62
|
+
if (!config) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (config.customization?.disableListActions) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const editDisabled = Boolean(config.customization?.disableEdit);
|
|
69
|
+
const deleteDisabled = Boolean(config.customization?.disableDelete);
|
|
70
|
+
const restoreDisabled = Boolean(config.customization?.disableRestore);
|
|
71
|
+
|
|
72
|
+
return !(editDisabled && deleteDisabled && restoreDisabled);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get computedIsDeleted() {
|
|
76
|
+
if (this.isDeleted) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return Boolean(this.item?.deleted_at);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get computedItemId() {
|
|
83
|
+
if (this.itemId !== undefined && this.itemId !== null && this.itemId !== '') {
|
|
84
|
+
return this.itemId;
|
|
85
|
+
}
|
|
86
|
+
if (this.config?.computeItemId && typeof this.config.computeItemId === 'function' && this.item) {
|
|
87
|
+
return this.config.computeItemId(this.item);
|
|
88
|
+
}
|
|
89
|
+
return this.item?.id;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get isEditable() {
|
|
93
|
+
if (this.disableEdit) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (this.config) {
|
|
97
|
+
if (this.config.customization?.disableListActions || this.config.customization?.disableEdit) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (typeof this.config.isItemEditable === 'function' && this.item) {
|
|
101
|
+
return this.config.isItemEditable(this.item);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get isDeletable() {
|
|
108
|
+
if (this.disableDelete) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (this.config) {
|
|
112
|
+
if (this.config.customization?.disableListActions || this.config.customization?.disableDelete) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (typeof this.config.isItemDeletable === 'function' && this.item) {
|
|
116
|
+
return this.config.isItemDeletable(this.item);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get isRestorable() {
|
|
123
|
+
if (this.disableRestore) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (this.config?.customization?.disableListActions || this.config?.customization?.disableRestore) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
render() {
|
|
133
|
+
return this.computedIsDeleted ? this.restoreActionsTemplate : this.regularActionsTemplate;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get regularActionsTemplate() {
|
|
137
|
+
const showEdit = this.isEditable;
|
|
138
|
+
const showDelete = this.isDeletable;
|
|
139
|
+
|
|
140
|
+
if (!showEdit && !showDelete) {
|
|
141
|
+
return nothing;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return html`
|
|
145
|
+
${showEdit
|
|
146
|
+
? html`<dile-button class="action-button edit" icon="material.edit" label="Edit" @click=${this.editClick}></dile-button>`
|
|
147
|
+
: nothing
|
|
148
|
+
}
|
|
149
|
+
${showDelete
|
|
150
|
+
? html`<dile-button class="action-button delete" icon="material.delete" label="Delete" @click=${this.deleteClick}></dile-button>`
|
|
151
|
+
: nothing
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get restoreActionsTemplate() {
|
|
157
|
+
if (!this.isRestorable) {
|
|
158
|
+
return nothing;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return html`
|
|
162
|
+
<dile-button class="action-button restore" icon="material.restore-from-trash" label="Restore" @click=${this.restoreClick}></dile-button>
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_dispatchItemEvent(eventName) {
|
|
167
|
+
this.dispatchEvent(new CustomEvent(eventName, {
|
|
168
|
+
bubbles: true,
|
|
169
|
+
composed: true,
|
|
170
|
+
detail: {
|
|
171
|
+
item: this.item,
|
|
172
|
+
itemId: this.computedItemId,
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
editClick(e) {
|
|
178
|
+
e?.stopPropagation();
|
|
179
|
+
this._dispatchItemEvent('crud-item-edit');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
deleteClick(e) {
|
|
183
|
+
e?.stopPropagation();
|
|
184
|
+
this._dispatchItemEvent('crud-item-delete');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
restoreClick(e) {
|
|
188
|
+
e?.stopPropagation();
|
|
189
|
+
this._dispatchItemEvent('crud-item-restore');
|
|
190
|
+
}
|
|
191
|
+
}
|