diff --git a/src/Autocomplete/assets/dist/controller.js b/src/Autocomplete/assets/dist/controller.js index f7536c0036b..e744a149d84 100644 --- a/src/Autocomplete/assets/dist/controller.js +++ b/src/Autocomplete/assets/dist/controller.js @@ -188,11 +188,9 @@ class default_1 extends Controller { } createOptionsDataStructure(selectElement) { return Array.from(selectElement.options).map((option) => { - const optgroup = option.closest('optgroup'); return { value: option.value, text: option.text, - group: optgroup ? optgroup.label : null, }; }); } @@ -209,7 +207,7 @@ class default_1 extends Controller { if (filteredOriginalOptions.length !== filteredNewOptions.length) { return false; } - const normalizeOption = (option) => `${option.value}-${option.text}-${option.group}`; + const normalizeOption = (option) => `${option.value}-${option.text}`; const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption)); const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption)); return (originalOptionsSet.size === newOptionsSet.size && @@ -240,6 +238,40 @@ _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _def this.tomSelect.setTextboxValue(''); }, closeAfterSelect: true, + onOptionAdd: (value, data) => { + let parentElement = this.tomSelect.input; + let optgroupData = null; + const optgroup = data[this.tomSelect.settings.optgroupField]; + if (optgroup && this.tomSelect.optgroups) { + optgroupData = this.tomSelect.optgroups[optgroup]; + if (optgroupData) { + const optgroupElement = parentElement.querySelector(`optgroup[label="${optgroupData.label}"]`); + if (optgroupElement) { + parentElement = optgroupElement; + } + } + } + const optionElement = document.createElement('option'); + optionElement.value = value; + optionElement.text = data[this.tomSelect.settings.labelField]; + const optionOrder = data['$order']; + let orderedOption = null; + for (const [, tomSelectOption] of Object.entries(this.tomSelect.options)) { + if (tomSelectOption['$order'] === optionOrder) { + orderedOption = parentElement.querySelector(`:scope > option[value="${tomSelectOption[this.tomSelect.settings.valueField]}"]`); + break; + } + } + if (orderedOption) { + orderedOption.insertAdjacentElement('afterend', optionElement); + } + else if (optionOrder >= 0) { + parentElement.append(optionElement); + } + else { + parentElement.prepend(optionElement); + } + }, }; if (!this.selectElement && !this.urlValue) { config.shouldLoad = () => false; diff --git a/src/Autocomplete/assets/src/controller.ts b/src/Autocomplete/assets/src/controller.ts index 8be04e8af14..72e516de332 100644 --- a/src/Autocomplete/assets/src/controller.ts +++ b/src/Autocomplete/assets/src/controller.ts @@ -10,6 +10,10 @@ export interface AutocompleteConnectOptions { tomSelect: TomSelect; options: any; } +interface OptionDataStructure { + value: string; + text: string; +} export default class extends Controller { static values = { @@ -38,7 +42,7 @@ export default class extends Controller { private mutationObserver: MutationObserver; private isObserving = false; private hasLoadedChoicesPreviously = false; - private originalOptions: Array<{ value: string; text: string; group: string | null }> = []; + private originalOptions: Array = []; initialize() { if (!this.mutationObserver) { @@ -146,6 +150,47 @@ export default class extends Controller { this.tomSelect.setTextboxValue(''); }, closeAfterSelect: true, + // fix positioning (in the dropdown) of options added through addOption() + onOptionAdd: (value: string, data: { [key: string]: any }) => { + let parentElement = this.tomSelect.input as Element; + let optgroupData = null; + + const optgroup = data[this.tomSelect.settings.optgroupField]; + if (optgroup && this.tomSelect.optgroups) { + optgroupData = this.tomSelect.optgroups[optgroup]; + if (optgroupData) { + const optgroupElement = parentElement.querySelector(`optgroup[label="${optgroupData.label}"]`); + if (optgroupElement) { + parentElement = optgroupElement; + } + } + } + + const optionElement = document.createElement('option'); + optionElement.value = value; + optionElement.text = data[this.tomSelect.settings.labelField]; + + const optionOrder = data['$order']; + let orderedOption = null; + + for (const [, tomSelectOption] of Object.entries(this.tomSelect.options)) { + if (tomSelectOption['$order'] === optionOrder) { + orderedOption = parentElement.querySelector( + `:scope > option[value="${tomSelectOption[this.tomSelect.settings.valueField]}"]` + ); + + break; + } + } + + if (orderedOption) { + orderedOption.insertAdjacentElement('afterend', optionElement); + } else if (optionOrder >= 0) { + parentElement.append(optionElement); + } else { + parentElement.prepend(optionElement); + } + }, }; // for non-autocompleting input elements, avoid the "No results" message that always shows @@ -411,20 +456,16 @@ export default class extends Controller { } } - private createOptionsDataStructure( - selectElement: HTMLSelectElement - ): Array<{ value: string; text: string; group: string | null }> { + private createOptionsDataStructure(selectElement: HTMLSelectElement): Array { return Array.from(selectElement.options).map((option) => { - const optgroup = option.closest('optgroup'); return { value: option.value, text: option.text, - group: optgroup ? optgroup.label : null, }; }); } - private areOptionsEquivalent(newOptions: Array<{ value: string; text: string; group: string | null }>): boolean { + private areOptionsEquivalent(newOptions: Array): boolean { // remove the empty option, which is added by TomSelect so may be missing from new options const filteredOriginalOptions = this.originalOptions.filter((option) => option.value !== ''); const filteredNewOptions = newOptions.filter((option) => option.value !== ''); @@ -444,8 +485,7 @@ export default class extends Controller { return false; } - const normalizeOption = (option: { value: string; text: string; group: string | null }) => - `${option.value}-${option.text}-${option.group}`; + const normalizeOption = (option: OptionDataStructure) => `${option.value}-${option.text}`; const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption)); const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption));