PageSourceSearch

https://maptools.com/assets/@symfony/ux-autocomplete/controller-FEEd2HP.js

js maptools.com collected 2026-10-02 06:14:06 UTC 13,744 bytes, 349 lines download raw bytes

1import { Controller } from "@hotwired/stimulus";
2import TomSelect from "tom-select";
3function _checkPrivateRedeclaration(e, t) {
4	if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
5}
6function _classPrivateMethodInitSpec(e, a) {
7	_checkPrivateRedeclaration(e, a), a.add(e);
8}
9function _classPrivateFieldInitSpec(e, t, a) {
10	_checkPrivateRedeclaration(e, t), t.set(e, a);
11}
12function _assertClassBrand(e, t, n) {
13	if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
14	throw new TypeError("Private element is not present on this object");
15}
16function _classPrivateFieldGet2(s, a) {
17	return s.get(_assertClassBrand(s, a));
18}
19var _Class_brand = /* @__PURE__ */ new WeakSet();
20var _normalizePluginsToHash = /* @__PURE__ */ new WeakMap();
21var _Class = class extends Controller {
22	constructor(..._args) {
23		super(..._args);
24		_classPrivateMethodInitSpec(this, _Class_brand);
25		this.isObserving = false;
26		this.hasLoadedChoicesPreviously = false;
27		this.originalOptions = [];
28		_classPrivateFieldInitSpec(this, _normalizePluginsToHash, (plugins) => {
29			if (Array.isArray(plugins)) return plugins.reduce((acc, plugin) => {
30				if (typeof plugin === "string") acc[plugin] = {};
31				if (typeof plugin === "object" && plugin.name) acc[plugin.name] = plugin.options || {};
32				return acc;
33			}, {});
34			return plugins;
35		});
36	}
37	initialize() {
38		if (!this.mutationObserver) this.mutationObserver = new MutationObserver((mutations) => {
39			this.onMutations(mutations);
40		});
41	}
42	connect() {
43		if (this.selectElement) this.originalOptions = this.createOptionsDataStructure(this.selectElement);
44		this.initializeTomSelect();
45	}
46	initializeTomSelect() {
47		if (this.selectElement) this.selectElement.setAttribute("data-skip-morph", "");
48		if (this.urlValue) {
49			this.tomSelect = _assertClassBrand(_Class_brand, this, _createAutocompleteWithRemoteData).call(this, this.urlValue, this.hasMinCharactersValue ? this.minCharactersValue : null);
50			return;
51		}
52		if (this.optionsAsHtmlValue) {
53			this.tomSelect = _assertClassBrand(_Class_brand, this, _createAutocompleteWithHtmlContents).call(this);
54			return;
55		}
56		this.tomSelect = _assertClassBrand(_Class_brand, this, _createAutocomplete).call(this);
57		this.startMutationObserver();
58	}
59	disconnect() {
60		this.stopMutationObserver();
61		if (!this.tomSelect) return;
62		let currentSelectedValues = [];
63		if (this.selectElement) if (this.selectElement.multiple) currentSelectedValues = Array.from(this.selectElement.options).filter((option) => option.selected).map((option) => option.value);
64		else currentSelectedValues = [this.selectElement.value];
65		this.tomSelect.destroy();
66		this.tomSelect = void 0;
67		if (this.selectElement) if (this.selectElement.multiple) Array.from(this.selectElement.options).forEach((option) => {
68			option.selected = currentSelectedValues.includes(option.value);
69		});
70		else this.selectElement.value = currentSelectedValues[0];
71	}
72	urlValueChanged() {
73		this.resetTomSelect();
74	}
75	getMaxOptions() {
76		return this.selectElement ? this.selectElement.options.length : 50;
77	}
78	get selectElement() {
79		if (!(this.element instanceof HTMLSelectElement)) return null;
80		return this.element;
81	}
82	get formElement() {
83		if (!(this.element instanceof HTMLInputElement) && !(this.element instanceof HTMLSelectElement)) throw new Error("Autocomplete Stimulus controller can only be used on an <input> or <select>.");
84		return this.element;
85	}
86	dispatchEvent(name, payload) {
87		this.dispatch(name, {
88			detail: payload,
89			prefix: "autocomplete"
90		});
91	}
92	get preload() {
93		if (!this.hasPreloadValue) return "focus";
94		if (this.preloadValue === "false") return false;
95		if (this.preloadValue === "true") return true;
96		return this.preloadValue;
97	}
98	resetTomSelect() {
99		if (this.tomSelect) {
100			this.dispatchEvent("before-reset", { tomSelect: this.tomSelect });
101			this.stopMutationObserver();
102			const currentHtml = this.element.innerHTML;
103			const currentValue = this.tomSelect.getValue();
104			this.tomSelect.destroy();
105			this.element.innerHTML = currentHtml;
106			this.initializeTomSelect();
107			this.tomSelect.setValue(currentValue);
108		}
109	}
110	changeTomSelectDisabledState(isDisabled) {
111		if (!this.tomSelect) return;
112		this.stopMutationObserver();
113		if (isDisabled) this.tomSelect.disable();
114		else this.tomSelect.enable();
115		this.startMutationObserver();
116	}
117	startMutationObserver() {
118		if (!this.isObserving && this.mutationObserver) {
119			this.mutationObserver.observe(this.element, {
120				childList: true,
121				subtree: true,
122				attributes: true,
123				characterData: true,
124				attributeOldValue: true
125			});
126			this.isObserving = true;
127		}
128	}
129	stopMutationObserver() {
130		if (this.isObserving && this.mutationObserver) {
131			this.mutationObserver.disconnect();
132			this.isObserving = false;
133		}
134	}
135	onMutations(mutations) {
136		let changeDisabledState = false;
137		let requireReset = false;
138		mutations.forEach((mutation) => {
139			switch (mutation.type) {
140				case "attributes":
141					if (mutation.target === this.element && mutation.attributeName === "disabled") {
142						changeDisabledState = true;
143						break;
144					}
145					if (mutation.target === this.element && mutation.attributeName === "multiple") {
146						if (this.element.hasAttribute("multiple") !== (mutation.oldValue === "multiple")) requireReset = true;
147						break;
148					}
149					break;
150			}
151		});
152		const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : [];
153		if (!this.areOptionsEquivalent(newOptions) || requireReset) {
154			this.originalOptions = newOptions;
155			this.resetTomSelect();
156		}
157		if (changeDisabledState) this.changeTomSelectDisabledState(this.formElement.disabled);
158	}
159	createOptionsDataStructure(selectElement) {
160		return Array.from(selectElement.options).map((option) => {
161			return {
162				value: option.value,
163				text: option.text
164			};
165		});
166	}
167	areOptionsEquivalent(newOptions) {
168		const filteredOriginalOptions = this.originalOptions.filter((option) => option.value !== "");
169		const filteredNewOptions = newOptions.filter((option) => option.value !== "");
170		const originalPlaceholderOption = this.originalOptions.find((option) => option.value === "");
171		const newPlaceholderOption = newOptions.find((option) => option.value === "");
172		if (originalPlaceholderOption && newPlaceholderOption && originalPlaceholderOption.text !== newPlaceholderOption.text) return false;
173		if (filteredOriginalOptions.length !== filteredNewOptions.length) return false;
174		const normalizeOption = (option) => `${option.value}-${option.text}`;
175		const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption));
176		const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption));
177		return originalOptionsSet.size === newOptionsSet.size && [...originalOptionsSet].every((option) => newOptionsSet.has(option));
178	}
179};
180function _getCommonConfig() {
181	const plugins = {};
182	const isMultiple = !this.selectElement || this.selectElement.multiple;
183	if (!this.formElement.disabled && !isMultiple) plugins.clear_button = { title: "" };
184	if (isMultiple) plugins.remove_button = { title: "" };
185	if (this.urlValue) plugins.virtual_scroll = {};
186	const config = {
187		render: {
188			no_results: () => {
189				return `<div class="no-results">${this.noResultsFoundTextValue}</div>`;
190			},
191			option_create: (data, escapeData) => {
192				return `<div class="create">${this.createOptionTextValue.replace("%placeholder%", `<strong>${escapeData(data.input)}</strong>`)}</div>`;
193			}
194		},
195		plugins,
196		onItemAdd: () => {
197			this.tomSelect?.setTextboxValue("");
198		},
199		closeAfterSelect: true,
200		onOptionAdd: (value, data) => {
201			if (!this.tomSelect) return;
202			let parentElement = this.tomSelect.input;
203			let optgroupData = null;
204			const optgroup = data[this.tomSelect.settings.optgroupField];
205			if (optgroup && this.tomSelect.optgroups) {
206				optgroupData = this.tomSelect.optgroups[optgroup];
207				if (optgroupData) {
208					const optgroupElement = parentElement.querySelector(`optgroup[label="${optgroupData.label}"]`);
209					if (optgroupElement) parentElement = optgroupElement;
210				}
211			}
212			const optionElement = document.createElement("option");
213			optionElement.value = value;
214			optionElement.text = data[this.tomSelect.settings.labelField];
215			const optionOrder = data.$order;
216			let orderedOption = null;
217			for (const [, tomSelectOption] of Object.entries(this.tomSelect.options)) if (tomSelectOption.$order === optionOrder) {
218				orderedOption = parentElement.querySelector(`:scope > option[value="${CSS.escape(tomSelectOption[this.tomSelect.settings.valueField])}"]`);
219				break;
220			}
221			if (orderedOption) orderedOption.insertAdjacentElement("afterend", optionElement);
222			else if (optionOrder >= 0) parentElement.append(optionElement);
223			else parentElement.prepend(optionElement);
224		}
225	};
226	if (!this.selectElement && !this.urlValue) config.shouldLoad = () => false;
227	return _assertClassBrand(_Class_brand, this, _mergeConfigs).call(this, config, this.tomSelectOptionsValue);
228}
229function _createAutocomplete() {
230	const config = _assertClassBrand(_Class_brand, this, _mergeConfigs).call(this, _assertClassBrand(_Class_brand, this, _getCommonConfig).call(this), { maxOptions: this.getMaxOptions() });
231	return _assertClassBrand(_Class_brand, this, _createTomSelect).call(this, config);
232}
233function _createAutocompleteWithHtmlContents() {
234	const commonConfig = _assertClassBrand(_Class_brand, this, _getCommonConfig).call(this);
235	const labelField = commonConfig.labelField ?? "text";
236	const config = _assertClassBrand(_Class_brand, this, _mergeConfigs).call(this, commonConfig, {
237		maxOptions: this.getMaxOptions(),
238		score: (search) => {
239			const scoringFunction = this.tomSelect?.getScoreFunction(search);
240			return (item) => {
241				return scoringFunction?.({
242					...item,
243					text: _assertClassBrand(_Class_brand, this, _stripTags).call(this, item[labelField])
244				});
245			};
246		},
247		render: {
248			...commonConfig.render,
249			item: (item) => `<div>${item[labelField]}</div>`,
250			option: (item) => `<div>${item[labelField]}</div>`
251		}
252	});
253	return _assertClassBrand(_Class_brand, this, _createTomSelect).call(this, config);
254}
255function _createAutocompleteWithRemoteData(autocompleteEndpointUrl, minCharacterLength) {
256	const commonConfig = _assertClassBrand(_Class_brand, this, _getCommonConfig).call(this);
257	const labelField = commonConfig.labelField ?? "text";
258	const config = _assertClassBrand(_Class_brand, this, _mergeConfigs).call(this, commonConfig, {
259		firstUrl: (query) => {
260			return `${autocompleteEndpointUrl}${autocompleteEndpointUrl.includes("?") ? "&" : "?"}query=${encodeURIComponent(query)}`;
261		},
262		load: function(query, callback) {
263			const url = this.getUrl(query);
264			fetch(url).then((response) => response.json()).then((json) => {
265				this.setNextUrl(query, json.next_page);
266				callback(json.results.options || json.results, json.results.optgroups || []);
267			}).catch(() => callback([], []));
268		},
269		shouldLoad: (query) => {
270			if (null !== minCharacterLength) return query.length >= minCharacterLength;
271			if (this.hasLoadedChoicesPreviously) return true;
272			if (query.length > 0) this.hasLoadedChoicesPreviously = true;
273			return query.length >= 3;
274		},
275		optgroupField: "group_by",
276		score: (_search) => (_item) => 1,
277		render: {
278			option: (item, escape) => `<div>${this.optionsAsHtmlValue ? item[labelField] : escape(item[labelField])}</div>`,
279			item: (item, escape) => `<div>${this.optionsAsHtmlValue ? item[labelField] : escape(item[labelField])}</div>`,
280			loading_more: () => {
281				return `<div class="loading-more-results">${this.loadingMoreTextValue}</div>`;
282			},
283			no_more_results: () => {
284				return `<div class="no-more-results">${this.noMoreResultsTextValue}</div>`;
285			},
286			no_results: () => {
287				return `<div class="no-results">${this.noResultsFoundTextValue}</div>`;
288			},
289			option_create: (data, escapeData) => {
290				return `<div class="create">${this.createOptionTextValue.replace("%placeholder%", `<strong>${escapeData(data.input)}</strong>`)}</div>`;
291			}
292		},
293		onFocus: () => {
294			if (this.resetOnFocusValue && this.tomSelect) {
295				if (this.tomSelect.control_input.value.trim() === "") {
296					this.tomSelect.clearOptions();
297					this.tomSelect.loadedSearches = {};
298					if (typeof this.tomSelect["clearPagination"] === "function") this.tomSelect["clearPagination"]();
299					this.tomSelect.load("");
300				}
301			}
302		},
303		preload: this.preload
304	});
305	return _assertClassBrand(_Class_brand, this, _createTomSelect).call(this, config);
306}
307function _stripTags(string) {
308	return string.replace(/(<([^>]+)>)/gi, "");
309}
310function _mergeConfigs(config1, config2) {
311	return {
312		...config1,
313		...config2,
314		plugins: _assertClassBrand(_Class_brand, this, _normalizePlugins).call(this, {
315			..._classPrivateFieldGet2(_normalizePluginsToHash, this).call(this, config1.plugins || {}),
316			..._classPrivateFieldGet2(_normalizePluginsToHash, this).call(this, config2.plugins || {})
317		})
318	};
319}
320function _normalizePlugins(plugins) {
321	return Object.entries(plugins).reduce((acc, [pluginName, pluginOptions]) => {
322		if (pluginOptions !== false) acc[pluginName] = pluginOptions;
323		return acc;
324	}, {});
325}
326function _createTomSelect(options) {
327	const preConnectPayload = { options };
328	this.dispatchEvent("pre-connect", preConnectPayload);
329	const tomSelect = new TomSelect(this.formElement, options);
330	const connectPayload = {
331		tomSelect,
332		options
333	};
334	this.dispatchEvent("connect", connectPayload);
335	return tomSelect;
336}
337_Class.values = {
338	url: String,
339	optionsAsHtml: Boolean,
340	loadingMoreText: String,
341	noResultsFoundText: String,
342	noMoreResultsText: String,
343	createOptionText: String,
344	minCharacters: Number,
345	tomSelectOptions: Object,
346	preload: String,
347	resetOnFocus: Boolean
348};
349export { _Class as default };

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.