Skip to content

Commit 7a801fa

Browse files
committed
[Autocomplete] Fix 2 bugs where TomSelect would reset when not necessary
1 parent 30c8dbc commit 7a801fa

File tree

3 files changed

+68
-8
lines changed

3 files changed

+68
-8
lines changed

src/Autocomplete/assets/dist/controller.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class default_1 extends Controller {
116116
}
117117
resetTomSelect() {
118118
if (this.tomSelect) {
119+
this.dispatchEvent('before-reset', { tomSelect: this.tomSelect });
119120
this.stopMutationObserver();
120121
const currentHtml = this.element.innerHTML;
121122
const currentValue = this.tomSelect.getValue();
@@ -143,6 +144,7 @@ class default_1 extends Controller {
143144
subtree: true,
144145
attributes: true,
145146
characterData: true,
147+
attributeOldValue: true,
146148
});
147149
this.isObserving = true;
148150
}
@@ -164,7 +166,11 @@ class default_1 extends Controller {
164166
break;
165167
}
166168
if (mutation.target === this.element && mutation.attributeName === 'multiple') {
167-
requireReset = true;
169+
const isNowMultiple = this.element.hasAttribute('multiple');
170+
const wasMultiple = mutation.oldValue === 'multiple';
171+
if (isNowMultiple !== wasMultiple) {
172+
requireReset = true;
173+
}
168174
break;
169175
}
170176
break;
@@ -191,12 +197,14 @@ class default_1 extends Controller {
191197
});
192198
}
193199
areOptionsEquivalent(newOptions) {
194-
if (this.originalOptions.length !== newOptions.length) {
200+
const filteredOriginalOptions = this.originalOptions.filter((option) => option.value !== '');
201+
const filteredNewOptions = newOptions.filter((option) => option.value !== '');
202+
if (filteredOriginalOptions.length !== filteredNewOptions.length) {
195203
return false;
196204
}
197205
const normalizeOption = (option) => `${option.value}-${option.text}-${option.group}`;
198-
const originalOptionsSet = new Set(this.originalOptions.map(normalizeOption));
199-
const newOptionsSet = new Set(newOptions.map(normalizeOption));
206+
const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption));
207+
const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption));
200208
return (originalOptionsSet.size === newOptionsSet.size &&
201209
[...originalOptionsSet].every((option) => newOptionsSet.has(option)));
202210
}

src/Autocomplete/assets/src/controller.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ export default class extends Controller {
325325

326326
private resetTomSelect(): void {
327327
if (this.tomSelect) {
328+
this.dispatchEvent('before-reset', { tomSelect: this.tomSelect });
328329
this.stopMutationObserver();
329330

330331
// Grab the current HTML then restore it after destroying TomSelect
@@ -358,6 +359,7 @@ export default class extends Controller {
358359
subtree: true,
359360
attributes: true,
360361
characterData: true,
362+
attributeOldValue: true,
361363
});
362364
this.isObserving = true;
363365
}
@@ -384,7 +386,11 @@ export default class extends Controller {
384386
}
385387

386388
if (mutation.target === this.element && mutation.attributeName === 'multiple') {
387-
requireReset = true;
389+
const isNowMultiple = this.element.hasAttribute('multiple');
390+
const wasMultiple = mutation.oldValue === 'multiple';
391+
if (isNowMultiple !== wasMultiple) {
392+
requireReset = true;
393+
}
388394

389395
break;
390396
}
@@ -419,14 +425,18 @@ export default class extends Controller {
419425
}
420426

421427
private areOptionsEquivalent(newOptions: Array<{ value: string; text: string; group: string | null }>): boolean {
422-
if (this.originalOptions.length !== newOptions.length) {
428+
// remove the empty option, which is added by TomSelect so may be missing from new options
429+
const filteredOriginalOptions = this.originalOptions.filter((option) => option.value !== '');
430+
const filteredNewOptions = newOptions.filter((option) => option.value !== '');
431+
432+
if (filteredOriginalOptions.length !== filteredNewOptions.length) {
423433
return false;
424434
}
425435

426436
const normalizeOption = (option: { value: string; text: string; group: string | null }) =>
427437
`${option.value}-${option.text}-${option.group}`;
428-
const originalOptionsSet = new Set(this.originalOptions.map(normalizeOption));
429-
const newOptionsSet = new Set(newOptions.map(normalizeOption));
438+
const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption));
439+
const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption));
430440

431441
return (
432442
originalOptionsSet.size === newOptionsSet.size &&

src/Autocomplete/assets/test/controller.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,4 +815,46 @@ describe('AutocompleteController', () => {
815815
});
816816
expect(getSelectedValues()).toEqual(['2', '3']);
817817
});
818+
819+
it('does not trigger a reset when the style of "multiple" attribute changes', async () => {
820+
const { container } = await startAutocompleteTest(`
821+
<select multiple data-testid='main-element' data-controller='autocomplete'>
822+
<option value=''>Select dogs</option>
823+
<option value='1'>dog1</option>
824+
<option value='2'>dog2</option>
825+
<option value='3'>dog3</option>
826+
</select>
827+
`);
828+
829+
let wasReset = false;
830+
container.addEventListener('autocomplete:before-reset', () => {
831+
wasReset = true;
832+
});
833+
834+
const selectElement = getByTestId(container, 'main-element') as HTMLSelectElement;
835+
selectElement.setAttribute('multiple', 'multiple');
836+
// wait for the mutation observe
837+
await shortDelay(10);
838+
expect(wasReset).toBe(false);
839+
});
840+
841+
it('does not trigger a reset based on the extra, empty select', async () => {
842+
const { container, tomSelect } = await startAutocompleteTest(`
843+
<select data-testid='main-element' data-controller='autocomplete'>
844+
<option value='1'>dog1</option>
845+
<option value='2'>dog2</option>
846+
<option value='3'>dog3</option>
847+
</select>
848+
`);
849+
850+
let wasReset = false;
851+
container.addEventListener('autocomplete:before-reset', () => {
852+
wasReset = true;
853+
});
854+
855+
tomSelect.addItem('2');
856+
// wait for the mutation observe
857+
await shortDelay(10);
858+
expect(wasReset).toBe(false);
859+
});
818860
});

0 commit comments

Comments
 (0)