@dile/crud 2.7.6 → 2.8.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/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 +82 -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 +2 -2
|
@@ -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,29 @@ 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
|
+
--dile-button-background-color: var(--dile-crud-view-switch-background-color, var(--dile-primary-color, #7BB93D));
|
|
78
|
+
--dile-button-text-color: var(--dile-crud-view-switch-text-color, var(--dile-on-primary-color, #fff));
|
|
79
|
+
--dile-button-border-color: var(--dile-crud-view-switch-border-color, var(--dile-primary-dark-color, #12354d));
|
|
80
|
+
}
|
|
81
|
+
.view-switch dile-icon {
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
--dile-icon-color: var(--dile-crud-view-switch-text-color, var(--dile-on-crud-action-color, #fff));
|
|
84
|
+
--dile-icon-rounded-background-color: var(--dile-crud-view-switch-background-color, var(--dile-crud-action-color, #888));
|
|
85
|
+
}
|
|
86
|
+
.view-switch dile-icon:focus-visible {
|
|
87
|
+
outline: 2px solid var(--dile-crud-action-focus-color, #12354d);
|
|
88
|
+
outline-offset: 2px;
|
|
89
|
+
}
|
|
90
|
+
@media (min-width: 605px) {
|
|
91
|
+
.view-switch dile-button-icon {
|
|
92
|
+
display: block;
|
|
93
|
+
}
|
|
94
|
+
.view-switch dile-icon {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
73
98
|
@media(min-width: 400px) {
|
|
74
99
|
.simplecard {
|
|
75
100
|
--dile-card-border: none;
|
|
@@ -99,6 +124,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
99
124
|
relationId: { type: String },
|
|
100
125
|
filtersAlwaysVisible: { type: Boolean },
|
|
101
126
|
singleActionDispatcher: { type: String },
|
|
127
|
+
_viewMode: { state: true },
|
|
102
128
|
};
|
|
103
129
|
}
|
|
104
130
|
|
|
@@ -106,6 +132,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
106
132
|
super();
|
|
107
133
|
this.actionIds = [];
|
|
108
134
|
this.filtersAlwaysVisible = false;
|
|
135
|
+
this._viewMode = 'item';
|
|
109
136
|
}
|
|
110
137
|
|
|
111
138
|
// GETTERS ELEMENTOS
|
|
@@ -216,6 +243,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
216
243
|
@crud-item-restore=${this.itemRestoreRequest}
|
|
217
244
|
@insert-requested=${this.openInsert}
|
|
218
245
|
.actionIds=${this.actionIds}
|
|
246
|
+
.viewMode=${this._viewMode}
|
|
219
247
|
belongsTo=${this.belongsTo}
|
|
220
248
|
relationId=${this.relationId}
|
|
221
249
|
language="${this.language}"
|
|
@@ -241,6 +269,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
241
269
|
: html`<div slot="menu">${this.helpTemplate}</div>`
|
|
242
270
|
}
|
|
243
271
|
<div class="actions" slot="actions">
|
|
272
|
+
${this.canSwitchListView ? this.listViewSwitchTemplate : ''}
|
|
244
273
|
${this.actionsTemplate}
|
|
245
274
|
${this.config.customization.disableFilter || this.filtersAlwaysVisible
|
|
246
275
|
? ''
|
|
@@ -287,6 +316,37 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
287
316
|
`
|
|
288
317
|
}
|
|
289
318
|
|
|
319
|
+
get canSwitchListView() {
|
|
320
|
+
return canSwitchListView(this.config);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
get listViewSwitchTemplate() {
|
|
324
|
+
const label = this._viewMode === 'grid'
|
|
325
|
+
? this.listViewLabelComputed(this.config.labels.listViewAction, this.translations)
|
|
326
|
+
: this.gridViewLabelComputed(this.config.labels.gridViewAction, this.translations);
|
|
327
|
+
const icon = this._viewMode === 'grid' ? listIcon : appsIcon;
|
|
328
|
+
return html`
|
|
329
|
+
<div class="view-switch action-controller">
|
|
330
|
+
<dile-icon
|
|
331
|
+
rounded
|
|
332
|
+
tabindex="0"
|
|
333
|
+
role="button"
|
|
334
|
+
aria-label="${label}"
|
|
335
|
+
title="${label}"
|
|
336
|
+
.icon=${icon}
|
|
337
|
+
@click=${this.toggleListView}
|
|
338
|
+
@keydown=${this.viewSwitchIconKeydown}
|
|
339
|
+
></dile-icon>
|
|
340
|
+
<dile-button-icon
|
|
341
|
+
@click=${this.toggleListView}
|
|
342
|
+
.icon=${icon}
|
|
343
|
+
>
|
|
344
|
+
${label}
|
|
345
|
+
</dile-button-icon>
|
|
346
|
+
</div>
|
|
347
|
+
`
|
|
348
|
+
}
|
|
349
|
+
|
|
290
350
|
get filtersAlwaysVisibleTemplate() {
|
|
291
351
|
return html`
|
|
292
352
|
<dile-crud-filters-inline
|
|
@@ -304,10 +364,30 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
|
|
|
304
364
|
&& (customization.disableFilter || this.filtersAlwaysVisible)
|
|
305
365
|
&& customization.disablePagination
|
|
306
366
|
&& customization.disableSort
|
|
307
|
-
&& customization.hideCheckboxSelection
|
|
367
|
+
&& customization.hideCheckboxSelection
|
|
368
|
+
&& !this.canSwitchListView;
|
|
308
369
|
}
|
|
309
370
|
// BEHAVIOURS
|
|
310
371
|
|
|
372
|
+
toggleListView() {
|
|
373
|
+
this._viewMode = this._viewMode === 'grid' ? 'item' : 'grid';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
viewSwitchIconKeydown(e) {
|
|
377
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
378
|
+
e.preventDefault();
|
|
379
|
+
this.toggleListView();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
listViewLabelComputed(label, translations) {
|
|
384
|
+
return label ? label : translations?.list_view_label ? translations.list_view_label : 'List view';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
gridViewLabelComputed(label, translations) {
|
|
388
|
+
return label ? label : translations?.grid_view_label ? translations.grid_view_label : 'Grid view';
|
|
389
|
+
}
|
|
390
|
+
|
|
311
391
|
openInsert() {
|
|
312
392
|
this.dispatchEvent(new CustomEvent('crud-item-insert', { bubbles: true, composed: true }));
|
|
313
393
|
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
|
@@ -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.1",
|
|
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",
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "a6cf598429db0f791f20a77f64ae14f10d42cf56"
|
|
36
36
|
}
|