1import { Controller } from '@hotwired/stimulus'; 2import TomSelect from 'tom-select'; 3 4/****************************************************************************** 5Copyright (c) Microsoft Corporation. 6 7Permission to use, copy, modify, and/or distribute this software for any 8purpose with or without fee is hereby granted. 9 10THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 11REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 12AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 13INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 14LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 15OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16PERFORMANCE OF THIS SOFTWARE. 17***************************************************************************** */ 18/* global Reflect, Promise, SuppressedError, Symbol */ 19 20 21function __classPrivateFieldGet(receiver, state, kind, f) { 22 if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); 23 if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); 24 return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); 25} 26 27typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { 28 var e = new Error(message); 29 return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; 30}; 31 32var _default_1_instances, _default_1_getCommonConfig, _default_1_createAutocomplete, _default_1_createAutocompleteWithHtmlContents, _default_1_createAutocompleteWithRemoteData, _default_1_stripTags, _default_1_mergeConfigs, _default_1_normalizePluginsToHash, _default_1_createTomSelect; 33class default_1 extends Controller { 34 constructor() { 35 super(...arguments); 36 _default_1_instances.add(this); 37 this.isObserving = false; 38 this.hasLoadedChoicesPreviously = false; 39 this.originalOptions = []; 40 _default_1_normalizePluginsToHash.set(this, (plugins) => { 41 if (Array.isArray(plugins)) { 42 return plugins.reduce((acc, plugin) => { 43 if (typeof plugin === 'string') { 44 acc[plugin] = {}; 45 } 46 if (typeof plugin === 'object' && plugin.name) { 47 acc[plugin.name] = plugin.options || {}; 48 } 49 return acc; 50 }, {}); 51 } 52 return plugins; 53 }); 54 } 55 initialize() { 56 if (!this.mutationObserver) { 57 this.mutationObserver = new MutationObserver((mutations) => { 58 this.onMutations(mutations); 59 }); 60 } 61 } 62 connect() { 63 if (this.selectElement) { 64 this.originalOptions = this.createOptionsDataStructure(this.selectElement); 65 } 66 this.initializeTomSelect(); 67 } 68 initializeTomSelect() { 69 if (this.selectElement) { 70 this.selectElement.setAttribute('data-skip-morph', ''); 71 } 72 if (this.urlValue) { 73 this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithRemoteData).call(this, this.urlValue, this.hasMinCharactersValue ? this.minCharactersValue : null); 74 return; 75 } 76 if (this.optionsAsHtmlValue) { 77 this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithHtmlContents).call(this); 78 return; 79 } 80 this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocomplete).call(this); 81 this.startMutationObserver(); 82 } 83 disconnect() { 84 this.stopMutationObserver(); 85 let currentSelectedValues = []; 86 if (this.selectElement) { 87 if (this.selectElement.multiple) { 88 currentSelectedValues = Array.from(this.selectElement.options) 89 .filter((option) => option.selected) 90 .map((option) => option.value); 91 } 92 else { 93 currentSelectedValues = [this.selectElement.value]; 94 } 95 } 96 this.tomSelect.destroy(); 97 if (this.selectElement) { 98 if (this.selectElement.multiple) {
99 Array.from(this.selectElement.options).forEach((option) => { 100 option.selected = currentSelectedValues.includes(option.value); 101 }); 102 } 103 else { 104 this.selectElement.value = currentSelectedValues[0]; 105 } 106 } 107 } 108 urlValueChanged() { 109 this.resetTomSelect(); 110 } 111 getMaxOptions() { 112 return this.selectElement ? this.selectElement.options.length : 50; 113 } 114 get selectElement() { 115 if (!(this.element instanceof HTMLSelectElement)) { 116 return null; 117 } 118 return this.element; 119 } 120 get formElement() { 121 if (!(this.element instanceof HTMLInputElement) && !(this.element instanceof HTMLSelectElement)) { 122 throw new Error('Autocomplete Stimulus controller can only be used on an <input> or <select>.'); 123 } 124 return this.element; 125 } 126 dispatchEvent(name, payload) { 127 this.dispatch(name, { detail: payload, prefix: 'autocomplete' }); 128 } 129 get preload() { 130 if (!this.hasPreloadValue) { 131 return 'focus'; 132 } 133 if (this.preloadValue === 'false') { 134 return false; 135 } 136 if (this.preloadValue === 'true') { 137 return true; 138 } 139 return this.preloadValue; 140 } 141 resetTomSelect() { 142 if (this.tomSelect) { 143 this.dispatchEvent('before-reset', { tomSelect: this.tomSelect }); 144 this.stopMutationObserver(); 145 const currentHtml = this.element.innerHTML; 146 const currentValue = this.tomSelect.getValue(); 147 this.tomSelect.destroy(); 148 this.element.innerHTML = currentHtml; 149 this.initializeTomSelect(); 150 this.tomSelect.setValue(currentValue); 151 } 152 } 153 changeTomSelectDisabledState(isDisabled) { 154 this.stopMutationObserver(); 155 if (isDisabled) { 156 this.tomSelect.disable(); 157 } 158 else { 159 this.tomSelect.enable(); 160 } 161 this.startMutationObserver(); 162 } 163 startMutationObserver() { 164 if (!this.isObserving && this.mutationObserver) { 165 this.mutationObserver.observe(this.element, { 166 childList: true, 167 subtree: true, 168 attributes: true, 169 characterData: true, 170 attributeOldValue: true, 171 }); 172 this.isObserving = true; 173 } 174 } 175 stopMutationObserver() { 176 if (this.isObserving && this.mutationObserver) { 177 this.mutationObserver.disconnect(); 178 this.isObserving = false; 179 } 180 } 181 onMutations(mutations) { 182 let changeDisabledState = false; 183 let requireReset = false; 184 mutations.forEach((mutation) => { 185 switch (mutation.type) { 186 case 'attributes': 187 if (mutation.target === this.element && mutation.attributeName === 'disabled') { 188 changeDisabledState = true; 189 break; 190 } 191 if (mutation.target === this.element && mutation.attributeName === 'multiple') { 192 const isNowMultiple = this.element.hasAttribute('multiple'); 193 const wasMultiple = mutation.oldValue === 'multiple'; 194 if (isNowMultiple !== wasMultiple) { 195 requireReset = true; 196 } 197 break; 198 } 199 break; 200 } 201 }); 202 const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : []; 203 const areOptionsEquivalent = this.areOptionsEquivalent(newOptions); 204 if (!areOptionsEquivalent || requireReset) { 205 this.originalOptions = newOptions; 206 this.resetTomSelect(); 207 } 208 if (changeDisabledState) { 209 this.changeTomSelectDisabledState(this.formElement.disabled); 210 } 211 } 212 createOptionsDataStructure(selectElement) { 213 return Array.from(selectElement.options).map((option) => { 214 return { 215 value: option.value, 216 text: option.text, 217 }; 218 }); 219 } 220 areOptionsEquivalent(newOptions) { 221 const filteredOriginalOptions = this.originalOptions.filter((option) => option.value !== ''); 222 const filteredNewOptions = newOptions.filter((option) => option.value !== ''); 223 const originalPlaceholderOption = this.originalOptions.find((option) => option.value === ''); 224 const newPlaceholderOption = newOptions.find((option) => option.value === ''); 225 if (originalPlaceholderOption && 226 newPlaceholderOption && 227 originalPlaceholderOption.text !== newPlaceholderOption.text) { 228 return false;
229 } 230 if (filteredOriginalOptions.length !== filteredNewOptions.length) { 231 return false; 232 } 233 const normalizeOption = (option) => `${option.value}-${option.text}`; 234 const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption)); 235 const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption)); 236 return (originalOptionsSet.size === newOptionsSet.size && 237 [...originalOptionsSet].every((option) => newOptionsSet.has(option))); 238 } 239} 240_default_1_normalizePluginsToHash = new WeakMap(), _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _default_1_getCommonConfig() { 241 const plugins = {}; 242 const isMultiple = !this.selectElement || this.selectElement.multiple; 243 if (!this.formElement.disabled && !isMultiple) { 244 plugins.clear_button = { title: '' }; 245 } 246 if (isMultiple) { 247 plugins.remove_button = { title: '' }; 248 } 249 if (this.urlValue) { 250 plugins.virtual_scroll = {}; 251 } 252 const render = { 253 no_results: () => { 254 return `<div class="no-results">${this.noResultsFoundTextValue}</div>`; 255 }, 256 option_create: (data, escapeData) => { 257 return `<div class="create">${this.createOptionTextValue.replace('%placeholder%', `<strong>${escapeData(data.input)}</strong>`)}</div>`; 258 }, 259 }; 260 const config = { 261 render, 262 plugins, 263 onItemAdd: () => { 264 this.tomSelect.setTextboxValue(''); 265 }, 266 closeAfterSelect: true, 267 onOptionAdd: (value, data) => { 268 let parentElement = this.tomSelect.input; 269 let optgroupData = null; 270 const optgroup = data[this.tomSelect.settings.optgroupField]; 271 if (optgroup && this.tomSelect.optgroups) { 272 optgroupData = this.tomSelect.optgroups[optgroup]; 273 if (optgroupData) { 274 const optgroupElement = parentElement.querySelector(`optgroup[label="${optgroupData.label}"]`); 275 if (optgroupElement) { 276 parentElement = optgroupElement; 277 } 278 } 279 } 280 const optionElement = document.createElement('option'); 281 optionElement.value = value; 282 optionElement.text = data[this.tomSelect.settings.labelField]; 283 const optionOrder = data.$order; 284 let orderedOption = null; 285 for (const [, tomSelectOption] of Object.entries(this.tomSelect.options)) { 286 if (tomSelectOption.$order === optionOrder) { 287 orderedOption = parentElement.querySelector(`:scope > option[value="${CSS.escape(tomSelectOption[this.tomSelect.settings.valueField])}"]`); 288 break; 289 } 290 } 291 if (orderedOption) { 292 orderedOption.insertAdjacentElement('afterend', optionElement); 293 } 294 else if (optionOrder >= 0) { 295 parentElement.append(optionElement); 296 } 297 else { 298 parentElement.prepend(optionElement); 299 } 300 }, 301 }; 302 if (!this.selectElement && !this.urlValue) { 303 config.shouldLoad = () => false; 304 } 305 return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeConfigs).call(this, config, this.tomSelectOptionsValue); 306}, _default_1_createAutocomplete = function _default_1_createAutocomplete() { 307 const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeConfigs).call(this, __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this), { 308 maxOptions: this.getMaxOptions(), 309 }); 310 return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config); 311}, _default_1_createAutocompleteWithHtmlContents = function _default_1_createAutocompleteWithHtmlContents() { 312 const commonConfig = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this); 313 const labelField = commonConfig.labelField ?? 'text'; 314 const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeConfigs).call(this, commonConfig, { 315 maxOptions: this.getMaxOptions(), 316 score: (search) => { 317 const scoringFunction = this.tomSelect.getScoreFunction(search); 318 return (item) => { 319 return scoringFunction({ ...item, text: __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_stripTags).call(this, item[labelField]) }); 320 }; 321 }, 322 render: { 323 item: (item) => `<div>${item[labelField]}</div>`, 324 option: (item) => `<div>${item[labelField]}</div>`, 325 }, 326 }); 327 return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config); 328}, _default_1_createAutocompleteWithRemoteData = function _default_1_createAutocompleteWithRemoteData(autocompleteEndpointUrl, minCharacterLength) { 329 const commonConfig = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this); 330 const labelField = commonConfig.labelField ?? 'text'; 331 const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeConfigs).call(this, commonConfig, { 332 firstUrl: (query) => { 333 const separator = autocompleteEndpointUrl.includes('?') ? '&' : '?'; 334 return `${autocompleteEndpointUrl}${separator}query=${encodeURIComponent(query)}`; 335 }, 336 load: function (query, callback) { 337 const url = this.getUrl(query); 338 fetch(url) 339 .then((response) => response.json()) 340 .then((json) => { 341 this.setNextUrl(query, json.next_page); 342 callback(json.results.options || json.results, json.results.optgroups || []); 343 }) 344 .catch(() => callback([], [])); 345 }, 346 shouldLoad: (query) => { 347 if (null !== minCharacterLength) { 348 return query.length >= minCharacterLength; 349 } 350 if (this.hasLoadedChoicesPreviously) { 351 return true; 352 } 353 if (query.length > 0) { 354 this.hasLoadedChoicesPreviously = true; 355 } 356 return query.length >= 3; 357 }, 358 optgroupField: 'group_by', 359 score: (search) => (item) => 1, 360 render: { 361 option: (item) => `<div>${item[labelField]}</div>`, 362 item: (item) => `<div>${item[labelField]}</div>`, 363 loading_more: () => { 364 return `<div class="loading-more-results">${this.loadingMoreTextValue}</div>`; 365 }, 366 no_more_results: () => { 367 return `<div class="no-more-results">${this.noMoreResultsTextValue}</div>`; 368 }, 369 no_results: () => { 370 return `<div class="no-results">${this.noResultsFoundTextValue}</div>`; 371 }, 372 option_create: (data, escapeData) => { 373 return `<div class="create">${this.createOptionTextValue.replace('%placeholder%', `<strong>${escapeData(data.input)}</strong>`)}</div>`; 374 }, 375 }, 376 preload: this.preload, 377 }); 378 return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config); 379}, _default_1_stripTags = function _default_1_stripTags(string) { 380 return string.replace(/(<([^>]+)>)/gi, ''); 381}, _default_1_mergeConfigs = function _default_1_mergeConfigs(config1, config2) { 382 return { 383 ...config1, 384 ...config2, 385 plugins: { 386 ...__classPrivateFieldGet(this, _default_1_normalizePluginsToHash, "f").call(this, config1.plugins || {}), 387 ...__classPrivateFieldGet(this, _default_1_normalizePluginsToHash, "f").call(this, config2.plugins || {}), 388 }, 389 }; 390}, _default_1_createTomSelect = function _default_1_createTomSelect(options) { 391 const preConnectPayload = { options }; 392 this.dispatchEvent('pre-connect', preConnectPayload); 393 const tomSelect = new TomSelect(this.formElement, options); 394 const connectPayload = { tomSelect, options }; 395 this.dispatchEvent('connect', connectPayload); 396 return tomSelect; 397}; 398default_1.values = { 399 url: String, 400 optionsAsHtml: Boolean,
401 loadingMoreText: String, 402 noResultsFoundText: String, 403 noMoreResultsText: String, 404 createOptionText: String, 405 minCharacters: Number, 406 tomSelectOptions: Object, 407 preload: String, 408}; 409 410export { default_1 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.