PageSourceSearch

https://eclipseinfo.fr/assets/@symfony/ux-autocomplete/controller-4yKT4My.js

js eclipseinfo.fr collected 2026-10-06 01:46:19 UTC 15,350 bytes, 412 lines download raw bytes

1var __typeError = (msg) => {
2  throw TypeError(msg);
3};
4var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
8
9// src/controller.ts
10import { Controller } from "@hotwired/stimulus";
11import TomSelect from "tom-select";
12var _instances, getCommonConfig_fn, createAutocomplete_fn, createAutocompleteWithHtmlContents_fn, createAutocompleteWithRemoteData_fn, stripTags_fn, mergeConfigs_fn, _normalizePluginsToHash, normalizePlugins_fn, createTomSelect_fn;
13var controller_default = class extends Controller {
14  constructor() {
15    super(...arguments);
16    __privateAdd(this, _instances);
17    this.isObserving = false;
18    this.hasLoadedChoicesPreviously = false;
19    this.originalOptions = [];
20    /**
21     * Normalizes the plugins to a hash, so that we can merge them easily.
22     */
23    __privateAdd(this, _normalizePluginsToHash, (plugins) => {
24      if (Array.isArray(plugins)) {
25        return plugins.reduce((acc, plugin) => {
26          if (typeof plugin === "string") {
27            acc[plugin] = {};
28          }
29          if (typeof plugin === "object" && plugin.name) {
30            acc[plugin.name] = plugin.options || {};
31          }
32          return acc;
33        }, {});
34      }
35      return plugins;
36    });
37  }
38  initialize() {
39    if (!this.mutationObserver) {
40      this.mutationObserver = new MutationObserver((mutations) => {
41        this.onMutations(mutations);
42      });
43    }
44  }
45  connect() {
46    if (this.selectElement) {
47      this.originalOptions = this.createOptionsDataStructure(this.selectElement);
48    }
49    this.initializeTomSelect();
50  }
51  initializeTomSelect() {
52    if (this.selectElement) {
53      this.selectElement.setAttribute("data-skip-morph", "");
54    }
55    if (this.urlValue) {
56      this.tomSelect = __privateMethod(this, _instances, createAutocompleteWithRemoteData_fn).call(this, this.urlValue, this.hasMinCharactersValue ? this.minCharactersValue : null);
57      return;
58    }
59    if (this.optionsAsHtmlValue) {
60      this.tomSelect = __privateMethod(this, _instances, createAutocompleteWithHtmlContents_fn).call(this);
61      return;
62    }
63    this.tomSelect = __privateMethod(this, _instances, createAutocomplete_fn).call(this);
64    this.startMutationObserver();
65  }
66  disconnect() {
67    this.stopMutationObserver();
68    let currentSelectedValues = [];
69    if (this.selectElement) {
70      if (this.selectElement.multiple) {
71        currentSelectedValues = Array.from(this.selectElement.options).filter((option) => option.selected).map((option) => option.value);
72      } else {
73        currentSelectedValues = [this.selectElement.value];
74      }
75    }
76    this.tomSelect.destroy();
77    if (this.selectElement) {
78      if (this.selectElement.multiple) {
79        Array.from(this.selectElement.options).forEach((option) => {
80          option.selected = currentSelectedValues.includes(option.value);
81        });
82      } else {
83        this.selectElement.value = currentSelectedValues[0];
84      }
85    }
86  }
87  urlValueChanged() {
88    this.resetTomSelect();
89  }
90  getMaxOptions() {
91    return this.selectElement ? this.selectElement.options.length : 50;
92  }
93  /**
94   * Returns the element, but only if it's a select element.
95   */
96  get selectElement() {
97    if (!(this.element instanceof HTMLSelectElement)) {
98      return null;
99    }
100    return this.element;
101  }
102  /**
103   * Getter to help typing.
104   */
105  get formElement() {
106    if (!(this.element instanceof HTMLInputElement) && !(this.element instanceof HTMLSelectElement)) {
107      throw new Error("Autocomplete Stimulus controller can only be used on an <input> or <select>.");
108    }
109    return this.element;
110  }
111  dispatchEvent(name, payload) {
112    this.dispatch(name, { detail: payload, prefix: "autocomplete" });
113  }
114  get preload() {
115    if (!this.hasPreloadValue) {
116      return "focus";
117    }
118    if (this.preloadValue === "false") {
119      return false;
120    }
121    if (this.preloadValue === "true") {
122      return true;
123    }
124    return this.preloadValue;
125  }
126  resetTomSelect() {
127    if (this.tomSelect) {
128      this.dispatchEvent("before-reset", { tomSelect: this.tomSelect });
129      this.stopMutationObserver();
130      const currentHtml = this.element.innerHTML;
131      const currentValue = this.tomSelect.getValue();
132      this.tomSelect.destroy();
133      this.element.innerHTML = currentHtml;
134      this.initializeTomSelect();
135      this.tomSelect.setValue(currentValue);
136    }
137  }
138  changeTomSelectDisabledState(isDisabled) {
139    this.stopMutationObserver();
140    if (isDisabled) {
141      this.tomSelect.disable();
142    } else {
143      this.tomSelect.enable();
144    }
145    this.startMutationObserver();
146  }
147  startMutationObserver() {
148    if (!this.isObserving && this.mutationObserver) {
149      this.mutationObserver.observe(this.element, {
150        childList: true,
151        subtree: true,
152        attributes: true,
153        characterData: true,
154        attributeOldValue: true
155      });
156      this.isObserving = true;
157    }
158  }
159  stopMutationObserver() {
160    if (this.isObserving && this.mutationObserver) {
161      this.mutationObserver.disconnect();
162      this.isObserving = false;
163    }
164  }
165  onMutations(mutations) {
166    let changeDisabledState = false;
167    let requireReset = false;
168    mutations.forEach((mutation) => {
169      switch (mutation.type) {
170        case "attributes":
171          if (mutation.target === this.element && mutation.attributeName === "disabled") {
172            changeDisabledState = true;
173            break;
174          }
175          if (mutation.target === this.element && mutation.attributeName === "multiple") {
176            const isNowMultiple = this.element.hasAttribute("multiple");
177            const wasMultiple = mutation.oldValue === "multiple";
178            if (isNowMultiple !== wasMultiple) {
179              requireReset = true;
180            }
181            break;
182          }
183          break;
184      }
185    });
186    const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : [];
187    const areOptionsEquivalent = this.areOptionsEquivalent(newOptions);
188    if (!areOptionsEquivalent || requireReset) {
189      this.originalOptions = newOptions;
190      this.resetTomSelect();
191    }
192    if (changeDisabledState) {
193      this.changeTomSelectDisabledState(this.formElement.disabled);
194    }
195  }
196  createOptionsDataStructure(selectElement) {
197    return Array.from(selectElement.options).map((option) => {
198      return {
199        value: option.value,
200        text: option.text
201      };
202    });
203  }
204  areOptionsEquivalent(newOptions) {
205    const filteredOriginalOptions = this.originalOptions.filter((option) => option.value !== "");
206    const filteredNewOptions = newOptions.filter((option) => option.value !== "");
207    const originalPlaceholderOption = this.originalOptions.find((option) => option.value === "");
208    const newPlaceholderOption = newOptions.find((option) => option.value === "");
209    if (originalPlaceholderOption && newPlaceholderOption && originalPlaceholderOption.text !== newPlaceholderOption.text) {
210      return false;
211    }
212    if (filteredOriginalOptions.length !== filteredNewOptions.length) {
213      return false;
214    }
215    const normalizeOption = (option) => `${option.value}-${option.text}`;
216    const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption));
217    const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption));
218    return originalOptionsSet.size === newOptionsSet.size && [...originalOptionsSet].every((option) => newOptionsSet.has(option));
219  }
220};
221_instances = new WeakSet();
222getCommonConfig_fn = function() {
223  const plugins = {};
224  const isMultiple = !this.selectElement || this.selectElement.multiple;
225  if (!this.formElement.disabled && !isMultiple) {
226    plugins.clear_button = { title: "" };
227  }
228  if (isMultiple) {
229    plugins.remove_button = { title: "" };
230  }
231  if (this.urlValue) {
232    plugins.virtual_scroll = {};
233  }
234  const render = {
235    no_results: () => {
236      return `<div class="no-results">${this.noResultsFoundTextValue}</div>`;
237    },
238    option_create: (data, escapeData) => {
239      return `<div class="create">${this.createOptionTextValue.replace("%placeholder%", `<strong>${escapeData(data.input)}</strong>`)}</div>`;
240    }
241  };
242  const config = {
243    render,
244    plugins,
245    // clear the text input after selecting a value
246    onItemAdd: () => {
247      this.tomSelect.setTextboxValue("");
248    },
249    closeAfterSelect: true,
250    // fix positioning (in the dropdown) of options added through addOption()
251    onOptionAdd: (value, data) => {
252      let parentElement = this.tomSelect.input;
253      let optgroupData = null;
254      const optgroup = data[this.tomSelect.settings.optgroupField];
255      if (optgroup && this.tomSelect.optgroups) {
256        optgroupData = this.tomSelect.optgroups[optgroup];
257        if (optgroupData) {
258          const optgroupElement = parentElement.querySelector(`optgroup[label="${optgroupData.label}"]`);
259          if (optgroupElement) {
260            parentElement = optgroupElement;
261          }
262        }
263      }
264      const optionElement = document.createElement("option");
265      optionElement.value = value;
266      optionElement.text = data[this.tomSelect.settings.labelField];
267      const optionOrder = data.$order;
268      let orderedOption = null;
269      for (const [, tomSelectOption] of Object.entries(this.tomSelect.options)) {
270        if (tomSelectOption.$order === optionOrder) {
271          orderedOption = parentElement.querySelector(
272            `:scope > option[value="${CSS.escape(tomSelectOption[this.tomSelect.settings.valueField])}"]`
273          );
274          break;
275        }
276      }
277      if (orderedOption) {
278        orderedOption.insertAdjacentElement("afterend", optionElement);
279      } else if (optionOrder >= 0) {
280        parentElement.append(optionElement);
281      } else {
282        parentElement.prepend(optionElement);
283      }
284    }
285  };
286  if (!this.selectElement && !this.urlValue) {
287    config.shouldLoad = () => false;
288  }
289  return __privateMethod(this, _instances, mergeConfigs_fn).call(this, config, this.tomSelectOptionsValue);
290};
291createAutocomplete_fn = function() {
292  const config = __privateMethod(this, _instances, mergeConfigs_fn).call(this, __privateMethod(this, _instances, getCommonConfig_fn).call(this), {
293    maxOptions: this.getMaxOptions()
294  });
295  return __privateMethod(this, _instances, createTomSelect_fn).call(this, config);
296};
297createAutocompleteWithHtmlContents_fn = function() {
298  const commonConfig = __privateMethod(this, _instances, getCommonConfig_fn).call(this);
299  const labelField = commonConfig.labelField ?? "text";
300  const config = __privateMethod(this, _instances, mergeConfigs_fn).call(this, commonConfig, {
301    maxOptions: this.getMaxOptions(),
302    score: (search) => {
303      const scoringFunction = this.tomSelect.getScoreFunction(search);
304      return (item) => {
305        return scoringFunction({ ...item, text: __privateMethod(this, _instances, stripTags_fn).call(this, item[labelField]) });
306      };
307    },
308    render: {
309      item: (item) => `<div>${item[labelField]}</div>`,
310      option: (item) => `<div>${item[labelField]}</div>`
311    }
312  });
313  return __privateMethod(this, _instances, createTomSelect_fn).call(this, config);
314};
315createAutocompleteWithRemoteData_fn = function(autocompleteEndpointUrl, minCharacterLength) {
316  const commonConfig = __privateMethod(this, _instances, getCommonConfig_fn).call(this);
317  const labelField = commonConfig.labelField ?? "text";
318  const config = __privateMethod(this, _instances, mergeConfigs_fn).call(this, commonConfig, {
319    firstUrl: (query) => {
320      const separator = autocompleteEndpointUrl.includes("?") ? "&" : "?";
321      return `${autocompleteEndpointUrl}${separator}query=${encodeURIComponent(query)}`;
322    },
323    // VERY IMPORTANT: use 'function (query, callback) { ... }' instead of the
324    // '(query, callback) => { ... }' syntax because, otherwise,
325    // the 'this.XXX' calls inside this method fail
326    load: function(query, callback) {
327      const url = this.getUrl(query);
328      fetch(url).then((response) => response.json()).then((json) => {
329        this.setNextUrl(query, json.next_page);
330        callback(json.results.options || json.results, json.results.optgroups || []);
331      }).catch(() => callback([], []));
332    },
333    shouldLoad: (query) => {
334      if (null !== minCharacterLength) {
335        return query.length >= minCharacterLength;
336      }
337      if (this.hasLoadedChoicesPreviously) {
338        return true;
339      }
340      if (query.length > 0) {
341        this.hasLoadedChoicesPreviously = true;
342      }
343      return query.length >= 3;
344    },
345    optgroupField: "group_by",
346    // avoid extra filtering after results are returned
347    score: (search) => (item) => 1,
348    render: {
349      option: (item) => `<div>${item[labelField]}</div>`,
350      item: (item) => `<div>${item[labelField]}</div>`,
351      loading_more: () => {
352        return `<div class="loading-more-results">${this.loadingMoreTextValue}</div>`;
353      },
354      no_more_results: () => {
355        return `<div class="no-more-results">${this.noMoreResultsTextValue}</div>`;
356      },
357      no_results: () => {
358        return `<div class="no-results">${this.noResultsFoundTextValue}</div>`;
359      },
360      option_create: (data, escapeData) => {
361        return `<div class="create">${this.createOptionTextValue.replace("%placeholder%", `<strong>${escapeData(data.input)}</strong>`)}</div>`;
362      }
363    },
364    preload: this.preload
365  });
366  return __privateMethod(this, _instances, createTomSelect_fn).call(this, config);
367};
368stripTags_fn = function(string) {
369  return string.replace(/(<([^>]+)>)/gi, "");
370};
371mergeConfigs_fn = function(config1, config2) {
372  return {
373    ...config1,
374    ...config2,
375    // Plugins from both configs should be merged together.
376    plugins: __privateMethod(this, _instances, normalizePlugins_fn).call(this, {
377      ...__privateGet(this, _normalizePluginsToHash).call(this, config1.plugins || {}),
378      ...__privateGet(this, _normalizePluginsToHash).call(this, config2.plugins || {})
379    })
380  };
381};
382_normalizePluginsToHash = new WeakMap();
383normalizePlugins_fn = function(plugins) {
384  return Object.entries(plugins).reduce((acc, [pluginName, pluginOptions]) => {
385    if (pluginOptions !== false) {
386      acc[pluginName] = pluginOptions;
387    }
388    return acc;
389  }, {});
390};
391createTomSelect_fn = function(options) {
392  const preConnectPayload = { options };
393  this.dispatchEvent("pre-connect", preConnectPayload);
394  const tomSelect = new TomSelect(this.formElement, options);
395  const connectPayload = { tomSelect, options };
396  this.dispatchEvent("connect", connectPayload);
397  return tomSelect;
398};
399controller_default.values = {
400  url: String,
401  optionsAsHtml: Boolean,
402  loadingMoreText: String,
403  noResultsFoundText: String,
404  noMoreResultsText: String,
405  createOptionText: String,
406  minCharacters: Number,
407  tomSelectOptions: Object,
408  preload: String
409};
410export {
411  controller_default as default
412};

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.