@dile/crud 2.7.5 → 2.8.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.
- package/components/crud/__screenshots__/crud.spec.js/dile-crud-list-grid-view-switch-defaults-to-the-item-template-and-toggles-to-the-grid-on-click-when-both-views-are-configured-1.png +0 -0
- package/components/crud/__screenshots__/crud.spec.js/dile-crud-list-grid-view-switch-does-not-render-the-switch-button-when-customization-disableListWiewSwitch-is-true-1.png +0 -0
- package/components/crud/crud.spec.js +86 -0
- package/components/crud/src/DileCrud.js +79 -2
- package/components/list/crud-list.spec.js +54 -0
- package/components/list/src/DileCrudList.js +10 -2
- package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-clicking-the-label-of-an-inactive-option-switches-to-it-using-its-own-configured-direction--without-flipping-it-1.png +0 -0
- package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-clicking-the-label-of-the-already-active-option-toggles-its-direction-instead-of-doing-nothing-1.png +0 -0
- package/components/ui/src/DileCrudListOptions.js +25 -3
- package/lib/defaultConfig.js +1 -0
- package/lib/i18n/en.js +2 -0
- package/lib/i18n/es.js +2 -0
- package/lib/listViewMode.js +17 -0
- package/lib/listViewMode.spec.js +73 -0
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { html } from 'lit';
|
|
2
3
|
import { CrudConfigBuilder } from '../../lib/CrudConfigBuilder.js';
|
|
3
4
|
import './crud.js';
|
|
4
5
|
|
|
@@ -274,3 +275,88 @@ describe('dile-crud sort synchronization', () => {
|
|
|
274
275
|
expect(lastSetSort).toBeNull();
|
|
275
276
|
});
|
|
276
277
|
});
|
|
278
|
+
|
|
279
|
+
describe('dile-crud list/grid view switch', () => {
|
|
280
|
+
afterEach(() => {
|
|
281
|
+
document.body.innerHTML = '';
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const baseCustomization = {
|
|
285
|
+
disableInsert: true,
|
|
286
|
+
disableHelp: true,
|
|
287
|
+
disableKeywordSearch: true,
|
|
288
|
+
disableSort: true,
|
|
289
|
+
disableFilter: true,
|
|
290
|
+
disablePagination: true,
|
|
291
|
+
hideCheckboxSelection: true,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
function buildConfig(overrides = {}) {
|
|
295
|
+
return new CrudConfigBuilder('https://example.test/api/countries', {
|
|
296
|
+
customization: { ...baseCustomization, ...(overrides.customization || {}) },
|
|
297
|
+
grid: { columns: [{ field: 'name', header: 'Name' }] },
|
|
298
|
+
templates: { item: (item) => html`<span>${item.name}</span>` },
|
|
299
|
+
...overrides,
|
|
300
|
+
}).getConfig();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
it('shows the item template by default and renders no switch button when only one view is configured', async () => {
|
|
304
|
+
const config = new CrudConfigBuilder('https://example.test/api/countries', {
|
|
305
|
+
customization: baseCustomization,
|
|
306
|
+
templates: { item: (item) => html`<span>${item.name}</span>` },
|
|
307
|
+
}).getConfig();
|
|
308
|
+
|
|
309
|
+
const el = document.createElement('dile-crud');
|
|
310
|
+
el.config = config;
|
|
311
|
+
document.body.appendChild(el);
|
|
312
|
+
await el.updateComplete;
|
|
313
|
+
|
|
314
|
+
const switchButtons = [...el.shadowRoot.querySelectorAll('dile-button-icon')]
|
|
315
|
+
.filter((btn) => /view/i.test(btn.textContent));
|
|
316
|
+
expect(switchButtons.length).toBe(0);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('defaults to the item view and toggles to the grid on click when both views are configured', async () => {
|
|
320
|
+
// Rendering per view mode (item vs. grid DOM) is covered in crud-list.spec.js;
|
|
321
|
+
// here we only verify dile-crud's own state, wiring and button behavior, since
|
|
322
|
+
// driving dile-crud-list's real data load would race its own network refresh.
|
|
323
|
+
const config = buildConfig();
|
|
324
|
+
|
|
325
|
+
const el = document.createElement('dile-crud');
|
|
326
|
+
el.config = config;
|
|
327
|
+
document.body.appendChild(el);
|
|
328
|
+
await el.updateComplete;
|
|
329
|
+
|
|
330
|
+
expect(el._viewMode).toBe('item');
|
|
331
|
+
expect(el.listElement.viewMode).toBe('item');
|
|
332
|
+
|
|
333
|
+
const switchButton = [...el.shadowRoot.querySelectorAll('dile-button-icon')]
|
|
334
|
+
.find((btn) => /view/i.test(btn.textContent));
|
|
335
|
+
expect(switchButton).toBeTruthy();
|
|
336
|
+
expect(switchButton.textContent.trim()).toBe('Grid view');
|
|
337
|
+
|
|
338
|
+
switchButton.click();
|
|
339
|
+
await el.updateComplete;
|
|
340
|
+
|
|
341
|
+
expect(el._viewMode).toBe('grid');
|
|
342
|
+
expect(el.listElement.viewMode).toBe('grid');
|
|
343
|
+
|
|
344
|
+
const switchButtonAfter = [...el.shadowRoot.querySelectorAll('dile-button-icon')]
|
|
345
|
+
.find((btn) => /view/i.test(btn.textContent));
|
|
346
|
+
expect(switchButtonAfter.textContent.trim()).toBe('List view');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('does not render the switch button when customization.disableListWiewSwitch is true', async () => {
|
|
350
|
+
const config = buildConfig({ customization: { disableListWiewSwitch: true } });
|
|
351
|
+
|
|
352
|
+
const el = document.createElement('dile-crud');
|
|
353
|
+
el.config = config;
|
|
354
|
+
document.body.appendChild(el);
|
|
355
|
+
await el.updateComplete;
|
|
356
|
+
|
|
357
|
+
const switchButtons = [...el.shadowRoot.querySelectorAll('dile-button-icon')]
|
|
358
|
+
.filter((btn) => /view/i.test(btn.textContent));
|
|
359
|
+
expect(switchButtons.length).toBe(0);
|
|
360
|
+
expect(el.listElement.viewMode).toBe('item');
|
|
361
|
+
});
|
|
362
|
+
});
|
|
@@ -4,6 +4,7 @@ import '@dile/ui/components/nav/nav.js';
|
|
|
4
4
|
import '@dile/ui/components/select/select.js';
|
|
5
5
|
import '@dile/ui/components/modal/modal.js';
|
|
6
6
|
import '@dile/ui/components/modal/modal-help.js';
|
|
7
|
+
import '@dile/ui/components/icon/icon.js';
|
|
7
8
|
import '../../item-delete/crud-item-delete.js';
|
|
8
9
|
import '../../item-restore/crud-item-restore.js';
|
|
9
10
|
import '../../list/crud-list.js';
|
|
@@ -19,8 +20,9 @@ import '../../action/crud-single-action-dispatcher.js';
|
|
|
19
20
|
import { formStyles } from '../../../styles/form-styles.js';
|
|
20
21
|
import { DileCrudMixin } from '../../../lib/DileCrudMixin.js';
|
|
21
22
|
import { crudStyles } from '../../../styles/crud-styles.js';
|
|
22
|
-
import { addIcon } from '@dile/icons';
|
|
23
|
+
import { addIcon, appsIcon, listIcon } from '@dile/icons';
|
|
23
24
|
import { DileI18nMixin } from '../../../lib/DileI18nMixin.js';
|
|
25
|
+
import { canSwitchListView } from '../../../lib/listViewMode.js';
|
|
24
26
|
|
|
25
27
|
export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
26
28
|
static styles = [
|
|
@@ -70,6 +72,26 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
70
72
|
--dile-icon-size: var(--dile-crud-insert-icon-size, 24px);
|
|
71
73
|
--dile-button-icon-separation: var(--dile-crud-insert-icon-separation, 0.3rem);
|
|
72
74
|
}
|
|
75
|
+
.view-switch dile-button-icon {
|
|
76
|
+
display: none;
|
|
77
|
+
}
|
|
78
|
+
.view-switch dile-icon {
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
--dile-icon-color: var(--dile-on-crud-action-color, #fff);
|
|
81
|
+
--dile-icon-rounded-background-color: var(--dile-crud-action-color, #888);
|
|
82
|
+
}
|
|
83
|
+
.view-switch dile-icon:focus-visible {
|
|
84
|
+
outline: 2px solid var(--dile-crud-action-focus-color, #12354d);
|
|
85
|
+
outline-offset: 2px;
|
|
86
|
+
}
|
|
87
|
+
@media (min-width: 605px) {
|
|
88
|
+
.view-switch dile-button-icon {
|
|
89
|
+
display: block;
|
|
90
|
+
}
|
|
91
|
+
.view-switch dile-icon {
|
|
92
|
+
display: none;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
73
95
|
@media(min-width: 400px) {
|
|
74
96
|
.simplecard {
|
|
75
97
|
--dile-card-border: none;
|
|
@@ -99,6 +121,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
99
121
|
relationId: { type: String },
|
|
100
122
|
filtersAlwaysVisible: { type: Boolean },
|
|
101
123
|
singleActionDispatcher: { type: String },
|
|
124
|
+
_viewMode: { state: true },
|
|
102
125
|
};
|
|
103
126
|
}
|
|
104
127
|
|
|
@@ -106,6 +129,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
106
129
|
super();
|
|
107
130
|
this.actionIds = [];
|
|
108
131
|
this.filtersAlwaysVisible = false;
|
|
132
|
+
this._viewMode = 'item';
|
|
109
133
|
}
|
|
110
134
|
|
|
111
135
|
// GETTERS ELEMENTOS
|
|
@@ -216,6 +240,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
216
240
|
@crud-item-restore=${this.itemRestoreRequest}
|
|
217
241
|
@insert-requested=${this.openInsert}
|
|
218
242
|
.actionIds=${this.actionIds}
|
|
243
|
+
.viewMode=${this._viewMode}
|
|
219
244
|
belongsTo=${this.belongsTo}
|
|
220
245
|
relationId=${this.relationId}
|
|
221
246
|
language="${this.language}"
|
|
@@ -241,6 +266,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
241
266
|
: html`<div slot="menu">${this.helpTemplate}</div>`
|
|
242
267
|
}
|
|
243
268
|
<div class="actions" slot="actions">
|
|
269
|
+
${this.canSwitchListView ? this.listViewSwitchTemplate : ''}
|
|
244
270
|
${this.actionsTemplate}
|
|
245
271
|
${this.config.customization.disableFilter || this.filtersAlwaysVisible
|
|
246
272
|
? ''
|
|
@@ -287,6 +313,37 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
287
313
|
`
|
|
288
314
|
}
|
|
289
315
|
|
|
316
|
+
get canSwitchListView() {
|
|
317
|
+
return canSwitchListView(this.config);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
get listViewSwitchTemplate() {
|
|
321
|
+
const label = this._viewMode === 'grid'
|
|
322
|
+
? this.listViewLabelComputed(this.config.labels.listViewAction, this.translations)
|
|
323
|
+
: this.gridViewLabelComputed(this.config.labels.gridViewAction, this.translations);
|
|
324
|
+
const icon = this._viewMode === 'grid' ? listIcon : appsIcon;
|
|
325
|
+
return html`
|
|
326
|
+
<div class="view-switch action-controller">
|
|
327
|
+
<dile-icon
|
|
328
|
+
rounded
|
|
329
|
+
tabindex="0"
|
|
330
|
+
role="button"
|
|
331
|
+
aria-label="${label}"
|
|
332
|
+
title="${label}"
|
|
333
|
+
.icon=${icon}
|
|
334
|
+
@click=${this.toggleListView}
|
|
335
|
+
@keydown=${this.viewSwitchIconKeydown}
|
|
336
|
+
></dile-icon>
|
|
337
|
+
<dile-button-icon
|
|
338
|
+
@click=${this.toggleListView}
|
|
339
|
+
.icon=${icon}
|
|
340
|
+
>
|
|
341
|
+
${label}
|
|
342
|
+
</dile-button-icon>
|
|
343
|
+
</div>
|
|
344
|
+
`
|
|
345
|
+
}
|
|
346
|
+
|
|
290
347
|
get filtersAlwaysVisibleTemplate() {
|
|
291
348
|
return html`
|
|
292
349
|
<dile-crud-filters-inline
|
|
@@ -304,10 +361,30 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
304
361
|
&& (customization.disableFilter || this.filtersAlwaysVisible)
|
|
305
362
|
&& customization.disablePagination
|
|
306
363
|
&& customization.disableSort
|
|
307
|
-
&& customization.hideCheckboxSelection
|
|
364
|
+
&& customization.hideCheckboxSelection
|
|
365
|
+
&& !this.canSwitchListView;
|
|
308
366
|
}
|
|
309
367
|
// BEHAVIOURS
|
|
310
368
|
|
|
369
|
+
toggleListView() {
|
|
370
|
+
this._viewMode = this._viewMode === 'grid' ? 'item' : 'grid';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
viewSwitchIconKeydown(e) {
|
|
374
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
this.toggleListView();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
listViewLabelComputed(label, translations) {
|
|
381
|
+
return label ? label : translations?.list_view_label ? translations.list_view_label : 'List view';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
gridViewLabelComputed(label, translations) {
|
|
385
|
+
return label ? label : translations?.grid_view_label ? translations.grid_view_label : 'Grid view';
|
|
386
|
+
}
|
|
387
|
+
|
|
311
388
|
openInsert() {
|
|
312
389
|
this.dispatchEvent(new CustomEvent('crud-item-insert', { bubbles: true, composed: true }));
|
|
313
390
|
if (this.config.insertOperation?.type === 'modal') {
|
|
@@ -316,4 +316,58 @@ describe('dile-crud-list grid template integration', () => {
|
|
|
316
316
|
expect(innerGrid.sortField).toBe('name');
|
|
317
317
|
expect(innerGrid.sortDirection).toBe('asc');
|
|
318
318
|
});
|
|
319
|
+
|
|
320
|
+
it('shows the item template by default when both templates.item and grid.columns are configured', async () => {
|
|
321
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
322
|
+
grid: { columns: [{ field: 'name', header: 'Name' }] },
|
|
323
|
+
templates: { item: (item) => html`<span class="item-name">${item.name}</span>` },
|
|
324
|
+
}).getConfig();
|
|
325
|
+
|
|
326
|
+
const el = document.createElement('dile-crud-list');
|
|
327
|
+
el.disableLoadOnStart = true;
|
|
328
|
+
el.config = config;
|
|
329
|
+
el.elements = sampleElements;
|
|
330
|
+
el.loading = false;
|
|
331
|
+
document.body.appendChild(el);
|
|
332
|
+
await el.updateComplete;
|
|
333
|
+
|
|
334
|
+
expect(el.shadowRoot.querySelectorAll('dile-crud-list-item').length).toBe(2);
|
|
335
|
+
expect(el.shadowRoot.querySelector('.grid-container')).toBeNull();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('shows the grid when viewMode is "grid" and both templates.item and grid.columns are configured', async () => {
|
|
339
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
340
|
+
grid: { columns: [{ field: 'name', header: 'Name' }] },
|
|
341
|
+
templates: { item: (item) => html`<span class="item-name">${item.name}</span>` },
|
|
342
|
+
}).getConfig();
|
|
343
|
+
|
|
344
|
+
const el = document.createElement('dile-crud-list');
|
|
345
|
+
el.disableLoadOnStart = true;
|
|
346
|
+
el.config = config;
|
|
347
|
+
el.elements = sampleElements;
|
|
348
|
+
el.loading = false;
|
|
349
|
+
el.viewMode = 'grid';
|
|
350
|
+
document.body.appendChild(el);
|
|
351
|
+
await el.updateComplete;
|
|
352
|
+
|
|
353
|
+
expect(el.shadowRoot.querySelectorAll('dile-crud-list-item').length).toBe(0);
|
|
354
|
+
expect(el.shadowRoot.querySelector('.grid-container')).toBeTruthy();
|
|
355
|
+
expect(el.shadowRoot.querySelector('dile-crud-data-grid')).toBeTruthy();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('still shows the grid when only grid.columns is configured, regardless of viewMode', async () => {
|
|
359
|
+
const config = new CrudConfigBuilder('https://example.test/api/customers', {
|
|
360
|
+
grid: { columns: [{ field: 'name', header: 'Name' }] },
|
|
361
|
+
}).getConfig();
|
|
362
|
+
|
|
363
|
+
const el = document.createElement('dile-crud-list');
|
|
364
|
+
el.disableLoadOnStart = true;
|
|
365
|
+
el.config = config;
|
|
366
|
+
el.elements = sampleElements;
|
|
367
|
+
el.loading = false;
|
|
368
|
+
document.body.appendChild(el);
|
|
369
|
+
await el.updateComplete;
|
|
370
|
+
|
|
371
|
+
expect(el.shadowRoot.querySelector('dile-crud-data-grid')).toBeTruthy();
|
|
372
|
+
});
|
|
319
373
|
});
|
|
@@ -9,6 +9,7 @@ import '../crud-select-all.js';
|
|
|
9
9
|
import '../crud-list-service.js';
|
|
10
10
|
import '../crud-data-grid.js';
|
|
11
11
|
import { DileI18nMixin } from '../../../lib/DileI18nMixin.js';
|
|
12
|
+
import { hasGridView, hasItemView } from '../../../lib/listViewMode.js';
|
|
12
13
|
|
|
13
14
|
export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
|
|
14
15
|
static styles = [
|
|
@@ -95,6 +96,7 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
|
|
|
95
96
|
relationId: { type: String },
|
|
96
97
|
disableLoadOnStart: { type: Boolean },
|
|
97
98
|
listError: { type: Object },
|
|
99
|
+
viewMode: { type: String },
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -112,6 +114,7 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
|
|
|
112
114
|
this.loading = true;
|
|
113
115
|
this.disableLoadOnStart = false;
|
|
114
116
|
this.listError = null;
|
|
117
|
+
this.viewMode = 'item';
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
firstUpdated() {
|
|
@@ -250,7 +253,12 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
|
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
get elementsTemplate() {
|
|
253
|
-
|
|
256
|
+
// When both a grid and an item view are configured, the item template is
|
|
257
|
+
// shown by default and the grid is only rendered once the user switches
|
|
258
|
+
// view via dile-crud's navActionsTemplate (see listViewMode.js).
|
|
259
|
+
const showGrid = hasGridView(this.config) && (!hasItemView(this.config) || this.viewMode === 'grid');
|
|
260
|
+
|
|
261
|
+
if (showGrid && this.config?.templates?.grid && typeof this.config.templates.grid === 'function') {
|
|
254
262
|
return html`
|
|
255
263
|
<div
|
|
256
264
|
class="grid-container"
|
|
@@ -261,7 +269,7 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
|
|
|
261
269
|
</div>
|
|
262
270
|
`;
|
|
263
271
|
}
|
|
264
|
-
if (this.config?.grid?.columns && Array.isArray(this.config.grid.columns)) {
|
|
272
|
+
if (showGrid && this.config?.grid?.columns && Array.isArray(this.config.grid.columns)) {
|
|
265
273
|
return html`
|
|
266
274
|
<div
|
|
267
275
|
class="grid-container"
|
|
@@ -25,6 +25,10 @@ export class DileCrudListOptions extends LitElement {
|
|
|
25
25
|
cursor: pointer;
|
|
26
26
|
--dile-icon-rounded-background-color: var(--dile-crud-action-color, #888);
|
|
27
27
|
}
|
|
28
|
+
dile-icon:focus-visible {
|
|
29
|
+
outline: 2px solid var(--dile-crud-action-focus-color, #12354d);
|
|
30
|
+
outline-offset: 2px;
|
|
31
|
+
}
|
|
28
32
|
@media (min-width: 605px) {
|
|
29
33
|
dile-button-icon {
|
|
30
34
|
display: block;
|
|
@@ -47,7 +51,15 @@ export class DileCrudListOptions extends LitElement {
|
|
|
47
51
|
return html`
|
|
48
52
|
<dile-menu-overlay horizontalAlign="under_right" verticalAlign="bottom">
|
|
49
53
|
<div slot="trigger">
|
|
50
|
-
<dile-icon
|
|
54
|
+
<dile-icon
|
|
55
|
+
rounded
|
|
56
|
+
tabindex="0"
|
|
57
|
+
role="button"
|
|
58
|
+
aria-label="${this.label}"
|
|
59
|
+
title="${this.label}"
|
|
60
|
+
.icon="${this.icon}"
|
|
61
|
+
@keydown=${this.iconKeydown}
|
|
62
|
+
></dile-icon>
|
|
51
63
|
<dile-button-icon label="${this.label}" .icon="${this.icon}" gray>${this.label}</dile-button-icon>
|
|
52
64
|
</div>
|
|
53
65
|
<div class="content" slot="content">
|
|
@@ -57,8 +69,18 @@ export class DileCrudListOptions extends LitElement {
|
|
|
57
69
|
`;
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
// The small-screen dile-icon has no native focus/activation support, unlike the
|
|
73
|
+
// real <button> inside dile-button-icon shown at wider widths, so it needs its
|
|
74
|
+
// own keyboard handling; the resulting click bubbles up to dile-menu-overlay's
|
|
75
|
+
// trigger listener the same way a pointer click already does.
|
|
76
|
+
iconKeydown(e) {
|
|
77
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
e.target.click();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
60
82
|
|
|
61
83
|
close() {
|
|
62
|
-
this.shadowRoot.querySelector('dile-menu-overlay').close();
|
|
63
|
-
}
|
|
84
|
+
this.shadowRoot.querySelector('dile-menu-overlay').close();
|
|
85
|
+
}
|
|
64
86
|
}
|
package/lib/defaultConfig.js
CHANGED
package/lib/i18n/en.js
CHANGED
package/lib/i18n/es.js
CHANGED
|
@@ -72,4 +72,6 @@ export const translations = {
|
|
|
72
72
|
select_relation_placeholder: "Selecciona...",
|
|
73
73
|
list_load_error: "Error al cargar la lista. Por favor, intenta de nuevo.",
|
|
74
74
|
retry_label: "Reintentar",
|
|
75
|
+
list_view_label: "Vista de lista",
|
|
76
|
+
grid_view_label: "Vista de cuadrícula",
|
|
75
77
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defaultConfig } from './defaultConfig.js';
|
|
2
|
+
|
|
3
|
+
export function hasGridView(config) {
|
|
4
|
+
return typeof config?.templates?.grid === 'function'
|
|
5
|
+
|| Array.isArray(config?.grid?.columns);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function hasItemView(config) {
|
|
9
|
+
return typeof config?.templates?.item === 'function'
|
|
10
|
+
&& config.templates.item !== defaultConfig.templates.item;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function canSwitchListView(config) {
|
|
14
|
+
return hasGridView(config)
|
|
15
|
+
&& hasItemView(config)
|
|
16
|
+
&& !config?.customization?.disableListWiewSwitch;
|
|
17
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
import { CrudConfigBuilder } from './CrudConfigBuilder.js';
|
|
4
|
+
import { hasGridView, hasItemView, canSwitchListView } from './listViewMode.js';
|
|
5
|
+
|
|
6
|
+
describe('listViewMode', () => {
|
|
7
|
+
it('detects a grid view from templates.grid', () => {
|
|
8
|
+
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
9
|
+
templates: { grid: () => html`` },
|
|
10
|
+
}).getConfig();
|
|
11
|
+
|
|
12
|
+
expect(hasGridView(config)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('detects a grid view from grid.columns', () => {
|
|
16
|
+
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
17
|
+
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
18
|
+
}).getConfig();
|
|
19
|
+
|
|
20
|
+
expect(hasGridView(config)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('reports no grid view when neither is configured', () => {
|
|
24
|
+
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
25
|
+
templates: { item: () => html`` },
|
|
26
|
+
}).getConfig();
|
|
27
|
+
|
|
28
|
+
expect(hasGridView(config)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('detects an item view only when templates.item was explicitly configured', () => {
|
|
32
|
+
const withItem = new CrudConfigBuilder('https://example.test/api', {
|
|
33
|
+
templates: { item: () => html`` },
|
|
34
|
+
}).getConfig();
|
|
35
|
+
const withoutItem = new CrudConfigBuilder('https://example.test/api', {
|
|
36
|
+
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
37
|
+
}).getConfig();
|
|
38
|
+
|
|
39
|
+
expect(hasItemView(withItem)).toBe(true);
|
|
40
|
+
expect(hasItemView(withoutItem)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('allows switching only when both views are configured and the switch is not disabled', () => {
|
|
44
|
+
const both = new CrudConfigBuilder('https://example.test/api', {
|
|
45
|
+
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
46
|
+
templates: { item: () => html`` },
|
|
47
|
+
}).getConfig();
|
|
48
|
+
|
|
49
|
+
expect(canSwitchListView(both)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('disallows switching when customization.disableListWiewSwitch is true', () => {
|
|
53
|
+
const config = new CrudConfigBuilder('https://example.test/api', {
|
|
54
|
+
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
55
|
+
templates: { item: () => html`` },
|
|
56
|
+
customization: { disableListWiewSwitch: true },
|
|
57
|
+
}).getConfig();
|
|
58
|
+
|
|
59
|
+
expect(canSwitchListView(config)).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('disallows switching when only one view is configured', () => {
|
|
63
|
+
const gridOnly = new CrudConfigBuilder('https://example.test/api', {
|
|
64
|
+
grid: { columns: [{ field: 'id', header: 'ID' }] },
|
|
65
|
+
}).getConfig();
|
|
66
|
+
const itemOnly = new CrudConfigBuilder('https://example.test/api', {
|
|
67
|
+
templates: { item: () => html`` },
|
|
68
|
+
}).getConfig();
|
|
69
|
+
|
|
70
|
+
expect(canSwitchListView(gridOnly)).toBe(false);
|
|
71
|
+
expect(canSwitchListView(itemOnly)).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/crud",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Components to create a generic crud system based on Web Components and Lit",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://dile-components.com/",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@dile/ui": "^3.12.
|
|
28
|
+
"@dile/ui": "^3.12.4",
|
|
29
29
|
"axios": "^1.19.0",
|
|
30
30
|
"lit": "^2.7.0 || ^3.0.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "305c109a3c920517956067fb52e51fa75e55896f"
|
|
36
36
|
}
|