@dile/crud 2.7.1 → 2.7.3

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.
@@ -132,3 +132,145 @@ describe('dile-crud initial filters', () => {
132
132
  expect(select.value).toBe('');
133
133
  });
134
134
  });
135
+
136
+ describe('dile-crud sort synchronization', () => {
137
+ afterEach(() => {
138
+ document.body.innerHTML = '';
139
+ });
140
+
141
+ it('syncs dile-crud-sort-form when the list sort changes (e.g. from a DataGrid column header)', async () => {
142
+ const config = new CrudConfigBuilder('https://example.test/api/customers', {
143
+ customization: {
144
+ disableInsert: true,
145
+ disableHelp: true,
146
+ disableKeywordSearch: true,
147
+ disableSort: false,
148
+ disableFilter: true,
149
+ disablePagination: true,
150
+ hideCheckboxSelection: true,
151
+ },
152
+ sort: {
153
+ options: [
154
+ { name: 'name', label: 'Name', direction: 'asc' },
155
+ ],
156
+ initialSortField: null,
157
+ },
158
+ }).getConfig();
159
+
160
+ const el = document.createElement('dile-crud');
161
+ el.config = config;
162
+ document.body.appendChild(el);
163
+ await el.updateComplete;
164
+ await Promise.resolve();
165
+
166
+ expect(el.sortFormElement).toBeTruthy();
167
+
168
+ el.listElement.setSort({ sortField: 'name', sortDirection: 'desc' });
169
+ await el.updateComplete;
170
+
171
+ expect(el.sortFormElement.sortField).toBe('name');
172
+ expect(el.sortFormElement.sortDirection).toBe('desc');
173
+ });
174
+
175
+ it('ignores a grid sort field that is not listed in config.sort.options instead of crashing the sort-form', async () => {
176
+ // Reproduces a DataGrid column marked `sortable: true` for a field (e.g. "continent")
177
+ // that has no corresponding entry in config.sort.options.
178
+ const config = new CrudConfigBuilder('https://example.test/api/countries', {
179
+ customization: {
180
+ disableInsert: true,
181
+ disableHelp: true,
182
+ disableKeywordSearch: true,
183
+ disableSort: false,
184
+ disableFilter: true,
185
+ disablePagination: true,
186
+ hideCheckboxSelection: true,
187
+ },
188
+ grid: {
189
+ columns: [
190
+ { field: 'name', header: 'Name', sortable: true },
191
+ { field: 'continent', header: 'Continent', sortable: true },
192
+ ],
193
+ },
194
+ sort: {
195
+ options: [
196
+ { name: 'name', label: 'Name', direction: 'asc' },
197
+ ],
198
+ initialSortField: 'name',
199
+ },
200
+ }).getConfig();
201
+
202
+ const el = document.createElement('dile-crud');
203
+ el.config = config;
204
+ document.body.appendChild(el);
205
+ await el.updateComplete;
206
+ await Promise.resolve();
207
+
208
+ const initialSortField = el.sortFormElement.sortField;
209
+
210
+ expect(() => {
211
+ el.listElement.dispatchEvent(new CustomEvent('crud-list-sort-changed', {
212
+ bubbles: true,
213
+ composed: true,
214
+ detail: { sortField: 'continent', sortDirection: 'asc' },
215
+ }));
216
+ }).not.toThrow();
217
+ await el.updateComplete;
218
+
219
+ // The sort-form's selection is left untouched rather than pointed at an unknown option.
220
+ expect(el.sortFormElement.sortField).toBe(initialSortField);
221
+ });
222
+
223
+ it('does not let the sort-form echo override a grid-driven sort with the field static configured default direction', async () => {
224
+ // "year" is configured with a static default direction of "desc". A DataGrid column
225
+ // header click always starts at "asc" on first click — reproduces the exact scenario
226
+ // where the sort silently snapped back to "desc" and repeated clicks appeared to
227
+ // "lose" the requested order.
228
+ const config = new CrudConfigBuilder('https://example.test/api/board-games', {
229
+ customization: {
230
+ disableInsert: true,
231
+ disableHelp: true,
232
+ disableKeywordSearch: true,
233
+ disableSort: false,
234
+ disableFilter: true,
235
+ disablePagination: true,
236
+ hideCheckboxSelection: true,
237
+ },
238
+ grid: {
239
+ columns: [
240
+ { field: 'year', header: 'Year', sortable: true },
241
+ ],
242
+ },
243
+ sort: {
244
+ options: [
245
+ { name: 'year', label: 'Year', direction: 'desc' },
246
+ ],
247
+ initialSortField: 'year',
248
+ },
249
+ }).getConfig();
250
+
251
+ const el = document.createElement('dile-crud');
252
+ el.config = config;
253
+ document.body.appendChild(el);
254
+ await el.updateComplete;
255
+ await Promise.resolve();
256
+
257
+ let lastSetSort = null;
258
+ el.listElement.setSort = (sortObj) => {
259
+ lastSetSort = sortObj;
260
+ };
261
+
262
+ // Simulate what DileCrudList.setSort() dispatches after a grid header click requesting "asc".
263
+ el.listElement.dispatchEvent(new CustomEvent('crud-list-sort-changed', {
264
+ bubbles: true,
265
+ composed: true,
266
+ detail: { sortField: 'year', sortDirection: 'asc' },
267
+ }));
268
+ await el.updateComplete;
269
+ await new Promise((resolve) => setTimeout(resolve, 0)); // flush any echo microtasks
270
+
271
+ expect(el.sortFormElement.sortField).toBe('year');
272
+ expect(el.sortFormElement.sortDirection).toBe('asc');
273
+ // The echo must not have triggered a second, corrupting setSort() call.
274
+ expect(lastSetSort).toBeNull();
275
+ });
276
+ });
@@ -124,6 +124,9 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
124
124
  get filtersElement() {
125
125
  return this.shadowRoot.getElementById('elfilters');
126
126
  }
