@dile/crud 2.6.4 → 2.7.1
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/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 +192 -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 +255 -0
- package/components/list/index.js +4 -2
- package/components/list/src/DileCrudDataGrid.js +89 -0
- package/components/list/src/DileCrudItemActions.js +191 -0
- package/components/list/src/DileCrudList.js +55 -10
- package/components/list/src/DileCrudListItem.js +78 -124
- 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
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { CrudConfigBuilder } from '../../lib/CrudConfigBuilder.js';
|
|
3
|
+
import './crud-data-grid.js';
|
|
4
|
+
import '@dile/ui/components/data-grid/data-grid.js';
|
|
5
|
+
|
|
6
|
+
describe('dile-crud-data-grid', () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
document.body.innerHTML = '';
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const sampleColumns = [
|
|
12
|
+
{ field: 'id', header: 'ID' },
|
|
13
|
+
{ field: 'name', header: 'Name' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const sampleItems = [
|
|
17
|
+
{ id: 1, name: 'Item A' },
|
|
18
|
+
{ id: 2, name: 'Item B', deleted_at: '2026-01-01' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
async function createGrid(customConfig = {}, props = {}) {
|
|
22
|
+
const config = new CrudConfigBuilder('https://example.test/api/items', customConfig).getConfig();
|
|
23
|
+
const el = document.createElement('dile-crud-data-grid');
|
|
24
|
+
el.config = config;
|
|
25
|
+
el.items = props.items ?? sampleItems;
|
|
26
|
+
el.selectedIds = props.selectedIds ?? [];
|
|
27
|
+
document.body.appendChild(el);
|
|
28
|
+
await el.updateComplete;
|
|
29
|
+
return el;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function innerGrid(el) {
|
|
33
|
+
return el.shadowRoot.querySelector('dile-data-grid');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it('builds columns from config.grid.columns and auto-appends an Actions column by default', async () => {
|
|
37
|
+
const el = await createGrid({ grid: { columns: sampleColumns } });
|
|
38
|
+
|
|
39
|
+
expect(el.columns.length).toBe(3);
|
|
40
|
+
expect(el.columns[0]).toEqual(sampleColumns[0]);
|
|
41
|
+
expect(el.columns[1]).toEqual(sampleColumns[1]);
|
|
42
|
+
expect(el.columns[2].header).toBe('Actions');
|
|
43
|
+
expect(el.columns[2].align).toBe('right');
|
|
44
|
+
expect(el.columns[2].width).toBe('110px');
|
|
45
|
+
expect(el.columns[2].hideCardLabel).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('does not append an Actions column when config.grid.hideActionsColumn is true', async () => {
|
|
49
|
+
const el = await createGrid({ grid: { columns: sampleColumns, hideActionsColumn: true } });
|
|
50
|
+
|
|
51
|
+
expect(el.columns.length).toBe(2);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does not append an Actions column when actions are globally disabled', async () => {
|
|
55
|
+
const el = await createGrid({
|
|
56
|
+
grid: { columns: sampleColumns },
|
|
57
|
+
customization: { disableListActions: true },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(el.columns.length).toBe(2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('honors actionsColumnHeader, actionsColumnWidth and actionsColumnAlign overrides', async () => {
|
|
64
|
+
const el = await createGrid({
|
|
65
|
+
grid: {
|
|
66
|
+
columns: sampleColumns,
|
|
67
|
+
actionsColumnHeader: 'Ops',
|
|
68
|
+
actionsColumnWidth: '150px',
|
|
69
|
+
actionsColumnAlign: 'center',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const actionsColumn = el.columns[el.columns.length - 1];
|
|
74
|
+
expect(actionsColumn.header).toBe('Ops');
|
|
75
|
+
expect(actionsColumn.width).toBe('150px');
|
|
76
|
+
expect(actionsColumn.align).toBe('center');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('falls back to config.labels.actionsHeader when grid.actionsColumnHeader is not set', async () => {
|
|
80
|
+
const el = await createGrid({
|
|
81
|
+
grid: { columns: sampleColumns },
|
|
82
|
+
labels: { actionsHeader: 'Manage' },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const actionsColumn = el.columns[el.columns.length - 1];
|
|
86
|
+
expect(actionsColumn.header).toBe('Manage');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('renders dile-crud-item-actions in the Actions column wired with .item and .config', async () => {
|
|
90
|
+
const el = await createGrid({ grid: { columns: sampleColumns } });
|
|
91
|
+
|
|
92
|
+
const grid = innerGrid(el);
|
|
93
|
+
const actionsElements = grid.shadowRoot.querySelectorAll('dile-crud-item-actions');
|
|
94
|
+
expect(actionsElements.length).toBe(sampleItems.length);
|
|
95
|
+
expect(actionsElements[0].item).toEqual(sampleItems[0]);
|
|
96
|
+
expect(actionsElements[0].config).toBe(el.config);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('derives selectable from !config.customization.hideCheckboxSelection by default', async () => {
|
|
100
|
+
const elHidden = await createGrid({ grid: { columns: sampleColumns }, customization: { hideCheckboxSelection: true } });
|
|
101
|
+
expect(innerGrid(elHidden).selectable).toBe(false);
|
|
102
|
+
|
|
103
|
+
const elShown = await createGrid({ grid: { columns: sampleColumns }, customization: { hideCheckboxSelection: false } });
|
|
104
|
+
expect(innerGrid(elShown).selectable).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('lets config.grid.selectable override the customization.hideCheckboxSelection default', async () => {
|
|
108
|
+
const elForcedOff = await createGrid({
|
|
109
|
+
grid: { columns: sampleColumns, selectable: false },
|
|
110
|
+
customization: { hideCheckboxSelection: false },
|
|
111
|
+
});
|
|
112
|
+
expect(innerGrid(elForcedOff).selectable).toBe(false);
|
|
113
|
+
|
|
114
|
+
const elForcedOn = await createGrid({
|
|
115
|
+
grid: { columns: sampleColumns, selectable: true },
|
|
116
|
+
customization: { hideCheckboxSelection: true },
|
|
117
|
+
});
|
|
118
|
+
expect(innerGrid(elForcedOn).selectable).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('forwards stickyFirstColumn, striped, responsiveMode and emptyMessage with sensible defaults', async () => {
|
|
122
|
+
const elDefaults = await createGrid({ grid: { columns: sampleColumns } });
|
|
123
|
+
const gridDefaults = innerGrid(elDefaults);
|
|
124
|
+
expect(gridDefaults.stickyFirstColumn).toBe(true);
|
|
125
|
+
expect(gridDefaults.striped).toBe(true);
|
|
126
|
+
expect(gridDefaults.responsiveMode).toBe('auto');
|
|
127
|
+
expect(gridDefaults.emptyMessage).toBe('No data available');
|
|
128
|
+
|
|
129
|
+
const elOverrides = await createGrid({
|
|
130
|
+
grid: {
|
|
131
|
+
columns: sampleColumns,
|
|
132
|
+
stickyFirstColumn: false,
|
|
133
|
+
striped: false,
|
|
134
|
+
responsiveMode: 'cards',
|
|
135
|
+
emptyMessage: 'Nothing here',
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
const gridOverrides = innerGrid(elOverrides);
|
|
139
|
+
expect(gridOverrides.stickyFirstColumn).toBe(false);
|
|
140
|
+
expect(gridOverrides.striped).toBe(false);
|
|
141
|
+
expect(gridOverrides.responsiveMode).toBe('cards');
|
|
142
|
+
expect(gridOverrides.emptyMessage).toBe('Nothing here');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('always forces sort-mode to external regardless of config', async () => {
|
|
146
|
+
const el = await createGrid({ grid: { columns: sampleColumns } });
|
|
147
|
+
expect(innerGrid(el).sortMode).toBe('external');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('wires computeRowId from config.computeItemId so selection matches custom ids', async () => {
|
|
151
|
+
const el = await createGrid(
|
|
152
|
+
{
|
|
153
|
+
grid: { columns: sampleColumns, selectable: true },
|
|
154
|
+
computeItemId: (item) => `custom-${item.id}`,
|
|
155
|
+
},
|
|
156
|
+
{ selectedIds: ['custom-2'] }
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const checkboxes = innerGrid(el).shadowRoot.querySelectorAll('dile-checkbox');
|
|
160
|
+
expect(checkboxes[0].checked).toBe(false);
|
|
161
|
+
expect(checkboxes[1].checked).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('leaves computeRowId unset when config.computeItemId is not a function', async () => {
|
|
165
|
+
const el = document.createElement('dile-crud-data-grid');
|
|
166
|
+
el.config = { grid: { columns: sampleColumns } };
|
|
167
|
+
el.items = sampleItems;
|
|
168
|
+
document.body.appendChild(el);
|
|
169
|
+
await el.updateComplete;
|
|
170
|
+
|
|
171
|
+
expect(el.computeRowId).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('applies the is-deleted row class to rows whose deleted_at is truthy', async () => {
|
|
175
|
+
const el = await createGrid({ grid: { columns: sampleColumns } });
|
|
176
|
+
|
|
177
|
+
const rows = innerGrid(el).shadowRoot.querySelectorAll('tbody tr');
|
|
178
|
+
expect(rows[0].classList.contains('is-deleted')).toBe(false);
|
|
179
|
+
expect(rows[1].classList.contains('is-deleted')).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('does not throw and still appends the Actions column when config is null', async () => {
|
|
183
|
+
const el = document.createElement('dile-crud-data-grid');
|
|
184
|
+
el.items = sampleItems;
|
|
185
|
+
document.body.appendChild(el);
|
|
186
|
+
await el.updateComplete;
|
|
187
|
+
|
|
188
|
+
expect(el.columns.length).toBe(1);
|
|
189
|
+
expect(el.columns[0].header).toBe('Actions');
|
|
190
|
+
expect(innerGrid(el)).toBeTruthy();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
import { CrudConfigBuilder } from '../../lib/CrudConfigBuilder.js';
|
|
4
|
+
import { DileCrudItemActions } from './src/DileCrudItemActions.js';
|
|
5
|
+
import './crud-item-actions.js';
|
|
6
|
+
|
|
7
|
+
describe('dile-crud-item-actions', () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
document.body.innerHTML = '';
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const sampleItem = { id: 10, name: 'Sample Item', deleted_at: null };
|
|
13
|
+
const deletedItem = { id: 20, name: 'Deleted Item', deleted_at: '2026-01-01' };
|
|
14
|
+
|
|
15
|
+
it('renders edit and delete buttons by default', async () => {
|
|
16
|
+
const el = document.createElement('dile-crud-item-actions');
|
|
17
|
+
el.item = sampleItem;
|
|
18
|
+
document.body.appendChild(el);
|
|
19
|
+
await el.updateComplete;
|
|
20
|
+
|
|
21
|
+
const editBtn = el.shadowRoot.querySelector('dile-button.edit');
|
|
22
|
+
const deleteBtn = el.shadowRoot.querySelector('dile-button.delete');
|
|
23
|
+
const restoreBtn = el.shadowRoot.querySelector('dile-button.restore');
|
|
24
|
+
|
|
25
|
+
expect(editBtn).toBeTruthy();
|
|
26
|
+
expect(deleteBtn).toBeTruthy();
|
|
27
|
+
expect(restoreBtn).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders restore button when item has deleted_at or isDeleted is true', async () => {
|
|
31
|
+
const el = document.createElement('dile-crud-item-actions');
|
|
32
|
+
el.item = deletedItem;
|
|
33
|
+
document.body.appendChild(el);
|
|
34
|
+
await el.updateComplete;
|
|
35
|
+
|
|
36
|
+
const editBtn = el.shadowRoot.querySelector('dile-button.edit');
|
|
37
|
+
const deleteBtn = el.shadowRoot.querySelector('dile-button.delete');
|
|
38
|
+
const restoreBtn = el.shadowRoot.querySelector('dile-button.restore');
|
|
39
|
+
|
|
40
|
+
expect(editBtn).toBeNull();
|
|
41
|
+
expect(deleteBtn).toBeNull();
|
|
42
|
+
expect(restoreBtn).toBeTruthy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('respects global customization and item-level callbacks from config', async () => {
|
|
46
|
+
const config = new CrudConfigBuilder('https://example.test/api/items', {
|
|
47
|
+
customization: {
|
|
48
|
+
disableEdit: false,
|
|
49
|
+
disableDelete: true,
|
|
50
|
+
},
|
|
51
|
+
isItemEditable: (item) => item.id !== 99,
|
|
52
|
+
computeItemId: (item) => `custom-${item.id}`,
|
|
53
|
+
}).getConfig();
|
|
54
|
+
|
|
55
|
+
const el = document.createElement('dile-crud-item-actions');
|
|
56
|
+
el.item = { id: 99, name: 'Not editable' };
|
|
57
|
+
el.config = config;
|
|
58
|
+
document.body.appendChild(el);
|
|
59
|
+
await el.updateComplete;
|
|
60
|
+
|
|
61
|
+
// Both edit (via isItemEditable returning false) and delete (via disableDelete: true) should be hidden
|
|
62
|
+
expect(el.shadowRoot.querySelector('dile-button.edit')).toBeNull();
|
|
63
|
+
expect(el.shadowRoot.querySelector('dile-button.delete')).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('hides all actions when disableListActions is true in config', async () => {
|
|
67
|
+
const config = new CrudConfigBuilder('https://example.test/api/items', {
|
|
68
|
+
customization: {
|
|
69
|
+
disableListActions: true,
|
|
70
|
+
},
|
|
71
|
+
}).getConfig();
|
|
72
|
+
|
|
73
|
+
expect(DileCrudItemActions.hasActions(config)).toBe(false);
|
|
74
|
+
|
|
75
|
+
const el = document.createElement('dile-crud-item-actions');
|
|
76
|
+
el.item = sampleItem;
|
|
77
|
+
el.config = config;
|
|
78
|
+
document.body.appendChild(el);
|
|
79
|
+
await el.updateComplete;
|
|
80
|
+
|
|
81
|
+
expect(el.shadowRoot.querySelector('dile-button.edit')).toBeNull();
|
|
82
|
+
expect(el.shadowRoot.querySelector('dile-button.delete')).toBeNull();
|
|
83
|
+
expect(el.shadowRoot.querySelector('dile-button.restore')).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('DileCrudItemActions.hasActions returns false if edit, delete and restore are all disabled', () => {
|
|
87
|
+
const config = new CrudConfigBuilder('https://example.test/api/items', {
|
|
88
|
+
customization: {
|
|
89
|
+
disableEdit: true,
|
|
90
|
+
disableDelete: true,
|
|
91
|
+
disableRestore: true,
|
|
92
|
+
},
|
|
93
|
+
}).getConfig();
|
|
94
|
+
|
|
95
|
+
expect(DileCrudItemActions.hasActions(config)).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('dispatches crud-item-edit with computed itemId from config', async () => {
|
|
99
|
+
const config = new CrudConfigBuilder('https://example.test/api/items', {
|
|
100
|
+
computeItemId: (item) => `computed-${item.id}`,
|
|
101
|
+
}).getConfig();
|
|
102
|
+
|
|
103
|
+
const el = document.createElement('dile-crud-item-actions');
|
|
104
|
+
el.item = sampleItem;
|
|
105
|
+
el.config = config;
|
|
106
|
+
document.body.appendChild(el);
|
|
107
|
+
await el.updateComplete;
|
|
108
|
+
|
|
109
|
+
let emittedDetail = null;
|
|
110
|
+
el.addEventListener('crud-item-edit', (e) => {
|
|
111
|
+
emittedDetail = e.detail;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const editBtn = el.shadowRoot.querySelector('dile-button.edit');
|
|
115
|
+
editBtn.click();
|
|
116
|
+
|
|
117
|
+
expect(emittedDetail).toEqual({
|
|
118
|
+
item: sampleItem,
|
|
119
|
+
itemId: 'computed-10',
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,255 @@
|
|
|
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.loading = false;
|
|
56
|
+
document.body.appendChild(el);
|
|
57
|
+
await el.updateComplete;
|
|
58
|
+
|
|
59
|
+
const listItems = el.shadowRoot.querySelectorAll('dile-crud-list-item');
|
|
60
|
+
expect(listItems.length).toBe(0);
|
|
61
|
+
|
|
62
|
+
const crudDataGrid = el.shadowRoot.querySelector('dile-crud-data-grid');
|
|
63
|
+
expect(crudDataGrid).toBeTruthy();
|
|
64
|
+
expect(crudDataGrid.items).toEqual(sampleElements);
|
|
65
|
+
expect(crudDataGrid.columns.length).toBe(3); // id, name + automatically injected Actions
|
|
66
|
+
expect(crudDataGrid.columns[2].header).toBe('Actions');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('hides actions in traditional list items when disableListActions is true', async () => {
|
|
70
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
71
|
+
customization: {
|
|
72
|
+
disableListActions: true,
|
|
73
|
+
},
|
|
74
|
+
templates: {
|
|
75
|
+
item: (item) => html`<span class="item-name">${item.name}</span>`,
|
|
76
|
+
},
|
|
77
|
+
}).getConfig();
|
|
78
|
+
|
|
79
|
+
const el = document.createElement('dile-crud-list');
|
|
80
|
+
el.disableLoadOnStart = true;
|
|
81
|
+
el.config = config;
|
|
82
|
+
el.elements = sampleElements;
|
|
83
|
+
el.loading = false;
|
|
84
|
+
document.body.appendChild(el);
|
|
85
|
+
await el.updateComplete;
|
|
86
|
+
|
|
87
|
+
const listItems = el.shadowRoot.querySelectorAll('dile-crud-list-item');
|
|
88
|
+
expect(listItems.length).toBe(2);
|
|
89
|
+
|
|
90
|
+
for (const item of listItems) {
|
|
91
|
+
const actionsDiv = item.shadowRoot.querySelector('.actions');
|
|
92
|
+
expect(actionsDiv).toBeNull();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('renders custom grid template when templates.grid is configured', async () => {
|
|
97
|
+
let receivedElements = null;
|
|
98
|
+
let receivedActionIds = null;
|
|
99
|
+
|
|
100
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
101
|
+
templates: {
|
|
102
|
+
grid: (elements, actionIds) => {
|
|
103
|
+
receivedElements = elements;
|
|
104
|
+
receivedActionIds = actionIds;
|
|
105
|
+
return html`
|
|
106
|
+
<dile-data-grid
|
|
107
|
+
.items="${elements}"
|
|
108
|
+
.selectedIds="${actionIds}"
|
|
109
|
+
selectable
|
|
110
|
+
.columns="${[
|
|
111
|
+
{ field: 'id', header: 'ID' },
|
|
112
|
+
{ field: 'name', header: 'Name' },
|
|
113
|
+
]}"
|
|
114
|
+
></dile-data-grid>
|
|
115
|
+
`;
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}).getConfig();
|
|
119
|
+
|
|
120
|
+
const el = document.createElement('dile-crud-list');
|
|
121
|
+
el.disableLoadOnStart = true;
|
|
122
|
+
el.config = config;
|
|
123
|
+
el.elements = sampleElements;
|
|
124
|
+
el.actionIds = [2];
|
|
125
|
+
el.loading = false;
|
|
126
|
+
document.body.appendChild(el);
|
|
127
|
+
await el.updateComplete;
|
|
128
|
+
|
|
129
|
+
const listItems = el.shadowRoot.querySelectorAll('dile-crud-list-item');
|
|
130
|
+
expect(listItems.length).toBe(0);
|
|
131
|
+
|
|
132
|
+
const gridContainer = el.shadowRoot.querySelector('.grid-container');
|
|
133
|
+
expect(gridContainer).toBeTruthy();
|
|
134
|
+
|
|
135
|
+
const dataGrid = el.shadowRoot.querySelector('dile-data-grid');
|
|
136
|
+
expect(dataGrid).toBeTruthy();
|
|
137
|
+
expect(receivedElements).toEqual(sampleElements);
|
|
138
|
+
expect(receivedActionIds).toEqual([2]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('updates sort when dile-data-grid-sort is dispatched', async () => {
|
|
142
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
143
|
+
templates: {
|
|
144
|
+
grid: (elements, actionIds) => html`
|
|
145
|
+
<dile-data-grid
|
|
146
|
+
.items="${elements}"
|
|
147
|
+
sort-mode="external"
|
|
148
|
+
.columns="${[
|
|
149
|
+
{ field: 'name', header: 'Name', sortable: true },
|
|
150
|
+
]}"
|
|
151
|
+
></dile-data-grid>
|
|
152
|
+
`,
|
|
153
|
+
},
|
|
154
|
+
}).getConfig();
|
|
155
|
+
|
|
156
|
+
const el = document.createElement('dile-crud-list');
|
|
157
|
+
el.disableLoadOnStart = true;
|
|
158
|
+
el.config = config;
|
|
159
|
+
el.elements = sampleElements;
|
|
160
|
+
el.loading = false;
|
|
161
|
+
document.body.appendChild(el);
|
|
162
|
+
await el.updateComplete;
|
|
163
|
+
|
|
164
|
+
let sortApplied = null;
|
|
165
|
+
el.setSort = (sortObj) => {
|
|
166
|
+
sortApplied = sortObj;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const gridContainer = el.shadowRoot.querySelector('.grid-container');
|
|
170
|
+
gridContainer.dispatchEvent(
|
|
171
|
+
new CustomEvent('dile-data-grid-sort', {
|
|
172
|
+
bubbles: true,
|
|
173
|
+
composed: true,
|
|
174
|
+
detail: {
|
|
175
|
+
field: 'name',
|
|
176
|
+
direction: 'asc',
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(sortApplied).toEqual({
|
|
182
|
+
sortField: 'name',
|
|
183
|
+
sortDirection: 'asc',
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('updates sort when dile-data-grid-sort is dispatched from the config.grid.columns branch', async () => {
|
|
188
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
189
|
+
grid: {
|
|
190
|
+
columns: [
|
|
191
|
+
{ field: 'name', header: 'Name', sortable: true },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
}).getConfig();
|
|
195
|
+
|
|
196
|
+
const el = document.createElement('dile-crud-list');
|
|
197
|
+
el.disableLoadOnStart = true;
|
|
198
|
+
el.config = config;
|
|
199
|
+
el.elements = sampleElements;
|
|
200
|
+
el.loading = false;
|
|
201
|
+
document.body.appendChild(el);
|
|
202
|
+
await el.updateComplete;
|
|
203
|
+
|
|
204
|
+
let sortApplied = null;
|
|
205
|
+
el.setSort = (sortObj) => {
|
|
206
|
+
sortApplied = sortObj;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const gridContainer = el.shadowRoot.querySelector('.grid-container');
|
|
210
|
+
gridContainer.dispatchEvent(
|
|
211
|
+
new CustomEvent('dile-data-grid-sort', {
|
|
212
|
+
bubbles: true,
|
|
213
|
+
composed: true,
|
|
214
|
+
detail: {
|
|
215
|
+
field: 'name',
|
|
216
|
+
direction: 'desc',
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(sortApplied).toEqual({
|
|
222
|
+
sortField: 'name',
|
|
223
|
+
sortDirection: 'desc',
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('prefers templates.grid over config.grid.columns when both are configured', async () => {
|
|
228
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
229
|
+
grid: {
|
|
230
|
+
columns: [
|
|
231
|
+
{ field: 'id', header: 'ID' },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
templates: {
|
|
235
|
+
grid: (elements, actionIds) => html`
|
|
236
|
+
<dile-data-grid
|
|
237
|
+
.items="${elements}"
|
|
238
|
+
.columns="${[{ field: 'name', header: 'Name' }]}"
|
|
239
|
+
></dile-data-grid>
|
|
240
|
+
`,
|
|
241
|
+
},
|
|
242
|
+
}).getConfig();
|
|
243
|
+
|
|
244
|
+
const el = document.createElement('dile-crud-list');
|
|
245
|
+
el.disableLoadOnStart = true;
|
|
246
|
+
el.config = config;
|
|
247
|
+
el.elements = sampleElements;
|
|
248
|
+
el.loading = false;
|
|
249
|
+
document.body.appendChild(el);
|
|
250
|
+
await el.updateComplete;
|
|
251
|
+
|
|
252
|
+
expect(el.shadowRoot.querySelector('dile-crud-data-grid')).toBeNull();
|
|
253
|
+
expect(el.shadowRoot.querySelector('dile-data-grid')).toBeTruthy();
|
|
254
|
+
});
|
|
255
|
+
});
|
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,89 @@
|
|
|
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
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
this.items = [];
|
|
26
|
+
this.selectedIds = [];
|
|
27
|
+
this.config = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get columns() {
|
|
31
|
+
const configuredCols = this.config?.grid?.columns || [];
|
|
32
|
+
const cols = [...configuredCols];
|
|
33
|
+
|
|
34
|
+
const hideActions = Boolean(this.config?.grid?.hideActionsColumn);
|
|
35
|
+
|
|
36
|
+
if (!hideActions && DileCrudItemActions.hasActions(this.config)) {
|
|
37
|
+
const customActionsHeader = this.config?.grid?.actionsColumnHeader || this.config?.labels?.actionsHeader || 'Actions';
|
|
38
|
+
const customActionsWidth = this.config?.grid?.actionsColumnWidth || '110px';
|
|
39
|
+
const customActionsAlign = this.config?.grid?.actionsColumnAlign || 'right';
|
|
40
|
+
|
|
41
|
+
cols.push({
|
|
42
|
+
header: customActionsHeader,
|
|
43
|
+
align: customActionsAlign,
|
|
44
|
+
width: customActionsWidth,
|
|
45
|
+
hideCardLabel: true,
|
|
46
|
+
render: (item) => html`
|
|
47
|
+
<dile-crud-item-actions
|
|
48
|
+
.item=${item}
|
|
49
|
+
.config=${this.config}
|
|
50
|
+
></dile-crud-item-actions>
|
|
51
|
+
`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return cols;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get computeRowId() {
|
|
59
|
+
const computeItemId = this.config?.computeItemId;
|
|
60
|
+
if (typeof computeItemId !== 'function') {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return (row) => computeItemId(row);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get rowClass() {
|
|
67
|
+
return (row) => (row?.deleted_at ? 'is-deleted' : '');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render() {
|
|
71
|
+
const gridConfig = this.config?.grid || {};
|
|
72
|
+
|
|
73
|
+
return html`
|
|
74
|
+
<dile-data-grid
|
|
75
|
+
.items=${this.items}
|
|
76
|
+
.columns=${this.columns}
|
|
77
|
+
.selectedIds=${this.selectedIds}
|
|
78
|
+
.computeRowId=${this.computeRowId}
|
|
79
|
+
.rowClass=${this.rowClass}
|
|
80
|
+
?selectable=${gridConfig.selectable !== undefined ? gridConfig.selectable : !this.config?.customization?.hideCheckboxSelection}
|
|
81
|
+
?sticky-first-column=${gridConfig.stickyFirstColumn !== undefined ? gridConfig.stickyFirstColumn : true}
|
|
82
|
+
?striped=${gridConfig.striped !== undefined ? gridConfig.striped : true}
|
|
83
|
+
responsive-mode=${gridConfig.responsiveMode || 'auto'}
|
|
84
|
+
empty-message=${gridConfig.emptyMessage || 'No data available'}
|
|
85
|
+
sort-mode="external"
|
|
86
|
+
></dile-data-grid>
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
}
|