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.