127
+ get sortFormElement() {
128
+ return this.shadowRoot.getElementById('elsort');
129
+ }
127
130
 
128
131
  // TEMPLATES
129
132
 
@@ -145,6 +148,7 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
145
148
  @item-checkbox-changed=${this.itemCheckboxChanged}
146
149
  @dile-chip-icon-click=${this.removeFilter}
147
150
  @crud-list-all-ids-selected=${this.crudSelectAll}
151
+ @crud-list-sort-changed=${this.listSortChanged}
148
152
  >
149
153
  ${this.listTemplate}
150
154
  </div>
@@ -265,14 +269,15 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
265
269
  ${this.config.customization.disableSort
266
270
  ? ''
267
271
  : html`
268
- <dile-crud-sort-form
272
+ <dile-crud-sort-form
273
+ id="elsort"
269
274
  class="action-controller"
270
- .sortOptions=${this.config.sort.options || []}
275
+ .sortOptions=${this.config.sort.options || []}
271
276
  sortField="${this.config.sort.initialSortField}"
272
277
  sortDirection="${this.config.sort.initialSortDirection || 'desc'}"
273
278
  @sort-changed=${this.sortFormChanged}
274
279
  language="${this.language}"
275
- ></dile-crud-sort-form>
280
+ ></dile-crud-sort-form>
276
281
  `
277
282
  }
278
283
  </div>
@@ -343,6 +348,21 @@ export class DileCrud extends DileI18nMixin(DileCrudMixin(LitElement)) {
343
348
  this.listElement.setSort(e.detail);
344
349
  }
345
350
 
351
+ listSortChanged(e) {
352
+ if (!this.sortFormElement) {
353
+ return;
354
+ }
355
+ const sortField = e.detail?.sortField;
356
+ const isKnownSortOption = !sortField || (this.config.sort?.options || []).some(option => option.name === sortField);
357
+ if (!isKnownSortOption) {
358
+ // The sort came from a source the sort-form doesn't know about (e.g. a DataGrid
359
+ // column not listed in config.sort.options) — leave the form's selection as-is
360
+ // instead of pointing it at an option it can't render.
361
+ return;
362
+ }
363
+ this.sortFormElement.syncSort(sortField, e.detail?.sortDirection);
364
+ }
365
+
346
366
  pageSizeChanged(e) {
347
367
  this.listElement.setPageSize(e.detail.pageSize);
348
368
  }
