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.