@@ -24,6 +24,9 @@ describe('dile-crud-data-grid', () => {
24
24
  el.config = config;
25
25
  el.items = props.items ?? sampleItems;
26
26
  el.selectedIds = props.selectedIds ?? [];
27
+ if (props.sort !== undefined) {
28
+ el.sort = props.sort;
29
+ }
27
30
  document.body.appendChild(el);
28
31
  await el.updateComplete;
29
32
  return el;
@@ -147,6 +150,18 @@ describe('dile-crud-data-grid', () => {
147
150
  expect(innerGrid(el).sortMode).toBe('external');
148
151
  });
149
152
 
153
+ it('forwards the sort property as sortField/sortDirection so the header keeps its sorted state', async () => {
154
+ const el = await createGrid({ grid: { columns: sampleColumns } }, { sort: { sortField: 'name', sortDirection: 'desc' } });
155
+ expect(innerGrid(el).sortField).toBe('name');
156
+ expect(innerGrid(el).sortDirection).toBe('desc');
157
+ });
158
+
159
+ it('defaults sortField/sortDirection to empty strings when sort is not set', async () => {
160
+ const el = await createGrid({ grid: { columns: sampleColumns } });
161
+ expect(innerGrid(el).sortField).toBe('');
162
+ expect(innerGrid(el).sortDirection).toBe('');
163
+ });
164
+
150
165
  it('wires computeRowId from config.computeItemId so selection matches custom ids', async () => {
151
166
  const el = await createGrid(
152
167
  {
@@ -52,6 +52,7 @@ describe('dile-crud-list grid template integration', () => {
52
52
  el.disableLoadOnStart = true;
53
53
  el.config = config;
54
54
  el.elements = sampleElements;
55
+ el.sort = { sortField: 'name', sortDirection: 'desc' };
55
56
  el.loading = false;
56
57
  document.body.appendChild(el);
57
58
  await el.updateComplete;
@@ -64,6 +65,7 @@ describe('dile-crud-list grid template integration', () => {
64
65
  expect(crudDataGrid.items).toEqual(sampleElements);
65
66
  expect(crudDataGrid.columns.length).toBe(3); // id, name + automatically injected Actions
66
67
  expect(crudDataGrid.columns[2].header).toBe('Actions');
68
+ expect(crudDataGrid.sort).toEqual({ sortField: 'name', sortDirection: 'desc' });
67
69
  });
68
70
 
69
71
  it('hides actions in traditional list items when disableListActions is true', async () => {
@@ -96,12 +98,14 @@ describe('dile-crud-list grid template integration', () => {
96
98
  it('renders custom grid template when templates.grid is configured', async () => {
97
99
  let receivedElements = null;
98
100
  let receivedActionIds = null;
101
+ let receivedSort = null;
99
102
 
100
103
  const config = new CrudConfigBuilder('https://example.test/api/customers', {
101
104
  templates: {
102
- grid: (elements, actionIds) => {
105
+ grid: (elements, actionIds, config, sort) => {
103
106
  receivedElements = elements;
104
107
  receivedActionIds = actionIds;
108
+ receivedSort = sort;
105
109
  return html`
106
110
  <dile-data-grid
107
111
  .items="${elements}"
@@ -122,6 +126,7 @@ describe('dile-crud-list grid template integration', () => {
122
126
  el.config = config;
123
127
  el.elements = sampleElements;
124
128
  el.actionIds = [2];
129
+ el.sort = { sortField: 'name', sortDirection: 'asc' };
125
130
  el.loading = false;
126
131
  document.body.appendChild(el);
127
132
  await el.updateComplete;
@@ -136,6 +141,7 @@ describe('dile-crud-list grid template integration', () => {
136
141
  expect(dataGrid).toBeTruthy();
137
142
  expect(receivedElements).toEqual(sampleElements);
138
143
  expect(receivedActionIds).toEqual([2]);
144
+ expect(receivedSort).toEqual({ sortField: 'name', sortDirection: 'asc' });
139
145
  });
140
146
 
141
147
  it('updates sort when dile-data-grid-sort is dispatched', async () => {
@@ -252,4 +258,62 @@ describe('dile-crud-list grid template integration', () => {
252
258
  expect(el.shadowRoot.querySelector('dile-crud-data-grid')).toBeNull();
253
259
  expect(el.shadowRoot.querySelector('dile-data-grid')).toBeTruthy();
254
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
+ });
255
319
  });
@@ -17,6 +17,7 @@ export class DileCrudDataGrid extends LitElement {
17
17
  items: { type: Array },
18
18
  selectedIds: { type: Array },
19
19
  config: { type: Object },
20
+ sort: { type: Object },
20
21
  };
21
22
  }
22
23
 
@@ -25,6 +26,7 @@ export class DileCrudDataGrid extends LitElement {
25
26
  this.items = [];
26
27
  this.selectedIds = [];
27
28
  this.config = null;
29
+ this.sort = null;
28
30
  }
29
31
 
30
32
  get columns() {
@@ -77,6 +79,8 @@ export class DileCrudDataGrid extends LitElement {
77
79
  .selectedIds=${this.selectedIds}
78
80
  .computeRowId=${this.computeRowId}
79
81
  .rowClass=${this.rowClass}
82
+ .sortField=${this.sort?.sortField || ''}
83
+ .sortDirection=${this.sort?.sortDirection || ''}
80
84
  ?selectable=${gridConfig.selectable !== undefined ? gridConfig.selectable : !this.config?.customization?.hideCheckboxSelection}
81
85
  ?sticky-first-column=${gridConfig.stickyFirstColumn !== undefined ? gridConfig.stickyFirstColumn : true}
82
86
  ?striped=${gridConfig.striped !== undefined ? gridConfig.striped : true}
@@ -257,7 +257,7 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
257
257
  @item-checkbox-changed=${this.onItemsCheckboxChanged}
258
258
  @dile-data-grid-sort=${this.onGridSort}
259
259
  >
260
- ${this.config.templates.grid(this.elements, this.actionIds, this.config)}
260
+ ${this.config.templates.grid(this.elements, this.actionIds, this.config, this.sort)}
261
261
  </div>
262
262
  `;
263
263
  }
@@ -272,6 +272,7 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
272
272
  .items=${this.elements}
273
273
  .selectedIds=${this.actionIds}
274
274
  .config=${this.config}
275
+ .sort=${this.sort}
275
276
  ></dile-crud-data-grid>
276
277
  </div>
277
278
  `;
@@ -377,7 +378,12 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
377
378
  this.loading = true;
378
379
  this.sort = sortObject;
379
380
  this.elservice.setSort(sortObject);
380
- }
381
+ this.dispatchEvent(new CustomEvent('crud-list-sort-changed', {
382
+ bubbles: true,
383
+ composed: true,
384
+ detail: sortObject,
385
+ }));
386
+ }
381
387
 
382
388
  onGridSort(e) {
383
389
  if (e.detail?.field) {
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './crud-sort-form.js';
3
+
4
+ describe('dile-crud-sort-form', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ const sortOptions = [
10
+ { name: 'name', label: 'Name', direction: 'asc' },
11
+ { name: 'id', label: 'Id', direction: 'asc' },
12
+ ];
13
+
14
+ async function createSortForm(props = {}) {
15
+ const el = document.createElement('dile-crud-sort-form');
16
+ el.sortOptions = props.sortOptions ?? sortOptions;
17
+ if (props.sortField !== undefined) {
18
+ el.sortField = props.sortField;
19
+ }
20
+ document.body.appendChild(el);
21
+ await el.updateComplete;
22
+ return el;
23
+ }
24
+
25
+ it('renders one radio/order-switch pair per configured sort option', async () => {
26
+ const el = await createSortForm();
27
+ expect(el.shadowRoot.querySelectorAll('dile-radio').length).toBe(2);
28
+ expect(el.shadowRoot.querySelectorAll('dile-order-switch').length).toBe(2);
29
+ });
30
+
31
+ it('getDirection returns the configured direction for a known field', async () => {
32
+ const el = await createSortForm();
33
+ await el.shadowRoot.querySelector('dile-order-switch[name="name"]').updateComplete;
34
+ expect(el.getDirection('name')).toBe('asc');
35
+ });
36
+
37
+ it('getDirection returns undefined instead of throwing for a field with no matching order-switch', async () => {
38
+ // Reproduces a DataGrid column marked sortable whose field isn't listed in config.sort.options.
39
+ const el = await createSortForm();
40
+ expect(() => el.getDirection('continent')).not.toThrow();
41
+ expect(el.getDirection('continent')).toBeUndefined();
42
+ });
43
+
44
+ it('getDirection returns undefined for a falsy field without touching the DOM', async () => {
45
+ const el = await createSortForm();
46
+ expect(el.getDirection(null)).toBeUndefined();
47
+ expect(el.getDirection('')).toBeUndefined();
48
+ });
49
+
50
+ // "year" has a static configured direction of "desc" so it can expose the regression:
51
+ // syncing an "asc" sort must not get silently corrected back to "desc".
52
+ const yearOptions = [{ name: 'year', label: 'Year', direction: 'desc' }];
53
+
54
+ it('syncSort reflects an externally-applied sort without re-dispatching sort-changed', async () => {
55
+ const el = await createSortForm({ sortOptions: yearOptions });
56
+
57
+ let dispatchCount = 0;
58
+ el.addEventListener('sort-changed', () => { dispatchCount++; });
59
+
60
+ el.syncSort('year', 'asc');
61
+ await el.updateComplete;
62
+
63
+ expect(el.sortField).toBe('year');
64
+ expect(el.sortDirection).toBe('asc');
65
+ expect(dispatchCount).toBe(0);
66
+ });
67
+
68
+ it('a genuine radio-group selection (not preceded by syncSort) still defaults to the option configured direction', async () => {
69
+ const el = await createSortForm({ sortOptions: yearOptions });
70
+
71
+ let receivedDetail = null;
72
+ el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
73
+
74
+ const radioGroup = el.shadowRoot.querySelector('dile-radio-group');
75
+ radioGroup.dispatchEvent(new CustomEvent('dile-radio-group-changed', {
76
+ detail: { name: 'selector', value: 'year' },
77
+ }));
78
+ await el.updateComplete;
79
+
80
+ expect(el.sortField).toBe('year');
81
+ expect(el.sortDirection).toBe('desc');
82
+ expect(receivedDetail).toEqual({ sortField: 'year', sortDirection: 'desc' });
83
+ });
84
+
85
+ it('clicking the label of an inactive option switches to it using its own configured direction, without flipping it', async () => {
86
+ const el = await createSortForm({
87
+ sortOptions: [
88
+ { name: 'name', label: 'Name', direction: 'asc' },
89
+ { name: 'year', label: 'Year', direction: 'desc' },
90
+ ],
91
+ sortField: 'name',
92
+ });
93
+ el.sortDirection = 'asc';
94
+ await el.updateComplete;
95
+
96
+ let receivedDetail = null;
97
+ el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
98
+
99
+ el.shadowRoot.querySelector('dile-order-switch[name="year"]').shadowRoot.querySelector('span').click();
100
+ await el.updateComplete;
101
+
102
+ expect(el.sortField).toBe('year');
103
+ expect(el.sortDirection).toBe('desc'); // year's own configured default, not flipped
104
+ expect(receivedDetail).toEqual({ sortField: 'year', sortDirection: 'desc' });
105
+ });
106
+
107
+ it('clicking the label of the already-active option toggles its direction instead of doing nothing', async () => {
108
+ // Reproduces the reported confusion: clicking "Name" again while already sorted by
109
+ // name silently did nothing, because the label always re-announced the same value.
110
+ const el = await createSortForm({
111
+ sortOptions: [{ name: 'name', label: 'Name', direction: 'asc' }],
112
+ sortField: 'name',
113
+ });
114
+ el.sortDirection = 'asc';
115
+ await el.updateComplete;
116
+
117
+ let receivedDetail = null;
118
+ el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
119
+
120
+ const label = () => el.shadowRoot.querySelector('dile-order-switch[name="name"]').shadowRoot.querySelector('span');
121
+
122
+ label().click();
123
+ await el.updateComplete;
124
+ expect(el.sortField).toBe('name');
125
+ expect(el.sortDirection).toBe('desc'); // toggled, not a no-op
126
+ expect(receivedDetail).toEqual({ sortField: 'name', sortDirection: 'desc' });
127
+
128
+ label().click();
129
+ await el.updateComplete;
130
+ expect(el.sortDirection).toBe('asc'); // toggles back on a second click
131
+ });
132
+ });
@@ -45,6 +45,19 @@ export class DileCrudSortForm extends DileI18nMixin(LitElement) {
45
45
  constructor() {
46
46
  super();
47
47
  this.sortOptions = [];
48
+ this._suppressChangeEcho = false;
49
+ }
50
+
51
+ // Reflects a sort that was already applied elsewhere (e.g. a DataGrid column header
52
+ // click) without re-dispatching sort-changed. Setting `sortField` below causes the
53
+ // inner <dile-radio-group> to echo a "changed" event on its own (it can't tell a
54
+ // programmatic update from a user click), which radioGroupChanged() would otherwise
55
+ // treat as a real selection and re-derive sortDirection from the option's *static*
56
+ // configured default — silently overriding whichever direction was actually requested.
57
+ syncSort(sortField, sortDirection) {
58
+ this._suppressChangeEcho = true;
59
+ this.sortField = sortField;
60
+ this.sortDirection = sortDirection;
48
61
  }
49
62
 
50
63
  render() {
@@ -63,10 +76,11 @@ export class DileCrudSortForm extends DileI18nMixin(LitElement) {
63
76
  ?checked=${this.sortField === option.name}
64
77
  value="${option.name}"
65
78
  ></dile-radio>
66
- <dile-order-switch
67
- label="${option.label}"
68
- name="${option.name}"
69
- value="${option.direction}"
79
+ <dile-order-switch
80
+ label="${option.label}"
81
+ name="${option.name}"
82
+ value="${option.direction}"
83
+ ?selected=${this.sortField === option.name}
70
84
  @element-changed=${this.elementChanged}
71
85
  ></dile-order-switch>
72
86
  </div>
@@ -83,6 +97,10 @@ export class DileCrudSortForm extends DileI18nMixin(LitElement) {
83
97
  }
84
98
 
85
99
  radioGroupChanged(e) {
100
+ if (this._suppressChangeEcho) {
101
+ this._suppressChangeEcho = false;
102
+ return;
103
+ }
86
104
  this.sortField = e.detail.value;
87
105
  this.sortDirection = this.getDirection(this.sortField);
88
106
  this.dispatchChanged();
@@ -90,7 +108,8 @@ export class DileCrudSortForm extends DileI18nMixin(LitElement) {
90
108
 
91
109
  getDirection(field) {
92
110
  if(field) {
93
- return this.shadowRoot.querySelector(`dile-order-switch[name="${field}"]`).value;
111
+ const orderSwitch = this.shadowRoot.querySelector(`dile-order-switch[name="${field}"]`);
112
+ return orderSwitch ? orderSwitch.value : undefined;
94
113
  }
95
114
  }
96
115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/crud",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
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.1",
28
+ "@dile/ui": "^3.12.3",
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": "add1124d218762f20cd86bd72bb04562541f9833"
35
+ "gitHead": "6ae2b6723c619ec22ce949bcb1bd730a596ef45f"
36
36
  }