tom_select.js

   1/**
   2* Tom Select v2.0.0
   3* Licensed under the Apache License, Version 2.0 (the "License");
   4*/
   5
   6(function (global, factory) {
   7	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
   8	typeof define === 'function' && define.amd ? define(factory) :
   9	(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TomSelect = factory());
  10}(this, (function () { 'use strict';
  11
  12	/**
  13	 * MicroEvent - to make any js object an event emitter
  14	 *
  15	 * - pure javascript - server compatible, browser compatible
  16	 * - dont rely on the browser doms
  17	 * - super simple - you get it immediatly, no mistery, no magic involved
  18	 *
  19	 * @author Jerome Etienne (https://github.com/jeromeetienne)
  20	 */
  21
  22	/**
  23	 * Execute callback for each event in space separated list of event names
  24	 *
  25	 */
  26	function forEvents(events, callback) {
  27	  events.split(/\s+/).forEach(event => {
  28	    callback(event);
  29	  });
  30	}
  31
  32	class MicroEvent {
  33	  constructor() {
  34	    this._events = {};
  35	  }
  36
  37	  on(events, fct) {
  38	    forEvents(events, event => {
  39	      this._events[event] = this._events[event] || [];
  40
  41	      this._events[event].push(fct);
  42	    });
  43	  }
  44
  45	  off(events, fct) {
  46	    var n = arguments.length;
  47
  48	    if (n === 0) {
  49	      this._events = {};
  50	      return;
  51	    }
  52
  53	    forEvents(events, event => {
  54	      if (n === 1) return delete this._events[event];
  55	      if (event in this._events === false) return;
  56
  57	      this._events[event].splice(this._events[event].indexOf(fct), 1);
  58	    });
  59	  }
  60
  61	  trigger(events, ...args) {
  62	    var self = this;
  63	    forEvents(events, event => {
  64	      if (event in self._events === false) return;
  65
  66	      for (let fct of self._events[event]) {
  67	        fct.apply(self, args);
  68	      }
  69	    });
  70	  }
  71
  72	}
  73
  74	/**
  75	 * microplugin.js
  76	 * Copyright (c) 2013 Brian Reavis & contributors
  77	 *
  78	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  79	 * file except in compliance with the License. You may obtain a copy of the License at:
  80	 * http://www.apache.org/licenses/LICENSE-2.0
  81	 *
  82	 * Unless required by applicable law or agreed to in writing, software distributed under
  83	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  84	 * ANY KIND, either express or implied. See the License for the specific language
  85	 * governing permissions and limitations under the License.
  86	 *
  87	 * @author Brian Reavis <brian@thirdroute.com>
  88	 */
  89	function MicroPlugin(Interface) {
  90	  Interface.plugins = {};
  91	  return class extends Interface {
  92	    constructor(...args) {
  93	      super(...args);
  94	      this.plugins = {
  95	        names: [],
  96	        settings: {},
  97	        requested: {},
  98	        loaded: {}
  99	      };
 100	    }
 101
 102	    /**
 103	     * Registers a plugin.
 104	     *
 105	     * @param {function} fn
 106	     */
 107	    static define(name, fn) {
 108	      Interface.plugins[name] = {
 109	        'name': name,
 110	        'fn': fn
 111	      };
 112	    }
 113	    /**
 114	     * Initializes the listed plugins (with options).
 115	     * Acceptable formats:
 116	     *
 117	     * List (without options):
 118	     *   ['a', 'b', 'c']
 119	     *
 120	     * List (with options):
 121	     *   [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
 122	     *
 123	     * Hash (with options):
 124	     *   {'a': { ... }, 'b': { ... }, 'c': { ... }}
 125	     *
 126	     * @param {array|object} plugins
 127	     */
 128
 129
 130	    initializePlugins(plugins) {
 131	      var key, name;
 132	      const self = this;
 133	      const queue = [];
 134
 135	      if (Array.isArray(plugins)) {
 136	        plugins.forEach(plugin => {
 137	          if (typeof plugin === 'string') {
 138	            queue.push(plugin);
 139	          } else {
 140	            self.plugins.settings[plugin.name] = plugin.options;
 141	            queue.push(plugin.name);
 142	          }
 143	        });
 144	      } else if (plugins) {
 145	        for (key in plugins) {
 146	          if (plugins.hasOwnProperty(key)) {
 147	            self.plugins.settings[key] = plugins[key];
 148	            queue.push(key);
 149	          }
 150	        }
 151	      }
 152
 153	      while (name = queue.shift()) {
 154	        self.require(name);
 155	      }
 156	    }
 157
 158	    loadPlugin(name) {
 159	      var self = this;
 160	      var plugins = self.plugins;
 161	      var plugin = Interface.plugins[name];
 162
 163	      if (!Interface.plugins.hasOwnProperty(name)) {
 164	        throw new Error('Unable to find "' + name + '" plugin');
 165	      }
 166
 167	      plugins.requested[name] = true;
 168	      plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
 169	      plugins.names.push(name);
 170	    }
 171	    /**
 172	     * Initializes a plugin.
 173	     *
 174	     */
 175
 176
 177	    require(name) {
 178	      var self = this;
 179	      var plugins = self.plugins;
 180
 181	      if (!self.plugins.loaded.hasOwnProperty(name)) {
 182	        if (plugins.requested[name]) {
 183	          throw new Error('Plugin has circular dependency ("' + name + '")');
 184	        }
 185
 186	        self.loadPlugin(name);
 187	      }
 188
 189	      return plugins.loaded[name];
 190	    }
 191
 192	  };
 193	}
 194
 195	// https://github.com/andrewrk/node-diacritics/blob/master/index.js
 196	var latin_pat;
 197	const accent_pat = '[\u0300-\u036F\u{b7}\u{2be}]'; // \u{2bc}
 198
 199	const accent_reg = new RegExp(accent_pat, 'g');
 200	var diacritic_patterns;
 201	const latin_convert = {
 202	  'æ': 'ae',
 203	  'ⱥ': 'a',
 204	  'ø': 'o'
 205	};
 206	const convert_pat = new RegExp(Object.keys(latin_convert).join('|'), 'g');
 207	/**
 208	 * code points generated from toCodePoints();
 209	 * removed 65339 to 65345
 210	 */
 211
 212	const code_points = [[67, 67], [160, 160], [192, 438], [452, 652], [961, 961], [1019, 1019], [1083, 1083], [1281, 1289], [1984, 1984], [5095, 5095], [7429, 7441], [7545, 7549], [7680, 7935], [8580, 8580], [9398, 9449], [11360, 11391], [42792, 42793], [42802, 42851], [42873, 42897], [42912, 42922], [64256, 64260], [65313, 65338], [65345, 65370]];
 213	/**
 214	 * Remove accents
 215	 * via https://github.com/krisk/Fuse/issues/133#issuecomment-318692703
 216	 *
 217	 */
 218
 219	const asciifold = str => {
 220	  return str.normalize('NFKD').replace(accent_reg, '').toLowerCase().replace(convert_pat, function (foreignletter) {
 221	    return latin_convert[foreignletter];
 222	  });
 223	};
 224	/**
 225	 * Convert array of strings to a regular expression
 226	 *	ex ['ab','a'] => (?:ab|a)
 227	 * 	ex ['a','b'] => [ab]
 228	 *
 229	 */
 230
 231
 232	const arrayToPattern = (chars, glue = '|') => {
 233	  if (chars.length == 1) {
 234	    return chars[0];
 235	  }
 236
 237	  var longest = 1;
 238	  chars.forEach(a => {
 239	    longest = Math.max(longest, a.length);
 240	  });
 241
 242	  if (longest == 1) {
 243	    return '[' + chars.join('') + ']';
 244	  }
 245
 246	  return '(?:' + chars.join(glue) + ')';
 247	};
 248	/**
 249	 * Get all possible combinations of substrings that add up to the given string
 250	 * https://stackoverflow.com/questions/30169587/find-all-the-combination-of-substrings-that-add-up-to-the-given-string
 251	 *
 252	 */
 253
 254	const allSubstrings = input => {
 255	  if (input.length === 1) return [[input]];
 256	  var result = [];
 257	  allSubstrings(input.substring(1)).forEach(function (subresult) {
 258	    var tmp = subresult.slice(0);
 259	    tmp[0] = input.charAt(0) + tmp[0];
 260	    result.push(tmp);
 261	    tmp = subresult.slice(0);
 262	    tmp.unshift(input.charAt(0));
 263	    result.push(tmp);
 264	  });
 265	  return result;
 266	};
 267	/**
 268	 * Generate a list of diacritics from the list of code points
 269	 *
 270	 */
 271
 272	const generateDiacritics = () => {
 273	  var diacritics = {};
 274	  code_points.forEach(code_range => {
 275	    for (let i = code_range[0]; i <= code_range[1]; i++) {
 276	      let diacritic = String.fromCharCode(i);
 277	      let latin = asciifold(diacritic);
 278
 279	      if (latin == diacritic.toLowerCase()) {
 280	        continue;
 281	      }
 282
 283	      if (!(latin in diacritics)) {
 284	        diacritics[latin] = [latin];
 285	      }
 286
 287	      var patt = new RegExp(arrayToPattern(diacritics[latin]), 'iu');
 288
 289	      if (diacritic.match(patt)) {
 290	        continue;
 291	      }
 292
 293	      diacritics[latin].push(diacritic);
 294	    }
 295	  });
 296	  var latin_chars = Object.keys(diacritics); // latin character pattern
 297	  // match longer substrings first
 298
 299	  latin_chars = latin_chars.sort((a, b) => b.length - a.length);
 300	  latin_pat = new RegExp('(' + arrayToPattern(latin_chars) + accent_pat + '*)', 'g'); // build diacritic patterns
 301	  // ae needs: 
 302	  //	(?:(?:ae|Æ|Ǽ|Ǣ)|(?:A|Ⓐ|A...)(?:E|ɛ|Ⓔ...))
 303
 304	  var diacritic_patterns = {};
 305	  latin_chars.sort((a, b) => a.length - b.length).forEach(latin => {
 306	    var substrings = allSubstrings(latin);
 307	    var pattern = substrings.map(sub_pat => {
 308	      sub_pat = sub_pat.map(l => {
 309	        if (diacritics.hasOwnProperty(l)) {
 310	          return arrayToPattern(diacritics[l]);
 311	        }
 312
 313	        return l;
 314	      });
 315	      return arrayToPattern(sub_pat, '');
 316	    });
 317	    diacritic_patterns[latin] = arrayToPattern(pattern);
 318	  });
 319	  return diacritic_patterns;
 320	};
 321	/**
 322	 * Expand a regular expression pattern to include diacritics
 323	 * 	eg /a/ becomes /aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐɑAⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ/
 324	 *
 325	 */
 326
 327	const diacriticRegexPoints = regex => {
 328	  if (diacritic_patterns === undefined) {
 329	    diacritic_patterns = generateDiacritics();
 330	  }
 331
 332	  const decomposed = regex.normalize('NFKD').toLowerCase();
 333	  return decomposed.split(latin_pat).map(part => {
 334	    if (part == '') {
 335	      return '';
 336	    } // "ffl" or "ffl"
 337
 338
 339	    const no_accent = asciifold(part);
 340
 341	    if (diacritic_patterns.hasOwnProperty(no_accent)) {
 342	      return diacritic_patterns[no_accent];
 343	    } // 'أهلا' (\u{623}\u{647}\u{644}\u{627}) or 'أهلا' (\u{627}\u{654}\u{647}\u{644}\u{627})
 344
 345
 346	    const composed_part = part.normalize('NFC');
 347
 348	    if (composed_part != part) {
 349	      return arrayToPattern([part, composed_part]);
 350	    }
 351
 352	    return part;
 353	  }).join('');
 354	};
 355
 356	// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
 357
 358	/**
 359	 * A property getter resolving dot-notation
 360	 * @param  {Object}  obj     The root object to fetch property on
 361	 * @param  {String}  name    The optionally dotted property name to fetch
 362	 * @return {Object}          The resolved property value
 363	 */
 364	const getAttr = (obj, name) => {
 365	  if (!obj) return;
 366	  return obj[name];
 367	};
 368	/**
 369	 * A property getter resolving dot-notation
 370	 * @param  {Object}  obj     The root object to fetch property on
 371	 * @param  {String}  name    The optionally dotted property name to fetch
 372	 * @return {Object}          The resolved property value
 373	 */
 374
 375	const getAttrNesting = (obj, name) => {
 376	  if (!obj) return;
 377	  var part,
 378	      names = name.split(".");
 379
 380	  while ((part = names.shift()) && (obj = obj[part]));
 381
 382	  return obj;
 383	};
 384	/**
 385	 * Calculates how close of a match the
 386	 * given value is against a search token.
 387	 *
 388	 */
 389
 390	const scoreValue = (value, token, weight) => {
 391	  var score, pos;
 392	  if (!value) return 0;
 393	  value = value + '';
 394	  pos = value.search(token.regex);
 395	  if (pos === -1) return 0;
 396	  score = token.string.length / value.length;
 397	  if (pos === 0) score += 0.5;
 398	  return score * weight;
 399	};
 400	/**
 401	 *
 402	 * https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
 403	 */
 404
 405	const escape_regex = str => {
 406	  return (str + '').replace(/([\$\(-\+\.\?\[-\^\{-\}])/g, '\\$1');
 407	};
 408	/**
 409	 * Cast object property to an array if it exists and has a value
 410	 *
 411	 */
 412
 413	const propToArray = (obj, key) => {
 414	  var value = obj[key];
 415	  if (typeof value == 'function') return value;
 416
 417	  if (value && !Array.isArray(value)) {
 418	    obj[key] = [value];
 419	  }
 420	};
 421	/**
 422	 * Iterates over arrays and hashes.
 423	 *
 424	 * ```
 425	 * iterate(this.items, function(item, id) {
 426	 *    // invoked for each item
 427	 * });
 428	 * ```
 429	 *
 430	 */
 431
 432	const iterate = (object, callback) => {
 433	  if (Array.isArray(object)) {
 434	    object.forEach(callback);
 435	  } else {
 436	    for (var key in object) {
 437	      if (object.hasOwnProperty(key)) {
 438	        callback(object[key], key);
 439	      }
 440	    }
 441	  }
 442	};
 443	const cmp = (a, b) => {
 444	  if (typeof a === 'number' && typeof b === 'number') {
 445	    return a > b ? 1 : a < b ? -1 : 0;
 446	  }
 447
 448	  a = asciifold(a + '').toLowerCase();
 449	  b = asciifold(b + '').toLowerCase();
 450	  if (a > b) return 1;
 451	  if (b > a) return -1;
 452	  return 0;
 453	};
 454
 455	/**
 456	 * sifter.js
 457	 * Copyright (c) 2013–2020 Brian Reavis & contributors
 458	 *
 459	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
 460	 * file except in compliance with the License. You may obtain a copy of the License at:
 461	 * http://www.apache.org/licenses/LICENSE-2.0
 462	 *
 463	 * Unless required by applicable law or agreed to in writing, software distributed under
 464	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 465	 * ANY KIND, either express or implied. See the License for the specific language
 466	 * governing permissions and limitations under the License.
 467	 *
 468	 * @author Brian Reavis <brian@thirdroute.com>
 469	 */
 470
 471	class Sifter {
 472	  // []|{};
 473
 474	  /**
 475	   * Textually searches arrays and hashes of objects
 476	   * by property (or multiple properties). Designed
 477	   * specifically for autocomplete.
 478	   *
 479	   */
 480	  constructor(items, settings) {
 481	    this.items = items;
 482	    this.settings = settings || {
 483	      diacritics: true
 484	    };
 485	  }
 486
 487	  /**
 488	   * Splits a search string into an array of individual
 489	   * regexps to be used to match results.
 490	   *
 491	   */
 492	  tokenize(query, respect_word_boundaries, weights) {
 493	    if (!query || !query.length) return [];
 494	    const tokens = [];
 495	    const words = query.split(/\s+/);
 496	    var field_regex;
 497
 498	    if (weights) {
 499	      field_regex = new RegExp('^(' + Object.keys(weights).map(escape_regex).join('|') + ')\:(.*)$');
 500	    }
 501
 502	    words.forEach(word => {
 503	      let field_match;
 504	      let field = null;
 505	      let regex = null; // look for "field:query" tokens
 506
 507	      if (field_regex && (field_match = word.match(field_regex))) {
 508	        field = field_match[1];
 509	        word = field_match[2];
 510	      }
 511
 512	      if (word.length > 0) {
 513	        regex = escape_regex(word);
 514
 515	        if (this.settings.diacritics) {
 516	          regex = diacriticRegexPoints(regex);
 517	        }
 518
 519	        if (respect_word_boundaries) regex = "\\b" + regex;
 520	      }
 521
 522	      tokens.push({
 523	        string: word,
 524	        regex: regex ? new RegExp(regex, 'iu') : null,
 525	        field: field
 526	      });
 527	    });
 528	    return tokens;
 529	  }
 530
 531	  /**
 532	   * Returns a function to be used to score individual results.
 533	   *
 534	   * Good matches will have a higher score than poor matches.
 535	   * If an item is not a match, 0 will be returned by the function.
 536	   *
 537	   * @returns {function}
 538	   */
 539	  getScoreFunction(query, options) {
 540	    var search = this.prepareSearch(query, options);
 541	    return this._getScoreFunction(search);
 542	  }
 543
 544	  _getScoreFunction(search) {
 545	    const tokens = search.tokens,
 546	          token_count = tokens.length;
 547
 548	    if (!token_count) {
 549	      return function () {
 550	        return 0;
 551	      };
 552	    }
 553
 554	    const fields = search.options.fields,
 555	          weights = search.weights,
 556	          field_count = fields.length,
 557	          getAttrFn = search.getAttrFn;
 558
 559	    if (!field_count) {
 560	      return function () {
 561	        return 1;
 562	      };
 563	    }
 564	    /**
 565	     * Calculates the score of an object
 566	     * against the search query.
 567	     *
 568	     */
 569
 570
 571	    const scoreObject = function () {
 572	      if (field_count === 1) {
 573	        return function (token, data) {
 574	          const field = fields[0].field;
 575	          return scoreValue(getAttrFn(data, field), token, weights[field]);
 576	        };
 577	      }
 578
 579	      return function (token, data) {
 580	        var sum = 0; // is the token specific to a field?
 581
 582	        if (token.field) {
 583	          const value = getAttrFn(data, token.field);
 584
 585	          if (!token.regex && value) {
 586	            sum += 1 / field_count;
 587	          } else {
 588	            sum += scoreValue(value, token, 1);
 589	          }
 590	        } else {
 591	          iterate(weights, (weight, field) => {
 592	            sum += scoreValue(getAttrFn(data, field), token, weight);
 593	          });
 594	        }
 595
 596	        return sum / field_count;
 597	      };
 598	    }();
 599
 600	    if (token_count === 1) {
 601	      return function (data) {
 602	        return scoreObject(tokens[0], data);
 603	      };
 604	    }
 605
 606	    if (search.options.conjunction === 'and') {
 607	      return function (data) {
 608	        var i = 0,
 609	            score,
 610	            sum = 0;
 611
 612	        for (; i < token_count; i++) {
 613	          score = scoreObject(tokens[i], data);
 614	          if (score <= 0) return 0;
 615	          sum += score;
 616	        }
 617
 618	        return sum / token_count;
 619	      };
 620	    } else {
 621	      return function (data) {
 622	        var sum = 0;
 623	        iterate(tokens, token => {
 624	          sum += scoreObject(token, data);
 625	        });
 626	        return sum / token_count;
 627	      };
 628	    }
 629	  }
 630
 631	  /**
 632	   * Returns a function that can be used to compare two
 633	   * results, for sorting purposes. If no sorting should
 634	   * be performed, `null` will be returned.
 635	   *
 636	   * @return function(a,b)
 637	   */
 638	  getSortFunction(query, options) {
 639	    var search = this.prepareSearch(query, options);
 640	    return this._getSortFunction(search);
 641	  }
 642
 643	  _getSortFunction(search) {
 644	    var i, n, implicit_score;
 645	    const self = this,
 646	          options = search.options,
 647	          sort = !search.query && options.sort_empty ? options.sort_empty : options.sort,
 648	          sort_flds = [],
 649	          multipliers = [];
 650
 651	    if (typeof sort == 'function') {
 652	      return sort.bind(this);
 653	    }
 654	    /**
 655	     * Fetches the specified sort field value
 656	     * from a search result item.
 657	     *
 658	     */
 659
 660
 661	    const get_field = function get_field(name, result) {
 662	      if (name === '$score') return result.score;
 663	      return search.getAttrFn(self.items[result.id], name);
 664	    }; // parse options
 665
 666
 667	    if (sort) {
 668	      for (i = 0, n = sort.length; i < n; i++) {
 669	        if (search.query || sort[i].field !== '$score') {
 670	          sort_flds.push(sort[i]);
 671	        }
 672	      }
 673	    } // the "$score" field is implied to be the primary
 674	    // sort field, unless it's manually specified
 675
 676
 677	    if (search.query) {
 678	      implicit_score = true;
 679
 680	      for (i = 0, n = sort_flds.length; i < n; i++) {
 681	        if (sort_flds[i].field === '$score') {
 682	          implicit_score = false;
 683	          break;
 684	        }
 685	      }
 686
 687	      if (implicit_score) {
 688	        sort_flds.unshift({
 689	          field: '$score',
 690	          direction: 'desc'
 691	        });
 692	      }
 693	    } else {
 694	      for (i = 0, n = sort_flds.length; i < n; i++) {
 695	        if (sort_flds[i].field === '$score') {
 696	          sort_flds.splice(i, 1);
 697	          break;
 698	        }
 699	      }
 700	    }
 701
 702	    for (i = 0, n = sort_flds.length; i < n; i++) {
 703	      multipliers.push(sort_flds[i].direction === 'desc' ? -1 : 1);
 704	    } // build function
 705
 706
 707	    const sort_flds_count = sort_flds.length;
 708
 709	    if (!sort_flds_count) {
 710	      return null;
 711	    } else if (sort_flds_count === 1) {
 712	      const sort_fld = sort_flds[0].field;
 713	      const multiplier = multipliers[0];
 714	      return function (a, b) {
 715	        return multiplier * cmp(get_field(sort_fld, a), get_field(sort_fld, b));
 716	      };
 717	    } else {
 718	      return function (a, b) {
 719	        var i, result, field;
 720
 721	        for (i = 0; i < sort_flds_count; i++) {
 722	          field = sort_flds[i].field;
 723	          result = multipliers[i] * cmp(get_field(field, a), get_field(field, b));
 724	          if (result) return result;
 725	        }
 726
 727	        return 0;
 728	      };
 729	    }
 730	  }
 731
 732	  /**
 733	   * Parses a search query and returns an object
 734	   * with tokens and fields ready to be populated
 735	   * with results.
 736	   *
 737	   */
 738	  prepareSearch(query, optsUser) {
 739	    const weights = {};
 740	    var options = Object.assign({}, optsUser);
 741	    propToArray(options, 'sort');
 742	    propToArray(options, 'sort_empty'); // convert fields to new format
 743
 744	    if (options.fields) {
 745	      propToArray(options, 'fields');
 746	      const fields = [];
 747	      options.fields.forEach(field => {
 748	        if (typeof field == 'string') {
 749	          field = {
 750	            field: field,
 751	            weight: 1
 752	          };
 753	        }
 754
 755	        fields.push(field);
 756	        weights[field.field] = 'weight' in field ? field.weight : 1;
 757	      });
 758	      options.fields = fields;
 759	    }
 760
 761	    return {
 762	      options: options,
 763	      query: query.toLowerCase().trim(),
 764	      tokens: this.tokenize(query, options.respect_word_boundaries, weights),
 765	      total: 0,
 766	      items: [],
 767	      weights: weights,
 768	      getAttrFn: options.nesting ? getAttrNesting : getAttr
 769	    };
 770	  }
 771
 772	  /**
 773	   * Searches through all items and returns a sorted array of matches.
 774	   *
 775	   */
 776	  search(query, options) {
 777	    var self = this,
 778	        score,
 779	        search;
 780	    search = this.prepareSearch(query, options);
 781	    options = search.options;
 782	    query = search.query; // generate result scoring function
 783
 784	    const fn_score = options.score || self._getScoreFunction(search); // perform search and sort
 785
 786
 787	    if (query.length) {
 788	      iterate(self.items, (item, id) => {
 789	        score = fn_score(item);
 790
 791	        if (options.filter === false || score > 0) {
 792	          search.items.push({
 793	            'score': score,
 794	            'id': id
 795	          });
 796	        }
 797	      });
 798	    } else {
 799	      iterate(self.items, (item, id) => {
 800	        search.items.push({
 801	          'score': 1,
 802	          'id': id
 803	        });
 804	      });
 805	    }
 806
 807	    const fn_sort = self._getSortFunction(search);
 808
 809	    if (fn_sort) search.items.sort(fn_sort); // apply limits
 810
 811	    search.total = search.items.length;
 812
 813	    if (typeof options.limit === 'number') {
 814	      search.items = search.items.slice(0, options.limit);
 815	    }
 816
 817	    return search;
 818	  }
 819
 820	}
 821
 822	/**
 823	 * Return a dom element from either a dom query string, jQuery object, a dom element or html string
 824	 * https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
 825	 *
 826	 * param query should be {}
 827	 */
 828
 829	const getDom = query => {
 830	  if (query.jquery) {
 831	    return query[0];
 832	  }
 833
 834	  if (query instanceof HTMLElement) {
 835	    return query;
 836	  }
 837
 838	  if (isHtmlString(query)) {
 839	    let div = document.createElement('div');
 840	    div.innerHTML = query.trim(); // Never return a text node of whitespace as the result
 841
 842	    return div.firstChild;
 843	  }
 844
 845	  return document.querySelector(query);
 846	};
 847	const isHtmlString = arg => {
 848	  if (typeof arg === 'string' && arg.indexOf('<') > -1) {
 849	    return true;
 850	  }
 851
 852	  return false;
 853	};
 854	const escapeQuery = query => {
 855	  return query.replace(/['"\\]/g, '\\$&');
 856	};
 857	/**
 858	 * Dispatch an event
 859	 *
 860	 */
 861
 862	const triggerEvent = (dom_el, event_name) => {
 863	  var event = document.createEvent('HTMLEvents');
 864	  event.initEvent(event_name, true, false);
 865	  dom_el.dispatchEvent(event);
 866	};
 867	/**
 868	 * Apply CSS rules to a dom element
 869	 *
 870	 */
 871
 872	const applyCSS = (dom_el, css) => {
 873	  Object.assign(dom_el.style, css);
 874	};
 875	/**
 876	 * Add css classes
 877	 *
 878	 */
 879
 880	const addClasses = (elmts, ...classes) => {
 881	  var norm_classes = classesArray(classes);
 882	  elmts = castAsArray(elmts);
 883	  elmts.map(el => {
 884	    norm_classes.map(cls => {
 885	      el.classList.add(cls);
 886	    });
 887	  });
 888	};
 889	/**
 890	 * Remove css classes
 891	 *
 892	 */
 893
 894	const removeClasses = (elmts, ...classes) => {
 895	  var norm_classes = classesArray(classes);
 896	  elmts = castAsArray(elmts);
 897	  elmts.map(el => {
 898	    norm_classes.map(cls => {
 899	      el.classList.remove(cls);
 900	    });
 901	  });
 902	};
 903	/**
 904	 * Return arguments
 905	 *
 906	 */
 907
 908	const classesArray = args => {
 909	  var classes = [];
 910	  iterate(args, _classes => {
 911	    if (typeof _classes === 'string') {
 912	      _classes = _classes.trim().split(/[\11\12\14\15\40]/);
 913	    }
 914
 915	    if (Array.isArray(_classes)) {
 916	      classes = classes.concat(_classes);
 917	    }
 918	  });
 919	  return classes.filter(Boolean);
 920	};
 921	/**
 922	 * Create an array from arg if it's not already an array
 923	 *
 924	 */
 925
 926	const castAsArray = arg => {
 927	  if (!Array.isArray(arg)) {
 928	    arg = [arg];
 929	  }
 930
 931	  return arg;
 932	};
 933	/**
 934	 * Get the closest node to the evt.target matching the selector
 935	 * Stops at wrapper
 936	 *
 937	 */
 938
 939	const parentMatch = (target, selector, wrapper) => {
 940	  if (wrapper && !wrapper.contains(target)) {
 941	    return;
 942	  }
 943
 944	  while (target && target.matches) {
 945	    if (target.matches(selector)) {
 946	      return target;
 947	    }
 948
 949	    target = target.parentNode;
 950	  }
 951	};
 952	/**
 953	 * Get the first or last item from an array
 954	 *
 955	 * > 0 - right (last)
 956	 * <= 0 - left (first)
 957	 *
 958	 */
 959
 960	const getTail = (list, direction = 0) => {
 961	  if (direction > 0) {
 962	    return list[list.length - 1];
 963	  }
 964
 965	  return list[0];
 966	};
 967	/**
 968	 * Return true if an object is empty
 969	 *
 970	 */
 971
 972	const isEmptyObject = obj => {
 973	  return Object.keys(obj).length === 0;
 974	};
 975	/**
 976	 * Get the index of an element amongst sibling nodes of the same type
 977	 *
 978	 */
 979
 980	const nodeIndex = (el, amongst) => {
 981	  if (!el) return -1;
 982	  amongst = amongst || el.nodeName;
 983	  var i = 0;
 984
 985	  while (el = el.previousElementSibling) {
 986	    if (el.matches(amongst)) {
 987	      i++;
 988	    }
 989	  }
 990
 991	  return i;
 992	};
 993	/**
 994	 * Set attributes of an element
 995	 *
 996	 */
 997
 998	const setAttr = (el, attrs) => {
 999	  iterate(attrs, (val, attr) => {
1000	    if (val == null) {
1001	      el.removeAttribute(attr);
1002	    } else {
1003	      el.setAttribute(attr, '' + val);
1004	    }
1005	  });
1006	};
1007	/**
1008	 * Replace a node
1009	 */
1010
1011	const replaceNode = (existing, replacement) => {
1012	  if (existing.parentNode) existing.parentNode.replaceChild(replacement, existing);
1013	};
1014
1015	/**
1016	 * highlight v3 | MIT license | Johann Burkard <jb@eaio.com>
1017	 * Highlights arbitrary terms in a node.
1018	 *
1019	 * - Modified by Marshal <beatgates@gmail.com> 2011-6-24 (added regex)
1020	 * - Modified by Brian Reavis <brian@thirdroute.com> 2012-8-27 (cleanup)
1021	 */
1022	const highlight = (element, regex) => {
1023	  if (regex === null) return; // convet string to regex
1024
1025	  if (typeof regex === 'string') {
1026	    if (!regex.length) return;
1027	    regex = new RegExp(regex, 'i');
1028	  } // Wrap matching part of text node with highlighting <span>, e.g.
1029	  // Soccer  ->  <span class="highlight">Soc</span>cer  for regex = /soc/i
1030
1031
1032	  const highlightText = node => {
1033	    var match = node.data.match(regex);
1034
1035	    if (match && node.data.length > 0) {
1036	      var spannode = document.createElement('span');
1037	      spannode.className = 'highlight';
1038	      var middlebit = node.splitText(match.index);
1039	      middlebit.splitText(match[0].length);
1040	      var middleclone = middlebit.cloneNode(true);
1041	      spannode.appendChild(middleclone);
1042	      replaceNode(middlebit, spannode);
1043	      return 1;
1044	    }
1045
1046	    return 0;
1047	  }; // Recurse element node, looking for child text nodes to highlight, unless element
1048	  // is childless, <script>, <style>, or already highlighted: <span class="hightlight">
1049
1050
1051	  const highlightChildren = node => {
1052	    if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName) && (node.className !== 'highlight' || node.tagName !== 'SPAN')) {
1053	      for (var i = 0; i < node.childNodes.length; ++i) {
1054	        i += highlightRecursive(node.childNodes[i]);
1055	      }
1056	    }
1057	  };
1058
1059	  const highlightRecursive = node => {
1060	    if (node.nodeType === 3) {
1061	      return highlightText(node);
1062	    }
1063
1064	    highlightChildren(node);
1065	    return 0;
1066	  };
1067
1068	  highlightRecursive(element);
1069	};
1070	/**
1071	 * removeHighlight fn copied from highlight v5 and
1072	 * edited to remove with(), pass js strict mode, and use without jquery
1073	 */
1074
1075	const removeHighlight = el => {
1076	  var elements = el.querySelectorAll("span.highlight");
1077	  Array.prototype.forEach.call(elements, function (el) {
1078	    var parent = el.parentNode;
1079	    parent.replaceChild(el.firstChild, el);
1080	    parent.normalize();
1081	  });
1082	};
1083
1084	const KEY_A = 65;
1085	const KEY_RETURN = 13;
1086	const KEY_ESC = 27;
1087	const KEY_LEFT = 37;
1088	const KEY_UP = 38;
1089	const KEY_RIGHT = 39;
1090	const KEY_DOWN = 40;
1091	const KEY_BACKSPACE = 8;
1092	const KEY_DELETE = 46;
1093	const KEY_TAB = 9;
1094	const IS_MAC = typeof navigator === 'undefined' ? false : /Mac/.test(navigator.userAgent);
1095	const KEY_SHORTCUT = IS_MAC ? 'metaKey' : 'ctrlKey'; // ctrl key or apple key for ma
1096
1097	var defaults = {
1098	  options: [],
1099	  optgroups: [],
1100	  plugins: [],
1101	  delimiter: ',',
1102	  splitOn: null,
1103	  // regexp or string for splitting up values from a paste command
1104	  persist: true,
1105	  diacritics: true,
1106	  create: null,
1107	  createOnBlur: false,
1108	  createFilter: null,
1109	  highlight: true,
1110	  openOnFocus: true,
1111	  shouldOpen: null,
1112	  maxOptions: 50,
1113	  maxItems: null,
1114	  hideSelected: null,
1115	  duplicates: false,
1116	  addPrecedence: false,
1117	  selectOnTab: false,
1118	  preload: null,
1119	  allowEmptyOption: false,
1120	  //closeAfterSelect: false,
1121	  loadThrottle: 300,
1122	  loadingClass: 'loading',
1123	  dataAttr: null,
1124	  //'data-data',
1125	  optgroupField: 'optgroup',
1126	  valueField: 'value',
1127	  labelField: 'text',
1128	  disabledField: 'disabled',
1129	  optgroupLabelField: 'label',
1130	  optgroupValueField: 'value',
1131	  lockOptgroupOrder: false,
1132	  sortField: '$order',
1133	  searchField: ['text'],
1134	  searchConjunction: 'and',
1135	  mode: null,
1136	  wrapperClass: 'ts-wrapper',
1137	  controlClass: 'ts-control',
1138	  dropdownClass: 'ts-dropdown',
1139	  dropdownContentClass: 'ts-dropdown-content',
1140	  itemClass: 'item',
1141	  optionClass: 'option',
1142	  dropdownParent: null,
1143	  controlInput: '<input type="text" autocomplete="off" size="1" />',
1144	  copyClassesToDropdown: false,
1145	  placeholder: null,
1146	  hidePlaceholder: null,
1147	  shouldLoad: function (query) {
1148	    return query.length > 0;
1149	  },
1150
1151	  /*
1152	  load                 : null, // function(query, callback) { ... }
1153	  score                : null, // function(search) { ... }
1154	  onInitialize         : null, // function() { ... }
1155	  onChange             : null, // function(value) { ... }
1156	  onItemAdd            : null, // function(value, $item) { ... }
1157	  onItemRemove         : null, // function(value) { ... }
1158	  onClear              : null, // function() { ... }
1159	  onOptionAdd          : null, // function(value, data) { ... }
1160	  onOptionRemove       : null, // function(value) { ... }
1161	  onOptionClear        : null, // function() { ... }
1162	  onOptionGroupAdd     : null, // function(id, data) { ... }
1163	  onOptionGroupRemove  : null, // function(id) { ... }
1164	  onOptionGroupClear   : null, // function() { ... }
1165	  onDropdownOpen       : null, // function(dropdown) { ... }
1166	  onDropdownClose      : null, // function(dropdown) { ... }
1167	  onType               : null, // function(str) { ... }
1168	  onDelete             : null, // function(values) { ... }
1169	  */
1170	  render: {
1171	    /*
1172	    item: null,
1173	    optgroup: null,
1174	    optgroup_header: null,
1175	    option: null,
1176	    option_create: null
1177	    */
1178	  }
1179	};
1180
1181	/**
1182	 * Converts a scalar to its best string representation
1183	 * for hash keys and HTML attribute values.
1184	 *
1185	 * Transformations:
1186	 *   'str'     -> 'str'
1187	 *   null      -> ''
1188	 *   undefined -> ''
1189	 *   true      -> '1'
1190	 *   false     -> '0'
1191	 *   0         -> '0'
1192	 *   1         -> '1'
1193	 *
1194	 */
1195	const hash_key = value => {
1196	  if (typeof value === 'undefined' || value === null) return null;
1197	  return get_hash(value);
1198	};
1199	const get_hash = value => {
1200	  if (typeof value === 'boolean') return value ? '1' : '0';
1201	  return value + '';
1202	};
1203	/**
1204	 * Escapes a string for use within HTML.
1205	 *
1206	 */
1207
1208	const escape_html = str => {
1209	  return (str + '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1210	};
1211	/**
1212	 * Debounce the user provided load function
1213	 *
1214	 */
1215
1216	const loadDebounce = (fn, delay) => {
1217	  var timeout;
1218	  return function (value, callback) {
1219	    var self = this;
1220
1221	    if (timeout) {
1222	      self.loading = Math.max(self.loading - 1, 0);
1223	      clearTimeout(timeout);
1224	    }
1225
1226	    timeout = setTimeout(function () {
1227	      timeout = null;
1228	      self.loadedSearches[value] = true;
1229	      fn.call(self, value, callback);
1230	    }, delay);
1231	  };
1232	};
1233	/**
1234	 * Debounce all fired events types listed in `types`
1235	 * while executing the provided `fn`.
1236	 *
1237	 */
1238
1239	const debounce_events = (self, types, fn) => {
1240	  var type;
1241	  var trigger = self.trigger;
1242	  var event_args = {}; // override trigger method
1243
1244	  self.trigger = function () {
1245	    var type = arguments[0];
1246
1247	    if (types.indexOf(type) !== -1) {
1248	      event_args[type] = arguments;
1249	    } else {
1250	      return trigger.apply(self, arguments);
1251	    }
1252	  }; // invoke provided function
1253
1254
1255	  fn.apply(self, []);
1256	  self.trigger = trigger; // trigger queued events
1257
1258	  for (type of types) {
1259	    if (type in event_args) {
1260	      trigger.apply(self, event_args[type]);
1261	    }
1262	  }
1263	};
1264	/**
1265	 * Determines the current selection within a text input control.
1266	 * Returns an object containing:
1267	 *   - start
1268	 *   - length
1269	 *
1270	 */
1271
1272	const getSelection = input => {
1273	  return {
1274	    start: input.selectionStart || 0,
1275	    length: (input.selectionEnd || 0) - (input.selectionStart || 0)
1276	  };
1277	};
1278	/**
1279	 * Prevent default
1280	 *
1281	 */
1282
1283	const preventDefault = (evt, stop = false) => {
1284	  if (evt) {
1285	    evt.preventDefault();
1286
1287	    if (stop) {
1288	      evt.stopPropagation();
1289	    }
1290	  }
1291	};
1292	/**
1293	 * Prevent default
1294	 *
1295	 */
1296
1297	const addEvent = (target, type, callback, options) => {
1298	  target.addEventListener(type, callback, options);
1299	};
1300	/**
1301	 * Return true if the requested key is down
1302	 * Will return false if more than one control character is pressed ( when [ctrl+shift+a] != [ctrl+a] )
1303	 * The current evt may not always set ( eg calling advanceSelection() )
1304	 *
1305	 */
1306
1307	const isKeyDown = (key_name, evt) => {
1308	  if (!evt) {
1309	    return false;
1310	  }
1311
1312	  if (!evt[key_name]) {
1313	    return false;
1314	  }
1315
1316	  var count = (evt.altKey ? 1 : 0) + (evt.ctrlKey ? 1 : 0) + (evt.shiftKey ? 1 : 0) + (evt.metaKey ? 1 : 0);
1317
1318	  if (count === 1) {
1319	    return true;
1320	  }
1321
1322	  return false;
1323	};
1324	/**
1325	 * Get the id of an element
1326	 * If the id attribute is not set, set the attribute with the given id
1327	 *
1328	 */
1329
1330	const getId = (el, id) => {
1331	  const existing_id = el.getAttribute('id');
1332
1333	  if (existing_id) {
1334	    return existing_id;
1335	  }
1336
1337	  el.setAttribute('id', id);
1338	  return id;
1339	};
1340	/**
1341	 * Returns a string with backslashes added before characters that need to be escaped.
1342	 */
1343
1344	const addSlashes = str => {
1345	  return str.replace(/[\\"']/g, '\\$&');
1346	};
1347	/**
1348	 *
1349	 */
1350
1351	const append = (parent, node) => {
1352	  if (node) parent.append(node);
1353	};
1354
1355	function getSettings(input, settings_user) {
1356	  var settings = Object.assign({}, defaults, settings_user);
1357	  var attr_data = settings.dataAttr;
1358	  var field_label = settings.labelField;
1359	  var field_value = settings.valueField;
1360	  var field_disabled = settings.disabledField;
1361	  var field_optgroup = settings.optgroupField;
1362	  var field_optgroup_label = settings.optgroupLabelField;
1363	  var field_optgroup_value = settings.optgroupValueField;
1364	  var tag_name = input.tagName.toLowerCase();
1365	  var placeholder = input.getAttribute('placeholder') || input.getAttribute('data-placeholder');
1366
1367	  if (!placeholder && !settings.allowEmptyOption) {
1368	    let option = input.querySelector('option[value=""]');
1369
1370	    if (option) {
1371	      placeholder = option.textContent;
1372	    }
1373	  }
1374
1375	  var settings_element = {
1376	    placeholder: placeholder,
1377	    options: [],
1378	    optgroups: [],
1379	    items: [],
1380	    maxItems: null
1381	  };
1382	  /**
1383	   * Initialize from a <select> element.
1384	   *
1385	   */
1386
1387	  var init_select = () => {
1388	    var tagName;
1389	    var options = settings_element.options;
1390	    var optionsMap = {};
1391	    var group_count = 1;
1392
1393	    var readData = el => {
1394	      var data = Object.assign({}, el.dataset); // get plain object from DOMStringMap
1395
1396	      var json = attr_data && data[attr_data];
1397
1398	      if (typeof json === 'string' && json.length) {
1399	        data = Object.assign(data, JSON.parse(json));
1400	      }
1401
1402	      return data;
1403	    };
1404
1405	    var addOption = (option, group) => {
1406	      var value = hash_key(option.value);
1407	      if (value == null) return;
1408	      if (!value && !settings.allowEmptyOption) return; // if the option already exists, it's probably been
1409	      // duplicated in another optgroup. in this case, push
1410	      // the current group to the "optgroup" property on the
1411	      // existing option so that it's rendered in both places.
1412
1413	      if (optionsMap.hasOwnProperty(value)) {
1414	        if (group) {
1415	          var arr = optionsMap[value][field_optgroup];
1416
1417	          if (!arr) {
1418	            optionsMap[value][field_optgroup] = group;
1419	          } else if (!Array.isArray(arr)) {
1420	            optionsMap[value][field_optgroup] = [arr, group];
1421	          } else {
1422	            arr.push(group);
1423	          }
1424	        }
1425	      } else {
1426	        var option_data = readData(option);
1427	        option_data[field_label] = option_data[field_label] || option.textContent;
1428	        option_data[field_value] = option_data[field_value] || value;
1429	        option_data[field_disabled] = option_data[field_disabled] || option.disabled;
1430	        option_data[field_optgroup] = option_data[field_optgroup] || group;
1431	        option_data.$option = option;
1432	        optionsMap[value] = option_data;
1433	        options.push(option_data);
1434	      }
1435
1436	      if (option.selected) {
1437	        settings_element.items.push(value);
1438	      }
1439	    };
1440
1441	    var addGroup = optgroup => {
1442	      var id, optgroup_data;
1443	      optgroup_data = readData(optgroup);
1444	      optgroup_data[field_optgroup_label] = optgroup_data[field_optgroup_label] || optgroup.getAttribute('label') || '';
1445	      optgroup_data[field_optgroup_value] = optgroup_data[field_optgroup_value] || group_count++;
1446	      optgroup_data[field_disabled] = optgroup_data[field_disabled] || optgroup.disabled;
1447	      settings_element.optgroups.push(optgroup_data);
1448	      id = optgroup_data[field_optgroup_value];
1449	      iterate(optgroup.children, option => {
1450	        addOption(option, id);
1451	      });
1452	    };
1453
1454	    settings_element.maxItems = input.hasAttribute('multiple') ? null : 1;
1455	    iterate(input.children, child => {
1456	      tagName = child.tagName.toLowerCase();
1457
1458	      if (tagName === 'optgroup') {
1459	        addGroup(child);
1460	      } else if (tagName === 'option') {
1461	        addOption(child);
1462	      }
1463	    });
1464	  };
1465	  /**
1466	   * Initialize from a <input type="text"> element.
1467	   *
1468	   */
1469
1470
1471	  var init_textbox = () => {
1472	    const data_raw = input.getAttribute(attr_data);
1473
1474	    if (!data_raw) {
1475	      var value = input.value.trim() || '';
1476	      if (!settings.allowEmptyOption && !value.length) return;
1477	      const values = value.split(settings.delimiter);
1478	      iterate(values, value => {
1479	        const option = {};
1480	        option[field_label] = value;
1481	        option[field_value] = value;
1482	        settings_element.options.push(option);
1483	      });
1484	      settings_element.items = values;
1485	    } else {
1486	      settings_element.options = JSON.parse(data_raw);
1487	      iterate(settings_element.options, opt => {
1488	        settings_element.items.push(opt[field_value]);
1489	      });
1490	    }
1491	  };
1492
1493	  if (tag_name === 'select') {
1494	    init_select();
1495	  } else {
1496	    init_textbox();
1497	  }
1498
1499	  return Object.assign({}, defaults, settings_element, settings_user);
1500	}
1501
1502	var instance_i = 0;
1503	class TomSelect extends MicroPlugin(MicroEvent) {
1504	  // @deprecated 1.8
1505	  constructor(input_arg, user_settings) {
1506	    super();
1507	    this.order = 0;
1508	    this.isOpen = false;
1509	    this.isDisabled = false;
1510	    this.isInvalid = false;
1511	    this.isValid = true;
1512	    this.isLocked = false;
1513	    this.isFocused = false;
1514	    this.isInputHidden = false;
1515	    this.isSetup = false;
1516	    this.ignoreFocus = false;
1517	    this.hasOptions = false;
1518	    this.lastValue = '';
1519	    this.caretPos = 0;
1520	    this.loading = 0;
1521	    this.loadedSearches = {};
1522	    this.activeOption = null;
1523	    this.activeItems = [];
1524	    this.optgroups = {};
1525	    this.options = {};
1526	    this.userOptions = {};
1527	    this.items = [];
1528	    instance_i++;
1529	    var dir;
1530	    var input = getDom(input_arg);
1531
1532	    if (input.tomselect) {
1533	      throw new Error('Tom Select already initialized on this element');
1534	    }
1535
1536	    input.tomselect = this; // detect rtl environment
1537
1538	    var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
1539	    dir = computedStyle.getPropertyValue('direction'); // setup default state
1540
1541	    const settings = getSettings(input, user_settings);
1542	    this.settings = settings;
1543	    this.input = input;
1544	    this.tabIndex = input.tabIndex || 0;
1545	    this.is_select_tag = input.tagName.toLowerCase() === 'select';
1546	    this.rtl = /rtl/i.test(dir);
1547	    this.inputId = getId(input, 'tomselect-' + instance_i);
1548	    this.isRequired = input.required; // search system
1549
1550	    this.sifter = new Sifter(this.options, {
1551	      diacritics: settings.diacritics
1552	    }); // option-dependent defaults
1553
1554	    settings.mode = settings.mode || (settings.maxItems === 1 ? 'single' : 'multi');
1555
1556	    if (typeof settings.hideSelected !== 'boolean') {
1557	      settings.hideSelected = settings.mode === 'multi';
1558	    }
1559
1560	    if (typeof settings.hidePlaceholder !== 'boolean') {
1561	      settings.hidePlaceholder = settings.mode !== 'multi';
1562	    } // set up createFilter callback
1563
1564
1565	    var filter = settings.createFilter;
1566
1567	    if (typeof filter !== 'function') {
1568	      if (typeof filter === 'string') {
1569	        filter = new RegExp(filter);
1570	      }
1571
1572	      if (filter instanceof RegExp) {
1573	        settings.createFilter = input => filter.test(input);
1574	      } else {
1575	        settings.createFilter = () => true;
1576	      }
1577	    }
1578
1579	    this.initializePlugins(settings.plugins);
1580	    this.setupCallbacks();
1581	    this.setupTemplates(); // Create all elements
1582
1583	    const wrapper = getDom('<div>');
1584	    const control = getDom('<div>');
1585
1586	    const dropdown = this._render('dropdown');
1587
1588	    const dropdown_content = getDom(`<div role="listbox" tabindex="-1">`);
1589	    const classes = this.input.getAttribute('class') || '';
1590	    const inputMode = settings.mode;
1591	    var control_input;
1592	    addClasses(wrapper, settings.wrapperClass, classes, inputMode);
1593	    addClasses(control, settings.controlClass);
1594	    append(wrapper, control);
1595	    addClasses(dropdown, settings.dropdownClass, inputMode);
1596
1597	    if (settings.copyClassesToDropdown) {
1598	      addClasses(dropdown, classes);
1599	    }
1600
1601	    addClasses(dropdown_content, settings.dropdownContentClass);
1602	    append(dropdown, dropdown_content);
1603	    getDom(settings.dropdownParent || wrapper).appendChild(dropdown); // default controlInput
1604
1605	    if (isHtmlString(settings.controlInput)) {
1606	      control_input = getDom(settings.controlInput); // set attributes
1607
1608	      var attrs = ['autocorrect', 'autocapitalize', 'autocomplete'];
1609	      iterate(attrs, attr => {
1610	        if (input.getAttribute(attr)) {
1611	          setAttr(control_input, {
1612	            [attr]: input.getAttribute(attr)
1613	          });
1614	        }
1615	      });
1616	      control_input.tabIndex = -1;
1617	      control.appendChild(control_input);
1618	      this.focus_node = control_input; // dom element	
1619	    } else if (settings.controlInput) {
1620	      control_input = getDom(settings.controlInput);
1621	      this.focus_node = control_input;
1622	    } else {
1623	      control_input = getDom('<input/>');
1624	      this.focus_node = control;
1625	    }
1626
1627	    this.wrapper = wrapper;
1628	    this.dropdown = dropdown;
1629	    this.dropdown_content = dropdown_content;
1630	    this.control = control;
1631	    this.control_input = control_input;
1632	    this.setup();
1633	  }
1634	  /**
1635	   * set up event bindings.
1636	   *
1637	   */
1638
1639
1640	  setup() {
1641	    const self = this;
1642	    const settings = self.settings;
1643	    const control_input = self.control_input;
1644	    const dropdown = self.dropdown;
1645	    const dropdown_content = self.dropdown_content;
1646	    const wrapper = self.wrapper;
1647	    const control = self.control;
1648	    const input = self.input;
1649	    const focus_node = self.focus_node;
1650	    const passive_event = {
1651	      passive: true
1652	    };
1653	    const listboxId = self.inputId + '-ts-dropdown';
1654	    setAttr(dropdown_content, {
1655	      id: listboxId
1656	    });
1657	    setAttr(focus_node, {
1658	      role: 'combobox',
1659	      'aria-haspopup': 'listbox',
1660	      'aria-expanded': 'false',
1661	      'aria-controls': listboxId
1662	    });
1663	    const control_id = getId(focus_node, self.inputId + '-ts-control');
1664	    const query = "label[for='" + escapeQuery(self.inputId) + "']";
1665	    const label = document.querySelector(query);
1666	    const label_click = self.focus.bind(self);
1667
1668	    if (label) {
1669	      addEvent(label, 'click', label_click);
1670	      setAttr(label, {
1671	        for: control_id
1672	      });
1673	      const label_id = getId(label, self.inputId + '-ts-label');
1674	      setAttr(focus_node, {
1675	        'aria-labelledby': label_id
1676	      });
1677	      setAttr(dropdown_content, {
1678	        'aria-labelledby': label_id
1679	      });
1680	    }
1681
1682	    wrapper.style.width = input.style.width;
1683
1684	    if (self.plugins.names.length) {
1685	      const classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
1686	      addClasses([wrapper, dropdown], classes_plugins);
1687	    }
1688
1689	    if ((settings.maxItems === null || settings.maxItems > 1) && self.is_select_tag) {
1690	      setAttr(input, {
1691	        multiple: 'multiple'
1692	      });
1693	    }
1694
1695	    if (self.settings.placeholder) {
1696	      setAttr(control_input, {
1697	        placeholder: settings.placeholder
1698	      });
1699	    } // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
1700
1701
1702	    if (!self.settings.splitOn && self.settings.delimiter) {
1703	      self.settings.splitOn = new RegExp('\\s*' + escape_regex(self.settings.delimiter) + '+\\s*');
1704	    } // debounce user defined load() if loadThrottle > 0
1705	    // after initializePlugins() so plugins can create/modify user defined loaders
1706
1707
1708	    if (settings.load && settings.loadThrottle) {
1709	      settings.load = loadDebounce(settings.load, settings.loadThrottle);
1710	    }
1711
1712	    self.control_input.type = input.type; // clicking on an option should select it
1713
1714	    addEvent(dropdown, 'click', evt => {
1715	      const option = parentMatch(evt.target, '[data-selectable]');
1716
1717	      if (option) {
1718	        self.onOptionSelect(evt, option);
1719	        preventDefault(evt, true);
1720	      }
1721	    });
1722	    addEvent(control, 'click', evt => {
1723	      var target_match = parentMatch(evt.target, '[data-ts-item]', control);
1724
1725	      if (target_match && self.onItemSelect(evt, target_match)) {
1726	        preventDefault(evt, true);
1727	        return;
1728	      } // retain focus (see control_input mousedown)
1729
1730
1731	      if (control_input.value != '') {
1732	        return;
1733	      }
1734
1735	      self.onClick();
1736	      preventDefault(evt, true);
1737	    }); // keydown on focus_node for arrow_down/arrow_up
1738
1739	    addEvent(focus_node, 'keydown', e => self.onKeyDown(e)); // keypress and input/keyup
1740
1741	    addEvent(control_input, 'keypress', e => self.onKeyPress(e));
1742	    addEvent(control_input, 'input', e => self.onInput(e));
1743	    addEvent(focus_node, 'resize', () => self.positionDropdown(), passive_event);
1744	    addEvent(focus_node, 'blur', e => self.onBlur(e));
1745	    addEvent(focus_node, 'focus', e => self.onFocus(e));
1746	    addEvent(focus_node, 'paste', e => self.onPaste(e));
1747
1748	    const doc_mousedown = evt => {
1749	      // blur if target is outside of this instance
1750	      // dropdown is not always inside wrapper
1751	      const target = evt.composedPath()[0];
1752
1753	      if (!wrapper.contains(target) && !dropdown.contains(target)) {
1754	        if (self.isFocused) {
1755	          self.blur();
1756	        }
1757
1758	        self.inputState();
1759	        return;
1760	      } // retain focus by preventing native handling. if the
1761	      // event target is the input it should not be modified.
1762	      // otherwise, text selection within the input won't work.
1763	      // Fixes bug #212 which is no covered by tests
1764
1765
1766	      if (target == control_input && self.isOpen) {
1767	        evt.stopPropagation(); // clicking anywhere in the control should not blur the control_input (which would close the dropdown)
1768	      } else {
1769	        preventDefault(evt, true);
1770	      }
1771	    };
1772
1773	    var win_scroll = () => {
1774	      if (self.isOpen) {
1775	        self.positionDropdown();
1776	      }
1777	    };
1778
1779	    addEvent(document, 'mousedown', doc_mousedown);
1780	    addEvent(window, 'scroll', win_scroll, passive_event);
1781	    addEvent(window, 'resize', win_scroll, passive_event);
1782
1783	    this._destroy = () => {
1784	      document.removeEventListener('mousedown', doc_mousedown);
1785	      window.removeEventListener('sroll', win_scroll);
1786	      window.removeEventListener('resize', win_scroll);
1787	      if (label) label.removeEventListener('click', label_click);
1788	    }; // store original html and tab index so that they can be
1789	    // restored when the destroy() method is called.
1790
1791
1792	    this.revertSettings = {
1793	      innerHTML: input.innerHTML,
1794	      tabIndex: input.tabIndex
1795	    };
1796	    input.tabIndex = -1;
1797	    input.insertAdjacentElement('afterend', self.wrapper);
1798	    self.sync(false);
1799	    settings.items = [];
1800	    delete settings.optgroups;
1801	    delete settings.options;
1802	    addEvent(input, 'invalid', e => {
1803	      if (self.isValid) {
1804	        self.isValid = false;
1805	        self.isInvalid = true;
1806	        self.refreshState();
1807	      }
1808	    });
1809	    self.updateOriginalInput();
1810	    self.refreshItems();
1811	    self.close(false);
1812	    self.inputState();
1813	    self.isSetup = true;
1814
1815	    if (input.disabled) {
1816	      self.disable();
1817	    } else {
1818	      self.enable(); //sets tabIndex
1819	    }
1820
1821	    self.on('change', this.onChange);
1822	    addClasses(input, 'tomselected', 'ts-hidden-accessible');
1823	    self.trigger('initialize'); // preload options
1824
1825	    if (settings.preload === true) {
1826	      self.preload();
1827	    }
1828	  }
1829	  /**
1830	   * Register options and optgroups
1831	   *
1832	   */
1833
1834
1835	  setupOptions(options = [], optgroups = []) {
1836	    // build options table
1837	    this.addOptions(options); // build optgroup table
1838
1839	    iterate(optgroups, optgroup => {
1840	      this.registerOptionGroup(optgroup);
1841	    });
1842	  }
1843	  /**
1844	   * Sets up default rendering functions.
1845	   */
1846
1847
1848	  setupTemplates() {
1849	    var self = this;
1850	    var field_label = self.settings.labelField;
1851	    var field_optgroup = self.settings.optgroupLabelField;
1852	    var templates = {
1853	      'optgroup': data => {
1854	        let optgroup = document.createElement('div');
1855	        optgroup.className = 'optgroup';
1856	        optgroup.appendChild(data.options);
1857	        return optgroup;
1858	      },
1859	      'optgroup_header': (data, escape) => {
1860	        return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
1861	      },
1862	      'option': (data, escape) => {
1863	        return '<div>' + escape(data[field_label]) + '</div>';
1864	      },
1865	      'item': (data, escape) => {
1866	        return '<div>' + escape(data[field_label]) + '</div>';
1867	      },
1868	      'option_create': (data, escape) => {
1869	        return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
1870	      },
1871	      'no_results': () => {
1872	        return '<div class="no-results">No results found</div>';
1873	      },
1874	      'loading': () => {
1875	        return '<div class="spinner"></div>';
1876	      },
1877	      'not_loading': () => {},
1878	      'dropdown': () => {
1879	        return '<div></div>';
1880	      }
1881	    };
1882	    self.settings.render = Object.assign({}, templates, self.settings.render);
1883	  }
1884	  /**
1885	   * Maps fired events to callbacks provided
1886	   * in the settings used when creating the control.
1887	   */
1888
1889
1890	  setupCallbacks() {
1891	    var key, fn;
1892	    var callbacks = {
1893	      'initialize': 'onInitialize',
1894	      'change': 'onChange',
1895	      'item_add': 'onItemAdd',
1896	      'item_remove': 'onItemRemove',
1897	      'item_select': 'onItemSelect',
1898	      'clear': 'onClear',
1899	      'option_add': 'onOptionAdd',
1900	      'option_remove': 'onOptionRemove',
1901	      'option_clear': 'onOptionClear',
1902	      'optgroup_add': 'onOptionGroupAdd',
1903	      'optgroup_remove': 'onOptionGroupRemove',
1904	      'optgroup_clear': 'onOptionGroupClear',
1905	      'dropdown_open': 'onDropdownOpen',
1906	      'dropdown_close': 'onDropdownClose',
1907	      'type': 'onType',
1908	      'load': 'onLoad',
1909	      'focus': 'onFocus',
1910	      'blur': 'onBlur'
1911	    };
1912
1913	    for (key in callbacks) {
1914	      fn = this.settings[callbacks[key]];
1915	      if (fn) this.on(key, fn);
1916	    }
1917	  }
1918	  /**
1919	   * Sync the Tom Select instance with the original input or select
1920	   *
1921	   */
1922
1923
1924	  sync(get_settings = true) {
1925	    const self = this;
1926	    const settings = get_settings ? getSettings(self.input, {
1927	      delimiter: self.settings.delimiter
1928	    }) : self.settings;
1929	    self.setupOptions(settings.options, settings.optgroups);
1930	    self.setValue(settings.items, true); // silent prevents recursion
1931
1932	    self.lastQuery = null; // so updated options will be displayed in dropdown
1933	  }
1934	  /**
1935	   * Triggered when the main control element
1936	   * has a click event.
1937	   *
1938	   */
1939
1940
1941	  onClick() {
1942	    var self = this;
1943
1944	    if (self.activeItems.length > 0) {
1945	      self.clearActiveItems();
1946	      self.focus();
1947	      return;
1948	    }
1949
1950	    if (self.isFocused && self.isOpen) {
1951	      self.blur();
1952	    } else {
1953	      self.focus();
1954	    }
1955	  }
1956	  /**
1957	   * @deprecated v1.7
1958	   *
1959	   */
1960
1961
1962	  onMouseDown() {}
1963	  /**
1964	   * Triggered when the value of the control has been changed.
1965	   * This should propagate the event to the original DOM
1966	   * input / select element.
1967	   */
1968
1969
1970	  onChange() {
1971	    triggerEvent(this.input, 'input');
1972	    triggerEvent(this.input, 'change');
1973	  }
1974	  /**
1975	   * Triggered on <input> paste.
1976	   *
1977	   */
1978
1979
1980	  onPaste(e) {
1981	    var self = this;
1982
1983	    if (self.isInputHidden || self.isLocked) {
1984	      preventDefault(e);
1985	      return;
1986	    } // If a regex or string is included, this will split the pasted
1987	    // input and create Items for each separate value
1988
1989
1990	    if (self.settings.splitOn) {
1991	      // Wait for pasted text to be recognized in value
1992	      setTimeout(() => {
1993	        var pastedText = self.inputValue();
1994
1995	        if (!pastedText.match(self.settings.splitOn)) {
1996	          return;
1997	        }
1998
1999	        var splitInput = pastedText.trim().split(self.settings.splitOn);
2000	        iterate(splitInput, piece => {
2001	          self.createItem(piece);
2002	        });
2003	      }, 0);
2004	    }
2005	  }
2006	  /**
2007	   * Triggered on <input> keypress.
2008	   *
2009	   */
2010
2011
2012	  onKeyPress(e) {
2013	    var self = this;
2014
2015	    if (self.isLocked) {
2016	      preventDefault(e);
2017	      return;
2018	    }
2019
2020	    var character = String.fromCharCode(e.keyCode || e.which);
2021
2022	    if (self.settings.create && self.settings.mode === 'multi' && character === self.settings.delimiter) {
2023	      self.createItem();
2024	      preventDefault(e);
2025	      return;
2026	    }
2027	  }
2028	  /**
2029	   * Triggered on <input> keydown.
2030	   *
2031	   */
2032
2033
2034	  onKeyDown(e) {
2035	    var self = this;
2036
2037	    if (self.isLocked) {
2038	      if (e.keyCode !== KEY_TAB) {
2039	        preventDefault(e);
2040	      }
2041
2042	      return;
2043	    }
2044
2045	    switch (e.keyCode) {
2046	      // ctrl+A: select all
2047	      case KEY_A:
2048	        if (isKeyDown(KEY_SHORTCUT, e)) {
2049	          if (self.control_input.value == '') {
2050	            preventDefault(e);
2051	            self.selectAll();
2052	            return;
2053	          }
2054	        }
2055
2056	        break;
2057	      // esc: close dropdown
2058
2059	      case KEY_ESC:
2060	        if (self.isOpen) {
2061	          preventDefault(e, true);
2062	          self.close();
2063	        }
2064
2065	        self.clearActiveItems();
2066	        return;
2067	      // down: open dropdown or move selection down
2068
2069	      case KEY_DOWN:
2070	        if (!self.isOpen && self.hasOptions) {
2071	          self.open();
2072	        } else if (self.activeOption) {
2073	          let next = self.getAdjacent(self.activeOption, 1);
2074	          if (next) self.setActiveOption(next);
2075	        }
2076
2077	        preventDefault(e);
2078	        return;
2079	      // up: move selection up
2080
2081	      case KEY_UP:
2082	        if (self.activeOption) {
2083	          let prev = self.getAdjacent(self.activeOption, -1);
2084	          if (prev) self.setActiveOption(prev);
2085	        }
2086
2087	        preventDefault(e);
2088	        return;
2089	      // return: select active option
2090
2091	      case KEY_RETURN:
2092	        if (self.canSelect(self.activeOption)) {
2093	          self.onOptionSelect(e, self.activeOption);
2094	          preventDefault(e); // if the option_create=null, the dropdown might be closed
2095	        } else if (self.settings.create && self.createItem()) {
2096	          preventDefault(e);
2097	        }
2098
2099	        return;
2100	      // left: modifiy item selection to the left
2101
2102	      case KEY_LEFT:
2103	        self.advanceSelection(-1, e);
2104	        return;
2105	      // right: modifiy item selection to the right
2106
2107	      case KEY_RIGHT:
2108	        self.advanceSelection(1, e);
2109	        return;
2110	      // tab: select active option and/or create item
2111
2112	      case KEY_TAB:
2113	        if (self.settings.selectOnTab) {
2114	          if (self.canSelect(self.activeOption)) {
2115	            self.onOptionSelect(e, self.activeOption); // prevent default [tab] behaviour of jump to the next field
2116	            // if select isFull, then the dropdown won't be open and [tab] will work normally
2117
2118	            preventDefault(e);
2119	          }
2120
2121	          if (self.settings.create && self.createItem()) {
2122	            preventDefault(e);
2123	          }
2124	        }
2125
2126	        return;
2127	      // delete|backspace: delete items
2128
2129	      case KEY_BACKSPACE:
2130	      case KEY_DELETE:
2131	        self.deleteSelection(e);
2132	        return;
2133	    } // don't enter text in the control_input when active items are selected
2134
2135
2136	    if (self.isInputHidden && !isKeyDown(KEY_SHORTCUT, e)) {
2137	      preventDefault(e);
2138	    }
2139	  }
2140	  /**
2141	   * Triggered on <input> keyup.
2142	   *
2143	   */
2144
2145
2146	  onInput(e) {
2147	    var self = this;
2148
2149	    if (self.isLocked) {
2150	      return;
2151	    }
2152
2153	    var value = self.inputValue();
2154
2155	    if (self.lastValue !== value) {
2156	      self.lastValue = value;
2157
2158	      if (self.settings.shouldLoad.call(self, value)) {
2159	        self.load(value);
2160	      }
2161
2162	      self.refreshOptions();
2163	      self.trigger('type', value);
2164	    }
2165	  }
2166	  /**
2167	   * Triggered on <input> focus.
2168	   *
2169	   */
2170
2171
2172	  onFocus(e) {
2173	    var self = this;
2174	    var wasFocused = self.isFocused;
2175
2176	    if (self.isDisabled) {
2177	      self.blur();
2178	      preventDefault(e);
2179	      return;
2180	    }
2181
2182	    if (self.ignoreFocus) return;
2183	    self.isFocused = true;
2184	    if (self.settings.preload === 'focus') self.preload();
2185	    if (!wasFocused) self.trigger('focus');
2186
2187	    if (!self.activeItems.length) {
2188	      self.showInput();
2189	      self.refreshOptions(!!self.settings.openOnFocus);
2190	    }
2191
2192	    self.refreshState();
2193	  }
2194	  /**
2195	   * Triggered on <input> blur.
2196	   *
2197	   */
2198
2199
2200	  onBlur(e) {
2201	    if (document.hasFocus() === false) return;
2202	    var self = this;
2203	    if (!self.isFocused) return;
2204	    self.isFocused = false;
2205	    self.ignoreFocus = false;
2206
2207	    var deactivate = () => {
2208	      self.close();
2209	      self.setActiveItem();
2210	      self.setCaret(self.items.length);
2211	      self.trigger('blur');
2212	    };
2213
2214	    if (self.settings.create && self.settings.createOnBlur) {
2215	      self.createItem(null, false, deactivate);
2216	    } else {
2217	      deactivate();
2218	    }
2219	  }
2220	  /**
2221	   * Triggered when the user clicks on an option
2222	   * in the autocomplete dropdown menu.
2223	   *
2224	   */
2225
2226
2227	  onOptionSelect(evt, option) {
2228	    var value,
2229	        self = this; // should not be possible to trigger a option under a disabled optgroup
2230
2231	    if (option.parentElement && option.parentElement.matches('[data-disabled]')) {
2232	      return;
2233	    }
2234
2235	    if (option.classList.contains('create')) {
2236	      self.createItem(null, true, () => {
2237	        if (self.settings.closeAfterSelect) {
2238	          self.close();
2239	        }
2240	      });
2241	    } else {
2242	      value = option.dataset.value;
2243
2244	      if (typeof value !== 'undefined') {
2245	        self.lastQuery = null;
2246	        self.addItem(value);
2247
2248	        if (self.settings.closeAfterSelect) {
2249	          self.close();
2250	        }
2251
2252	        if (!self.settings.hideSelected && evt.type && /click/.test(evt.type)) {
2253	          self.setActiveOption(option);
2254	        }
2255	      }
2256	    }
2257	  }
2258	  /**
2259	   * Return true if the given option can be selected
2260	   *
2261	   */
2262
2263
2264	  canSelect(option) {
2265	    if (this.isOpen && option && this.dropdown_content.contains(option)) {
2266	      return true;
2267	    }
2268
2269	    return false;
2270	  }
2271	  /**
2272	   * Triggered when the user clicks on an item
2273	   * that has been selected.
2274	   *
2275	   */
2276
2277
2278	  onItemSelect(evt, item) {
2279	    var self = this;
2280
2281	    if (!self.isLocked && self.settings.mode === 'multi') {
2282	      preventDefault(evt);
2283	      self.setActiveItem(item, evt);
2284	      return true;
2285	    }
2286
2287	    return false;
2288	  }
2289	  /**
2290	   * Determines whether or not to invoke
2291	   * the user-provided option provider / loader
2292	   *
2293	   * Note, there is a subtle difference between
2294	   * this.canLoad() and this.settings.shouldLoad();
2295	   *
2296	   *	- settings.shouldLoad() is a user-input validator.
2297	   *	When false is returned, the not_loading template
2298	   *	will be added to the dropdown
2299	   *
2300	   *	- canLoad() is lower level validator that checks
2301	   * 	the Tom Select instance. There is no inherent user
2302	   *	feedback when canLoad returns false
2303	   *
2304	   */
2305
2306
2307	  canLoad(value) {
2308	    if (!this.settings.load) return false;
2309	    if (this.loadedSearches.hasOwnProperty(value)) return false;
2310	    return true;
2311	  }
2312	  /**
2313	   * Invokes the user-provided option provider / loader.
2314	   *
2315	   */
2316
2317
2318	  load(value) {
2319	    const self = this;
2320	    if (!self.canLoad(value)) return;
2321	    addClasses(self.wrapper, self.settings.loadingClass);
2322	    self.loading++;
2323	    const callback = self.loadCallback.bind(self);
2324	    self.settings.load.call(self, value, callback);
2325	  }
2326	  /**
2327	   * Invoked by the user-provided option provider
2328	   *
2329	   */
2330
2331
2332	  loadCallback(options, optgroups) {
2333	    const self = this;
2334	    self.loading = Math.max(self.loading - 1, 0);
2335	    self.lastQuery = null;
2336	    self.clearActiveOption(); // when new results load, focus should be on first option
2337
2338	    self.setupOptions(options, optgroups);
2339	    self.refreshOptions(self.isFocused && !self.isInputHidden);
2340
2341	    if (!self.loading) {
2342	      removeClasses(self.wrapper, self.settings.loadingClass);
2343	    }
2344
2345	    self.trigger('load', options, optgroups);
2346	  }
2347
2348	  preload() {
2349	    var classList = this.wrapper.classList;
2350	    if (classList.contains('preloaded')) return;
2351	    classList.add('preloaded');
2352	    this.load('');
2353	  }
2354	  /**
2355	   * Sets the input field of the control to the specified value.
2356	   *
2357	   */
2358
2359
2360	  setTextboxValue(value = '') {
2361	    var input = this.control_input;
2362	    var changed = input.value !== value;
2363
2364	    if (changed) {
2365	      input.value = value;
2366	      triggerEvent(input, 'update');
2367	      this.lastValue = value;
2368	    }
2369	  }
2370	  /**
2371	   * Returns the value of the control. If multiple items
2372	   * can be selected (e.g. <select multiple>), this returns
2373	   * an array. If only one item can be selected, this
2374	   * returns a string.
2375	   *
2376	   */
2377
2378
2379	  getValue() {
2380	    if (this.is_select_tag && this.input.hasAttribute('multiple')) {
2381	      return this.items;
2382	    }
2383
2384	    return this.items.join(this.settings.delimiter);
2385	  }
2386	  /**
2387	   * Resets the selected items to the given value.
2388	   *
2389	   */
2390
2391
2392	  setValue(value, silent) {
2393	    var events = silent ? [] : ['change'];
2394	    debounce_events(this, events, () => {
2395	      this.clear(silent);
2396	      this.addItems(value, silent);
2397	    });
2398	  }
2399	  /**
2400	   * Resets the number of max items to the given value
2401	   *
2402	   */
2403
2404
2405	  setMaxItems(value) {
2406	    if (value === 0) value = null; //reset to unlimited items.
2407
2408	    this.settings.maxItems = value;
2409	    this.refreshState();
2410	  }
2411	  /**
2412	   * Sets the selected item.
2413	   *
2414	   */
2415
2416
2417	  setActiveItem(item, e) {
2418	    var self = this;
2419	    var eventName;
2420	    var i, begin, end, swap;
2421	    var last;
2422	    if (self.settings.mode === 'single') return; // clear the active selection
2423
2424	    if (!item) {
2425	      self.clearActiveItems();
2426
2427	      if (self.isFocused) {
2428	        self.showInput();
2429	      }
2430
2431	      return;
2432	    } // modify selection
2433
2434
2435	    eventName = e && e.type.toLowerCase();
2436
2437	    if (eventName === 'click' && isKeyDown('shiftKey', e) && self.activeItems.length) {
2438	      last = self.getLastActive();
2439	      begin = Array.prototype.indexOf.call(self.control.children, last);
2440	      end = Array.prototype.indexOf.call(self.control.children, item);
2441
2442	      if (begin > end) {
2443	        swap = begin;
2444	        begin = end;
2445	        end = swap;
2446	      }
2447
2448	      for (i = begin; i <= end; i++) {
2449	        item = self.control.children[i];
2450
2451	        if (self.activeItems.indexOf(item) === -1) {
2452	          self.setActiveItemClass(item);
2453	        }
2454	      }
2455
2456	      preventDefault(e);
2457	    } else if (eventName === 'click' && isKeyDown(KEY_SHORTCUT, e) || eventName === 'keydown' && isKeyDown('shiftKey', e)) {
2458	      if (item.classList.contains('active')) {
2459	        self.removeActiveItem(item);
2460	      } else {
2461	        self.setActiveItemClass(item);
2462	      }
2463	    } else {
2464	      self.clearActiveItems();
2465	      self.setActiveItemClass(item);
2466	    } // ensure control has focus
2467
2468
2469	    self.hideInput();
2470
2471	    if (!self.isFocused) {
2472	      self.focus();
2473	    }
2474	  }
2475	  /**
2476	   * Set the active and last-active classes
2477	   *
2478	   */
2479
2480
2481	  setActiveItemClass(item) {
2482	    const self = this;
2483	    const last_active = self.control.querySelector('.last-active');
2484	    if (last_active) removeClasses(last_active, 'last-active');
2485	    addClasses(item, 'active last-active');
2486	    self.trigger('item_select', item);
2487
2488	    if (self.activeItems.indexOf(item) == -1) {
2489	      self.activeItems.push(item);
2490	    }
2491	  }
2492	  /**
2493	   * Remove active item
2494	   *
2495	   */
2496
2497
2498	  removeActiveItem(item) {
2499	    var idx = this.activeItems.indexOf(item);
2500	    this.activeItems.splice(idx, 1);
2501	    removeClasses(item, 'active');
2502	  }
2503	  /**
2504	   * Clears all the active items
2505	   *
2506	   */
2507
2508
2509	  clearActiveItems() {
2510	    removeClasses(this.activeItems, 'active');
2511	    this.activeItems = [];
2512	  }
2513	  /**
2514	   * Sets the selected item in the dropdown menu
2515	   * of available options.
2516	   *
2517	   */
2518
2519
2520	  setActiveOption(option) {
2521	    if (option === this.activeOption) {
2522	      return;
2523	    }
2524
2525	    this.clearActiveOption();
2526	    if (!option) return;
2527	    this.activeOption = option;
2528	    setAttr(this.focus_node, {
2529	      'aria-activedescendant': option.getAttribute('id')
2530	    });
2531	    setAttr(option, {
2532	      'aria-selected': 'true'
2533	    });
2534	    addClasses(option, 'active');
2535	    this.scrollToOption(option);
2536	  }
2537	  /**
2538	   * Sets the dropdown_content scrollTop to display the option
2539	   *
2540	   */
2541
2542
2543	  scrollToOption(option, behavior) {
2544	    if (!option) return;
2545	    const content = this.dropdown_content;
2546	    const height_menu = content.clientHeight;
2547	    const scrollTop = content.scrollTop || 0;
2548	    const height_item = option.offsetHeight;
2549	    const y = option.getBoundingClientRect().top - content.getBoundingClientRect().top + scrollTop;
2550
2551	    if (y + height_item > height_menu + scrollTop) {
2552	      this.scroll(y - height_menu + height_item, behavior);
2553	    } else if (y < scrollTop) {
2554	      this.scroll(y, behavior);
2555	    }
2556	  }
2557	  /**
2558	   * Scroll the dropdown to the given position
2559	   *
2560	   */
2561
2562
2563	  scroll(scrollTop, behavior) {
2564	    const content = this.dropdown_content;
2565
2566	    if (behavior) {
2567	      content.style.scrollBehavior = behavior;
2568	    }
2569
2570	    content.scrollTop = scrollTop;
2571	    content.style.scrollBehavior = '';
2572	  }
2573	  /**
2574	   * Clears the active option
2575	   *
2576	   */
2577
2578
2579	  clearActiveOption() {
2580	    if (this.activeOption) {
2581	      removeClasses(this.activeOption, 'active');
2582	      setAttr(this.activeOption, {
2583	        'aria-selected': null
2584	      });
2585	    }
2586
2587	    this.activeOption = null;
2588	    setAttr(this.focus_node, {
2589	      'aria-activedescendant': null
2590	    });
2591	  }
2592	  /**
2593	   * Selects all items (CTRL + A).
2594	   */
2595
2596
2597	  selectAll() {
2598	    if (this.settings.mode === 'single') return;
2599	    const activeItems = this.controlChildren();
2600	    if (!activeItems.length) return;
2601	    this.hideInput();
2602	    this.close();
2603	    this.activeItems = activeItems;
2604	    addClasses(activeItems, 'active');
2605	  }
2606	  /**
2607	   * Determines if the control_input should be in a hidden or visible state
2608	   *
2609	   */
2610
2611
2612	  inputState() {
2613	    var self = this;
2614	    if (!self.control.contains(self.control_input)) return;
2615	    setAttr(self.control_input, {
2616	      placeholder: self.settings.placeholder
2617	    });
2618
2619	    if (self.activeItems.length > 0 || !self.isFocused && self.settings.hidePlaceholder && self.items.length > 0) {
2620	      self.setTextboxValue();
2621	      self.isInputHidden = true;
2622	    } else {
2623	      if (self.settings.hidePlaceholder && self.items.length > 0) {
2624	        setAttr(self.control_input, {
2625	          placeholder: ''
2626	        });
2627	      }
2628
2629	      self.isInputHidden = false;
2630	    }
2631
2632	    self.wrapper.classList.toggle('input-hidden', self.isInputHidden);
2633	  }
2634	  /**
2635	   * Hides the input element out of view, while
2636	   * retaining its focus.
2637	   * @deprecated 1.3
2638	   */
2639
2640
2641	  hideInput() {
2642	    this.inputState();
2643	  }
2644	  /**
2645	   * Restores input visibility.
2646	   * @deprecated 1.3
2647	   */
2648
2649
2650	  showInput() {
2651	    this.inputState();
2652	  }
2653	  /**
2654	   * Get the input value
2655	   */
2656
2657
2658	  inputValue() {
2659	    return this.control_input.value.trim();
2660	  }
2661	  /**
2662	   * Gives the control focus.
2663	   */
2664
2665
2666	  focus() {
2667	    var self = this;
2668	    if (self.isDisabled) return;
2669	    self.ignoreFocus = true;
2670
2671	    if (self.control_input.offsetWidth) {
2672	      self.control_input.focus();
2673	    } else {
2674	      self.focus_node.focus();
2675	    }
2676
2677	    setTimeout(() => {
2678	      self.ignoreFocus = false;
2679	      self.onFocus();
2680	    }, 0);
2681	  }
2682	  /**
2683	   * Forces the control out of focus.
2684	   *
2685	   */
2686
2687
2688	  blur() {
2689	    this.focus_node.blur();
2690	    this.onBlur();
2691	  }
2692	  /**
2693	   * Returns a function that scores an object
2694	   * to show how good of a match it is to the
2695	   * provided query.
2696	   *
2697	   * @return {function}
2698	   */
2699
2700
2701	  getScoreFunction(query) {
2702	    return this.sifter.getScoreFunction(query, this.getSearchOptions());
2703	  }
2704	  /**
2705	   * Returns search options for sifter (the system
2706	   * for scoring and sorting results).
2707	   *
2708	   * @see https://github.com/orchidjs/sifter.js
2709	   * @return {object}
2710	   */
2711
2712
2713	  getSearchOptions() {
2714	    var settings = this.settings;
2715	    var sort = settings.sortField;
2716
2717	    if (typeof settings.sortField === 'string') {
2718	      sort = [{
2719	        field: settings.sortField
2720	      }];
2721	    }
2722
2723	    return {
2724	      fields: settings.searchField,
2725	      conjunction: settings.searchConjunction,
2726	      sort: sort,
2727	      nesting: settings.nesting
2728	    };
2729	  }
2730	  /**
2731	   * Searches through available options and returns
2732	   * a sorted array of matches.
2733	   *
2734	   */
2735
2736
2737	  search(query) {
2738	    var i, result, calculateScore;
2739	    var self = this;
2740	    var options = this.getSearchOptions(); // validate user-provided result scoring function
2741
2742	    if (self.settings.score) {
2743	      calculateScore = self.settings.score.call(self, query);
2744
2745	      if (typeof calculateScore !== 'function') {
2746	        throw new Error('Tom Select "score" setting must be a function that returns a function');
2747	      }
2748	    } // perform search
2749
2750
2751	    if (query !== self.lastQuery) {
2752	      self.lastQuery = query;
2753	      result = self.sifter.search(query, Object.assign(options, {
2754	        score: calculateScore
2755	      }));
2756	      self.currentResults = result;
2757	    } else {
2758	      result = Object.assign({}, self.currentResults);
2759	    } // filter out selected items
2760
2761
2762	    if (self.settings.hideSelected) {
2763	      for (i = result.items.length - 1; i >= 0; i--) {
2764	        let hashed = hash_key(result.items[i].id);
2765
2766	        if (hashed && self.items.indexOf(hashed) !== -1) {
2767	          result.items.splice(i, 1);
2768	        }
2769	      }
2770	    }
2771
2772	    return result;
2773	  }
2774	  /**
2775	   * Refreshes the list of available options shown
2776	   * in the autocomplete dropdown menu.
2777	   *
2778	   */
2779
2780
2781	  refreshOptions(triggerDropdown = true) {
2782	    var i, j, k, n, optgroup, optgroups, html, has_create_option, active_value, active_group;
2783	    var create;
2784	    const groups = {};
2785	    const groups_order = [];
2786	    var self = this;
2787	    var query = self.inputValue();
2788	    var results = self.search(query);
2789	    var active_option = self.activeOption;
2790	    var show_dropdown = self.settings.shouldOpen || false;
2791	    var dropdown_content = self.dropdown_content;
2792
2793	    if (active_option) {
2794	      active_value = active_option.dataset.value;
2795	      active_group = active_option.closest('[data-group]');
2796	    } // build markup
2797
2798
2799	    n = results.items.length;
2800
2801	    if (typeof self.settings.maxOptions === 'number') {
2802	      n = Math.min(n, self.settings.maxOptions);
2803	    }
2804
2805	    if (n > 0) {
2806	      show_dropdown = true;
2807	    } // render and group available options individually
2808
2809
2810	    for (i = 0; i < n; i++) {
2811	      // get option dom element
2812	      let opt_value = results.items[i].id;
2813	      let option = self.options[opt_value];
2814	      let option_el = self.getOption(opt_value, true); // toggle 'selected' class
2815
2816	      if (!self.settings.hideSelected) {
2817	        option_el.classList.toggle('selected', self.items.includes(opt_value));
2818	      }
2819
2820	      optgroup = option[self.settings.optgroupField] || '';
2821	      optgroups = Array.isArray(optgroup) ? optgroup : [optgroup];
2822
2823	      for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
2824	        optgroup = optgroups[j];
2825
2826	        if (!self.optgroups.hasOwnProperty(optgroup)) {
2827	          optgroup = '';
2828	        }
2829
2830	        if (!groups.hasOwnProperty(optgroup)) {
2831	          groups[optgroup] = document.createDocumentFragment();
2832	          groups_order.push(optgroup);
2833	        } // nodes can only have one parent, so if the option is in mutple groups, we need a clone
2834
2835
2836	        if (j > 0) {
2837	          option_el = option_el.cloneNode(true);
2838	          setAttr(option_el, {
2839	            id: option.$id + '-clone-' + j,
2840	            'aria-selected': null
2841	          });
2842	          option_el.classList.add('ts-cloned');
2843	          removeClasses(option_el, 'active');
2844	        } // make sure we keep the activeOption in the same group
2845
2846
2847	        if (active_value == opt_value && active_group && active_group.dataset.group === optgroup) {
2848	          active_option = option_el;
2849	        }
2850
2851	        groups[optgroup].appendChild(option_el);
2852	      }
2853	    } // sort optgroups
2854
2855
2856	    if (this.settings.lockOptgroupOrder) {
2857	      groups_order.sort((a, b) => {
2858	        var a_order = self.optgroups[a] && self.optgroups[a].$order || 0;
2859	        var b_order = self.optgroups[b] && self.optgroups[b].$order || 0;
2860	        return a_order - b_order;
2861	      });
2862	    } // render optgroup headers & join groups
2863
2864
2865	    html = document.createDocumentFragment();
2866	    iterate(groups_order, optgroup => {
2867	      if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].children.length) {
2868	        let group_options = document.createDocumentFragment();
2869	        let header = self.render('optgroup_header', self.optgroups[optgroup]);
2870	        append(group_options, header);
2871	        append(group_options, groups[optgroup]);
2872	        let group_html = self.render('optgroup', {
2873	          group: self.optgroups[optgroup],
2874	          options: group_options
2875	        });
2876	        append(html, group_html);
2877	      } else {
2878	        append(html, groups[optgroup]);
2879	      }
2880	    });
2881	    dropdown_content.innerHTML = '';
2882	    append(dropdown_content, html); // highlight matching terms inline
2883
2884	    if (self.settings.highlight) {
2885	      removeHighlight(dropdown_content);
2886
2887	      if (results.query.length && results.tokens.length) {
2888	        iterate(results.tokens, tok => {
2889	          highlight(dropdown_content, tok.regex);
2890	        });
2891	      }
2892	    } // helper method for adding templates to dropdown
2893
2894
2895	    var add_template = template => {
2896	      let content = self.render(template, {
2897	        input: query
2898	      });
2899
2900	      if (content) {
2901	        show_dropdown = true;
2902	        dropdown_content.insertBefore(content, dropdown_content.firstChild);
2903	      }
2904
2905	      return content;
2906	    }; // add loading message
2907
2908
2909	    if (self.loading) {
2910	      add_template('loading'); // invalid query
2911	    } else if (!self.settings.shouldLoad.call(self, query)) {
2912	      add_template('not_loading'); // add no_results message
2913	    } else if (results.items.length === 0) {
2914	      add_template('no_results');
2915	    } // add create option
2916
2917
2918	    has_create_option = self.canCreate(query);
2919
2920	    if (has_create_option) {
2921	      create = add_template('option_create');
2922	    } // activate
2923
2924
2925	    self.hasOptions = results.items.length > 0 || has_create_option;
2926
2927	    if (show_dropdown) {
2928	      if (results.items.length > 0) {
2929	        if (!dropdown_content.contains(active_option) && self.settings.mode === 'single' && self.items.length) {
2930	          active_option = self.getOption(self.items[0]);
2931	        }
2932
2933	        if (!dropdown_content.contains(active_option)) {
2934	          let active_index = 0;
2935
2936	          if (create && !self.settings.addPrecedence) {
2937	            active_index = 1;
2938	          }
2939
2940	          active_option = self.selectable()[active_index];
2941	        }
2942	      } else if (create) {
2943	        active_option = create;
2944	      }
2945
2946	      if (triggerDropdown && !self.isOpen) {
2947	        self.open();
2948	        self.scrollToOption(active_option, 'auto');
2949	      }
2950
2951	      self.setActiveOption(active_option);
2952	    } else {
2953	      self.clearActiveOption();
2954
2955	      if (triggerDropdown && self.isOpen) {
2956	        self.close(false); // if create_option=null, we want the dropdown to close but not reset the textbox value
2957	      }
2958	    }
2959	  }
2960	  /**
2961	   * Return list of selectable options
2962	   *
2963	   */
2964
2965
2966	  selectable() {
2967	    return this.dropdown_content.querySelectorAll('[data-selectable]');
2968	  }
2969	  /**
2970	   * Adds an available option. If it already exists,
2971	   * nothing will happen. Note: this does not refresh
2972	   * the options list dropdown (use `refreshOptions`
2973	   * for that).
2974	   *
2975	   * Usage:
2976	   *
2977	   *   this.addOption(data)
2978	   *
2979	   */
2980
2981
2982	  addOption(data, user_created = false) {
2983	    const self = this; // @deprecated 1.7.7
2984	    // use addOptions( array, user_created ) for adding multiple options
2985
2986	    if (Array.isArray(data)) {
2987	      self.addOptions(data, user_created);
2988	      return false;
2989	    }
2990
2991	    const key = hash_key(data[self.settings.valueField]);
2992
2993	    if (key === null || self.options.hasOwnProperty(key)) {
2994	      return false;
2995	    }
2996
2997	    data.$order = data.$order || ++self.order;
2998	    data.$id = self.inputId + '-opt-' + data.$order;
2999	    self.options[key] = data;
3000	    self.lastQuery = null;
3001
3002	    if (user_created) {
3003	      self.userOptions[key] = user_created;
3004	      self.trigger('option_add', key, data);
3005	    }
3006
3007	    return key;
3008	  }
3009	  /**
3010	   * Add multiple options
3011	   *
3012	   */
3013
3014
3015	  addOptions(data, user_created = false) {
3016	    iterate(data, dat => {
3017	      this.addOption(dat, user_created);
3018	    });
3019	  }
3020	  /**
3021	   * @deprecated 1.7.7
3022	   */
3023
3024
3025	  registerOption(data) {
3026	    return this.addOption(data);
3027	  }
3028	  /**
3029	   * Registers an option group to the pool of option groups.
3030	   *
3031	   * @return {boolean|string}
3032	   */
3033
3034
3035	  registerOptionGroup(data) {
3036	    var key = hash_key(data[this.settings.optgroupValueField]);
3037	    if (key === null) return false;
3038	    data.$order = data.$order || ++this.order;
3039	    this.optgroups[key] = data;
3040	    return key;
3041	  }
3042	  /**
3043	   * Registers a new optgroup for options
3044	   * to be bucketed into.
3045	   *
3046	   */
3047
3048
3049	  addOptionGroup(id, data) {
3050	    var hashed_id;
3051	    data[this.settings.optgroupValueField] = id;
3052
3053	    if (hashed_id = this.registerOptionGroup(data)) {
3054	      this.trigger('optgroup_add', hashed_id, data);
3055	    }
3056	  }
3057	  /**
3058	   * Removes an existing option group.
3059	   *
3060	   */
3061
3062
3063	  removeOptionGroup(id) {
3064	    if (this.optgroups.hasOwnProperty(id)) {
3065	      delete this.optgroups[id];
3066	      this.clearCache();
3067	      this.trigger('optgroup_remove', id);
3068	    }
3069	  }
3070	  /**
3071	   * Clears all existing option groups.
3072	   */
3073
3074
3075	  clearOptionGroups() {
3076	    this.optgroups = {};
3077	    this.clearCache();
3078	    this.trigger('optgroup_clear');
3079	  }
3080	  /**
3081	   * Updates an option available for selection. If
3082	   * it is visible in the selected items or options
3083	   * dropdown, it will be re-rendered automatically.
3084	   *
3085	   */
3086
3087
3088	  updateOption(value, data) {
3089	    const self = this;
3090	    var item_new;
3091	    var index_item;
3092	    const value_old = hash_key(value);
3093	    const value_new = hash_key(data[self.settings.valueField]); // sanity checks
3094
3095	    if (value_old === null) return;
3096	    if (!self.options.hasOwnProperty(value_old)) return;
3097	    if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
3098	    const option = self.getOption(value_old);
3099	    const item = self.getItem(value_old);
3100	    data.$order = data.$order || self.options[value_old].$order;
3101	    delete self.options[value_old]; // invalidate render cache
3102	    // don't remove existing node yet, we'll remove it after replacing it
3103
3104	    self.uncacheValue(value_new);
3105	    self.options[value_new] = data; // update the option if it's in the dropdown
3106
3107	    if (option) {
3108	      if (self.dropdown_content.contains(option)) {
3109	        const option_new = self._render('option', data);
3110
3111	        replaceNode(option, option_new);
3112
3113	        if (self.activeOption === option) {
3114	          self.setActiveOption(option_new);
3115	        }
3116	      }
3117
3118	      option.remove();
3119	    } // update the item if we have one
3120
3121
3122	    if (item) {
3123	      index_item = self.items.indexOf(value_old);
3124
3125	      if (index_item !== -1) {
3126	        self.items.splice(index_item, 1, value_new);
3127	      }
3128
3129	      item_new = self._render('item', data);
3130	      if (item.classList.contains('active')) addClasses(item_new, 'active');
3131	      replaceNode(item, item_new);
3132	    } // invalidate last query because we might have updated the sortField
3133
3134
3135	    self.lastQuery = null;
3136	  }
3137	  /**
3138	   * Removes a single option.
3139	   *
3140	   */
3141
3142
3143	  removeOption(value, silent) {
3144	    const self = this;
3145	    value = get_hash(value);
3146	    self.uncacheValue(value);
3147	    delete self.userOptions[value];
3148	    delete self.options[value];
3149	    self.lastQuery = null;
3150	    self.trigger('option_remove', value);
3151	    self.removeItem(value, silent);
3152	  }
3153	  /**
3154	   * Clears all options.
3155	   */
3156
3157
3158	  clearOptions() {
3159	    this.loadedSearches = {};
3160	    this.userOptions = {};
3161	    this.clearCache();
3162	    var selected = {};
3163	    iterate(this.options, (option, key) => {
3164	      if (this.items.indexOf(key) >= 0) {
3165	        selected[key] = this.options[key];
3166	      }
3167	    });
3168	    this.options = this.sifter.items = selected;
3169	    this.lastQuery = null;
3170	    this.trigger('option_clear');
3171	  }
3172	  /**
3173	   * Returns the dom element of the option
3174	   * matching the given value.
3175	   *
3176	   */
3177
3178
3179	  getOption(value, create = false) {
3180	    const hashed = hash_key(value);
3181
3182	    if (hashed !== null && this.options.hasOwnProperty(hashed)) {
3183	      const option = this.options[hashed];
3184
3185	      if (option.$div) {
3186	        return option.$div;
3187	      }
3188
3189	      if (create) {
3190	        return this._render('option', option);
3191	      }
3192	    }
3193
3194	    return null;
3195	  }
3196	  /**
3197	   * Returns the dom element of the next or previous dom element of the same type
3198	   * Note: adjacent options may not be adjacent DOM elements (optgroups)
3199	   *
3200	   */
3201
3202
3203	  getAdjacent(option, direction, type = 'option') {
3204	    var self = this,
3205	        all;
3206
3207	    if (!option) {
3208	      return null;
3209	    }
3210
3211	    if (type == 'item') {
3212	      all = self.controlChildren();
3213	    } else {
3214	      all = self.dropdown_content.querySelectorAll('[data-selectable]');
3215	    }
3216
3217	    for (let i = 0; i < all.length; i++) {
3218	      if (all[i] != option) {
3219	        continue;
3220	      }
3221
3222	      if (direction > 0) {
3223	        return all[i + 1];
3224	      }
3225
3226	      return all[i - 1];
3227	    }
3228
3229	    return null;
3230	  }
3231	  /**
3232	   * Returns the dom element of the item
3233	   * matching the given value.
3234	   *
3235	   */
3236
3237
3238	  getItem(item) {
3239	    if (typeof item == 'object') {
3240	      return item;
3241	    }
3242
3243	    var value = hash_key(item);
3244	    return value !== null ? this.control.querySelector(`[data-value="${addSlashes(value)}"]`) : null;
3245	  }
3246	  /**
3247	   * "Selects" multiple items at once. Adds them to the list
3248	   * at the current caret position.
3249	   *
3250	   */
3251
3252
3253	  addItems(values, silent) {
3254	    var self = this;
3255	    var items = Array.isArray(values) ? values : [values];
3256	    items = items.filter(x => self.items.indexOf(x) === -1);
3257
3258	    for (let i = 0, n = items.length; i < n; i++) {
3259	      self.isPending = i < n - 1;
3260	      self.addItem(items[i], silent);
3261	    }
3262	  }
3263	  /**
3264	   * "Selects" an item. Adds it to the list
3265	   * at the current caret position.
3266	   *
3267	   */
3268
3269
3270	  addItem(value, silent) {
3271	    var events = silent ? [] : ['change', 'dropdown_close'];
3272	    debounce_events(this, events, () => {
3273	      var item, wasFull;
3274	      const self = this;
3275	      const inputMode = self.settings.mode;
3276	      const hashed = hash_key(value);
3277
3278	      if (hashed && self.items.indexOf(hashed) !== -1) {
3279	        if (inputMode === 'single') {
3280	          self.close();
3281	        }
3282
3283	        if (inputMode === 'single' || !self.settings.duplicates) {
3284	          return;
3285	        }
3286	      }
3287
3288	      if (hashed === null || !self.options.hasOwnProperty(hashed)) return;
3289	      if (inputMode === 'single') self.clear(silent);
3290	      if (inputMode === 'multi' && self.isFull()) return;
3291	      item = self._render('item', self.options[hashed]);
3292
3293	      if (self.control.contains(item)) {
3294	        // duplicates
3295	        item = item.cloneNode(true);
3296	      }
3297
3298	      wasFull = self.isFull();
3299	      self.items.splice(self.caretPos, 0, hashed);
3300	      self.insertAtCaret(item);
3301
3302	      if (self.isSetup) {
3303	        // update menu / remove the option (if this is not one item being added as part of series)
3304	        if (!self.isPending && self.settings.hideSelected) {
3305	          let option = self.getOption(hashed);
3306	          let next = self.getAdjacent(option, 1);
3307
3308	          if (next) {
3309	            self.setActiveOption(next);
3310	          }
3311	        } // refreshOptions after setActiveOption(),
3312	        // otherwise setActiveOption() will be called by refreshOptions() with the wrong value
3313
3314
3315	        if (!self.isPending && !self.settings.closeAfterSelect) {
3316	          self.refreshOptions(self.isFocused && inputMode !== 'single');
3317	        } // hide the menu if the maximum number of items have been selected or no options are left
3318
3319
3320	        if (self.settings.closeAfterSelect != false && self.isFull()) {
3321	          self.close();
3322	        } else if (!self.isPending) {
3323	          self.positionDropdown();
3324	        }
3325
3326	        self.trigger('item_add', hashed, item);
3327
3328	        if (!self.isPending) {
3329	          self.updateOriginalInput({
3330	            silent: silent
3331	          });
3332	        }
3333	      }
3334
3335	      if (!self.isPending || !wasFull && self.isFull()) {
3336	        self.inputState();
3337	        self.refreshState();
3338	      }
3339	    });
3340	  }
3341	  /**
3342	   * Removes the selected item matching
3343	   * the provided value.
3344	   *
3345	   */
3346
3347
3348	  removeItem(item = null, silent) {
3349	    const self = this;
3350	    item = self.getItem(item);
3351	    if (!item) return;
3352	    var i, idx;
3353	    const value = item.dataset.value;
3354	    i = nodeIndex(item);
3355	    item.remove();
3356
3357	    if (item.classList.contains('active')) {
3358	      idx = self.activeItems.indexOf(item);
3359	      self.activeItems.splice(idx, 1);
3360	      removeClasses(item, 'active');
3361	    }
3362
3363	    self.items.splice(i, 1);
3364	    self.lastQuery = null;
3365
3366	    if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
3367	      self.removeOption(value, silent);
3368	    }
3369
3370	    if (i < self.caretPos) {
3371	      self.setCaret(self.caretPos - 1);
3372	    }
3373
3374	    self.updateOriginalInput({
3375	      silent: silent
3376	    });
3377	    self.refreshState();
3378	    self.positionDropdown();
3379	    self.trigger('item_remove', value, item);
3380	  }
3381	  /**
3382	   * Invokes the `create` method provided in the
3383	   * TomSelect options that should provide the data
3384	   * for the new item, given the user input.
3385	   *
3386	   * Once this completes, it will be added
3387	   * to the item list.
3388	   *
3389	   */
3390
3391
3392	  createItem(input = null, triggerDropdown = true, callback = () => {}) {
3393	    var self = this;
3394	    var caret = self.caretPos;
3395	    var output;
3396	    input = input || self.inputValue();
3397
3398	    if (!self.canCreate(input)) {
3399	      callback();
3400	      return false;
3401	    }
3402
3403	    self.lock();
3404	    var created = false;
3405
3406	    var create = data => {
3407	      self.unlock();
3408	      if (!data || typeof data !== 'object') return callback();
3409	      var value = hash_key(data[self.settings.valueField]);
3410
3411	      if (typeof value !== 'string') {
3412	        return callback();
3413	      }
3414
3415	      self.setTextboxValue();
3416	      self.addOption(data, true);
3417	      self.setCaret(caret);
3418	      self.addItem(value);
3419	      callback(data);
3420	      created = true;
3421	    };
3422
3423	    if (typeof self.settings.create === 'function') {
3424	      output = self.settings.create.call(this, input, create);
3425	    } else {
3426	      output = {
3427	        [self.settings.labelField]: input,
3428	        [self.settings.valueField]: input
3429	      };
3430	    }
3431
3432	    if (!created) {
3433	      create(output);
3434	    }
3435
3436	    return true;
3437	  }
3438	  /**
3439	   * Re-renders the selected item lists.
3440	   */
3441
3442
3443	  refreshItems() {
3444	    var self = this;
3445	    self.lastQuery = null;
3446
3447	    if (self.isSetup) {
3448	      self.addItems(self.items);
3449	    }
3450
3451	    self.updateOriginalInput();
3452	    self.refreshState();
3453	  }
3454	  /**
3455	   * Updates all state-dependent attributes
3456	   * and CSS classes.
3457	   */
3458
3459
3460	  refreshState() {
3461	    const self = this;
3462	    self.refreshValidityState();
3463	    const isFull = self.isFull();
3464	    const isLocked = self.isLocked;
3465	    self.wrapper.classList.toggle('rtl', self.rtl);
3466	    const wrap_classList = self.wrapper.classList;
3467	    wrap_classList.toggle('focus', self.isFocused);
3468	    wrap_classList.toggle('disabled', self.isDisabled);
3469	    wrap_classList.toggle('required', self.isRequired);
3470	    wrap_classList.toggle('invalid', !self.isValid);
3471	    wrap_classList.toggle('locked', isLocked);
3472	    wrap_classList.toggle('full', isFull);
3473	    wrap_classList.toggle('input-active', self.isFocused && !self.isInputHidden);
3474	    wrap_classList.toggle('dropdown-active', self.isOpen);
3475	    wrap_classList.toggle('has-options', isEmptyObject(self.options));
3476	    wrap_classList.toggle('has-items', self.items.length > 0);
3477	  }
3478	  /**
3479	   * Update the `required` attribute of both input and control input.
3480	   *
3481	   * The `required` property needs to be activated on the control input
3482	   * for the error to be displayed at the right place. `required` also
3483	   * needs to be temporarily deactivated on the input since the input is
3484	   * hidden and can't show errors.
3485	   */
3486
3487
3488	  refreshValidityState() {
3489	    var self = this;
3490
3491	    if (!self.input.checkValidity) {
3492	      return;
3493	    }
3494
3495	    self.isValid = self.input.checkValidity();
3496	    self.isInvalid = !self.isValid;
3497	  }
3498	  /**
3499	   * Determines whether or not more items can be added
3500	   * to the control without exceeding the user-defined maximum.
3501	   *
3502	   * @returns {boolean}
3503	   */
3504
3505
3506	  isFull() {
3507	    return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
3508	  }
3509	  /**
3510	   * Refreshes the original <select> or <input>
3511	   * element to reflect the current state.
3512	   *
3513	   */
3514
3515
3516	  updateOriginalInput(opts = {}) {
3517	    const self = this;
3518	    var option, label;
3519	    const empty_option = self.input.querySelector('option[value=""]');
3520
3521	    if (self.is_select_tag) {
3522	      const selected = [];
3523
3524	      function AddSelected(option_el, value, label) {
3525	        if (!option_el) {
3526	          option_el = getDom('<option value="' + escape_html(value) + '">' + escape_html(label) + '</option>');
3527	        } // don't move empty option from top of list
3528	        // fixes bug in firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1725293				
3529
3530
3531	        if (option_el != empty_option) {
3532	          self.input.append(option_el);
3533	        }
3534
3535	        selected.push(option_el);
3536	        option_el.selected = true;
3537	        return option_el;
3538	      } // unselect all selected options
3539
3540
3541	      self.input.querySelectorAll('option:checked').forEach(option_el => {
3542	        option_el.selected = false;
3543	      }); // nothing selected?
3544
3545	      if (self.items.length == 0 && self.settings.mode == 'single') {
3546	        AddSelected(empty_option, "", ""); // order selected <option> tags for values in self.items
3547	      } else {
3548	        self.items.forEach(value => {
3549	          option = self.options[value];
3550	          label = option[self.settings.labelField] || '';
3551
3552	          if (selected.includes(option.$option)) {
3553	            const reuse_opt = self.input.querySelector(`option[value="${addSlashes(value)}"]:not(:checked)`);
3554	            AddSelected(reuse_opt, value, label);
3555	          } else {
3556	            option.$option = AddSelected(option.$option, value, label);
3557	          }
3558	        });
3559	      }
3560	    } else {
3561	      self.input.value = self.getValue();
3562	    }
3563
3564	    if (self.isSetup) {
3565	      if (!opts.silent) {
3566	        self.trigger('change', self.getValue());
3567	      }
3568	    }
3569	  }
3570	  /**
3571	   * Shows the autocomplete dropdown containing
3572	   * the available options.
3573	   */
3574
3575
3576	  open() {
3577	    var self = this;
3578	    if (self.isLocked || self.isOpen || self.settings.mode === 'multi' && self.isFull()) return;
3579	    self.isOpen = true;
3580	    setAttr(self.focus_node, {
3581	      'aria-expanded': 'true'
3582	    });
3583	    self.refreshState();
3584	    applyCSS(self.dropdown, {
3585	      visibility: 'hidden',
3586	      display: 'block'
3587	    });
3588	    self.positionDropdown();
3589	    applyCSS(self.dropdown, {
3590	      visibility: 'visible',
3591	      display: 'block'
3592	    });
3593	    self.focus();
3594	    self.trigger('dropdown_open', self.dropdown);
3595	  }
3596	  /**
3597	   * Closes the autocomplete dropdown menu.
3598	   */
3599
3600
3601	  close(setTextboxValue = true) {
3602	    var self = this;
3603	    var trigger = self.isOpen;
3604
3605	    if (setTextboxValue) {
3606	      // before blur() to prevent form onchange event
3607	      self.setTextboxValue();
3608
3609	      if (self.settings.mode === 'single' && self.items.length) {
3610	        self.hideInput();
3611	      }
3612	    }
3613
3614	    self.isOpen = false;
3615	    setAttr(self.focus_node, {
3616	      'aria-expanded': 'false'
3617	    });
3618	    applyCSS(self.dropdown, {
3619	      display: 'none'
3620	    });
3621
3622	    if (self.settings.hideSelected) {
3623	      self.clearActiveOption();
3624	    }
3625
3626	    self.refreshState();
3627	    if (trigger) self.trigger('dropdown_close', self.dropdown);
3628	  }
3629	  /**
3630	   * Calculates and applies the appropriate
3631	   * position of the dropdown if dropdownParent = 'body'.
3632	   * Otherwise, position is determined by css
3633	   */
3634
3635
3636	  positionDropdown() {
3637	    if (this.settings.dropdownParent !== 'body') {
3638	      return;
3639	    }
3640
3641	    var context = this.control;
3642	    var rect = context.getBoundingClientRect();
3643	    var top = context.offsetHeight + rect.top + window.scrollY;
3644	    var left = rect.left + window.scrollX;
3645	    applyCSS(this.dropdown, {
3646	      width: rect.width + 'px',
3647	      top: top + 'px',
3648	      left: left + 'px'
3649	    });
3650	  }
3651	  /**
3652	   * Resets / clears all selected items
3653	   * from the control.
3654	   *
3655	   */
3656
3657
3658	  clear(silent) {
3659	    var self = this;
3660	    if (!self.items.length) return;
3661	    var items = self.controlChildren();
3662	    iterate(items, item => {
3663	      self.removeItem(item, true);
3664	    });
3665	    self.showInput();
3666	    if (!silent) self.updateOriginalInput();
3667	    self.trigger('clear');
3668	  }
3669	  /**
3670	   * A helper method for inserting an element
3671	   * at the current caret position.
3672	   *
3673	   */
3674
3675
3676	  insertAtCaret(el) {
3677	    const self = this;
3678	    const caret = self.caretPos;
3679	    const target = self.control;
3680	    target.insertBefore(el, target.children[caret]);
3681	    self.setCaret(caret + 1);
3682	  }
3683	  /**
3684	   * Removes the current selected item(s).
3685	   *
3686	   */
3687
3688
3689	  deleteSelection(e) {
3690	    var direction, selection, caret, tail;
3691	    var self = this;
3692	    direction = e && e.keyCode === KEY_BACKSPACE ? -1 : 1;
3693	    selection = getSelection(self.control_input); // determine items that will be removed
3694
3695	    const rm_items = [];
3696
3697	    if (self.activeItems.length) {
3698	      tail = getTail(self.activeItems, direction);
3699	      caret = nodeIndex(tail);
3700
3701	      if (direction > 0) {
3702	        caret++;
3703	      }
3704
3705	      iterate(self.activeItems, item => rm_items.push(item));
3706	    } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
3707	      const items = self.controlChildren();
3708
3709	      if (direction < 0 && selection.start === 0 && selection.length === 0) {
3710	        rm_items.push(items[self.caretPos - 1]);
3711	      } else if (direction > 0 && selection.start === self.inputValue().length) {
3712	        rm_items.push(items[self.caretPos]);
3713	      }
3714	    }
3715
3716	    const values = rm_items.map(item => item.dataset.value); // allow the callback to abort
3717
3718	    if (!values.length || typeof self.settings.onDelete === 'function' && self.settings.onDelete.call(self, values, e) === false) {
3719	      return false;
3720	    }
3721
3722	    preventDefault(e, true); // perform removal
3723
3724	    if (typeof caret !== 'undefined') {
3725	      self.setCaret(caret);
3726	    }
3727
3728	    while (rm_items.length) {
3729	      self.removeItem(rm_items.pop());
3730	    }
3731
3732	    self.showInput();
3733	    self.positionDropdown();
3734	    self.refreshOptions(false);
3735	    return true;
3736	  }
3737	  /**
3738	   * Selects the previous / next item (depending on the `direction` argument).
3739	   *
3740	   * > 0 - right
3741	   * < 0 - left
3742	   *
3743	   */
3744
3745
3746	  advanceSelection(direction, e) {
3747	    var last_active,
3748	        adjacent,
3749	        self = this;
3750	    if (self.rtl) direction *= -1;
3751	    if (self.inputValue().length) return; // add or remove to active items
3752
3753	    if (isKeyDown(KEY_SHORTCUT, e) || isKeyDown('shiftKey', e)) {
3754	      last_active = self.getLastActive(direction);
3755
3756	      if (last_active) {
3757	        if (!last_active.classList.contains('active')) {
3758	          adjacent = last_active;
3759	        } else {
3760	          adjacent = self.getAdjacent(last_active, direction, 'item');
3761	        } // if no active item, get items adjacent to the control input
3762
3763	      } else if (direction > 0) {
3764	        adjacent = self.control_input.nextElementSibling;
3765	      } else {
3766	        adjacent = self.control_input.previousElementSibling;
3767	      }
3768
3769	      if (adjacent) {
3770	        if (adjacent.classList.contains('active')) {
3771	          self.removeActiveItem(last_active);
3772	        }
3773
3774	        self.setActiveItemClass(adjacent); // mark as last_active !! after removeActiveItem() on last_active
3775	      } // move caret to the left or right
3776
3777	    } else {
3778	      self.moveCaret(direction);
3779	    }
3780	  }
3781
3782	  moveCaret(direction) {}
3783	  /**
3784	   * Get the last active item
3785	   *
3786	   */
3787
3788
3789	  getLastActive(direction) {
3790	    let last_active = this.control.querySelector('.last-active');
3791
3792	    if (last_active) {
3793	      return last_active;
3794	    }
3795
3796	    var result = this.control.querySelectorAll('.active');
3797
3798	    if (result) {
3799	      return getTail(result, direction);
3800	    }
3801	  }
3802	  /**
3803	   * Moves the caret to the specified index.
3804	   *
3805	   * The input must be moved by leaving it in place and moving the
3806	   * siblings, due to the fact that focus cannot be restored once lost
3807	   * on mobile webkit devices
3808	   *
3809	   */
3810
3811
3812	  setCaret(new_pos) {
3813	    this.caretPos = this.items.length;
3814	  }
3815	  /**
3816	   * Return list of item dom elements
3817	   *
3818	   */
3819
3820
3821	  controlChildren() {
3822	    return Array.from(this.control.querySelectorAll('[data-ts-item]'));
3823	  }
3824	  /**
3825	   * Disables user input on the control. Used while
3826	   * items are being asynchronously created.
3827	   */
3828
3829
3830	  lock() {
3831	    this.isLocked = true;
3832	    this.refreshState();
3833	  }
3834	  /**
3835	   * Re-enables user input on the control.
3836	   */
3837
3838
3839	  unlock() {
3840	    this.isLocked = false;
3841	    this.refreshState();
3842	  }
3843	  /**
3844	   * Disables user input on the control completely.
3845	   * While disabled, it cannot receive focus.
3846	   */
3847
3848
3849	  disable() {
3850	    var self = this;
3851	    self.input.disabled = true;
3852	    self.control_input.disabled = true;
3853	    self.focus_node.tabIndex = -1;
3854	    self.isDisabled = true;
3855	    this.close();
3856	    self.lock();
3857	  }
3858	  /**
3859	   * Enables the control so that it can respond
3860	   * to focus and user input.
3861	   */
3862
3863
3864	  enable() {
3865	    var self = this;
3866	    self.input.disabled = false;
3867	    self.control_input.disabled = false;
3868	    self.focus_node.tabIndex = self.tabIndex;
3869	    self.isDisabled = false;
3870	    self.unlock();
3871	  }
3872	  /**
3873	   * Completely destroys the control and
3874	   * unbinds all event listeners so that it can
3875	   * be garbage collected.
3876	   */
3877
3878
3879	  destroy() {
3880	    var self = this;
3881	    var revertSettings = self.revertSettings;
3882	    self.trigger('destroy');
3883	    self.off();
3884	    self.wrapper.remove();
3885	    self.dropdown.remove();
3886	    self.input.innerHTML = revertSettings.innerHTML;
3887	    self.input.tabIndex = revertSettings.tabIndex;
3888	    removeClasses(self.input, 'tomselected', 'ts-hidden-accessible');
3889
3890	    self._destroy();
3891
3892	    delete self.input.tomselect;
3893	  }
3894	  /**
3895	   * A helper method for rendering "item" and
3896	   * "option" templates, given the data.
3897	   *
3898	   */
3899
3900
3901	  render(templateName, data) {
3902	    if (typeof this.settings.render[templateName] !== 'function') {
3903	      return null;
3904	    }
3905
3906	    return this._render(templateName, data);
3907	  }
3908	  /**
3909	   * _render() can be called directly when we know we don't want to hit the cache
3910	   * return type could be null for some templates, we need https://github.com/microsoft/TypeScript/issues/33014
3911	   */
3912
3913
3914	  _render(templateName, data) {
3915	    var value = '',
3916	        id,
3917	        html;
3918	    const self = this;
3919
3920	    if (templateName === 'option' || templateName == 'item') {
3921	      value = get_hash(data[self.settings.valueField]);
3922	    } // render markup
3923
3924
3925	    html = self.settings.render[templateName].call(this, data, escape_html);
3926
3927	    if (html == null) {
3928	      return html;
3929	    }
3930
3931	    html = getDom(html); // add mandatory attributes
3932
3933	    if (templateName === 'option' || templateName === 'option_create') {
3934	      if (data[self.settings.disabledField]) {
3935	        setAttr(html, {
3936	          'aria-disabled': 'true'
3937	        });
3938	      } else {
3939	        setAttr(html, {
3940	          'data-selectable': ''
3941	        });
3942	      }
3943	    } else if (templateName === 'optgroup') {
3944	      id = data.group[self.settings.optgroupValueField];
3945	      setAttr(html, {
3946	        'data-group': id
3947	      });
3948
3949	      if (data.group[self.settings.disabledField]) {
3950	        setAttr(html, {
3951	          'data-disabled': ''
3952	        });
3953	      }
3954	    }
3955
3956	    if (templateName === 'option' || templateName === 'item') {
3957	      setAttr(html, {
3958	        'data-value': value
3959	      }); // make sure we have some classes if a template is overwritten
3960
3961	      if (templateName === 'item') {
3962	        addClasses(html, self.settings.itemClass);
3963	        setAttr(html, {
3964	          'data-ts-item': ''
3965	        });
3966	      } else {
3967	        addClasses(html, self.settings.optionClass);
3968	        setAttr(html, {
3969	          role: 'option',
3970	          id: data.$id
3971	        }); // update cache
3972
3973	        self.options[value].$div = html;
3974	      }
3975	    }
3976
3977	    return html;
3978	  }
3979	  /**
3980	   * Clears the render cache for a template. If
3981	   * no template is given, clears all render
3982	   * caches.
3983	   *
3984	   */
3985
3986
3987	  clearCache() {
3988	    iterate(this.options, (option, value) => {
3989	      if (option.$div) {
3990	        option.$div.remove();
3991	        delete option.$div;
3992	      }
3993	    });
3994	  }
3995	  /**
3996	   * Removes a value from item and option caches
3997	   *
3998	   */
3999
4000
4001	  uncacheValue(value) {
4002	    const option_el = this.getOption(value);
4003	    if (option_el) option_el.remove();
4004	  }
4005	  /**
4006	   * Determines whether or not to display the
4007	   * create item prompt, given a user input.
4008	   *
4009	   */
4010
4011
4012	  canCreate(input) {
4013	    return this.settings.create && input.length > 0 && this.settings.createFilter.call(this, input);
4014	  }
4015	  /**
4016	   * Wraps this.`method` so that `new_fn` can be invoked 'before', 'after', or 'instead' of the original method
4017	   *
4018	   * this.hook('instead','onKeyDown',function( arg1, arg2 ...){
4019	   *
4020	   * });
4021	   */
4022
4023
4024	  hook(when, method, new_fn) {
4025	    var self = this;
4026	    var orig_method = self[method];
4027
4028	    self[method] = function () {
4029	      var result, result_new;
4030
4031	      if (when === 'after') {
4032	        result = orig_method.apply(self, arguments);
4033	      }
4034
4035	      result_new = new_fn.apply(self, arguments);
4036
4037	      if (when === 'instead') {
4038	        return result_new;
4039	      }
4040
4041	      if (when === 'before') {
4042	        result = orig_method.apply(self, arguments);
4043	      }
4044
4045	      return result;
4046	    };
4047	  }
4048
4049	}
4050
4051	/**
4052	 * Plugin: "change_listener" (Tom Select)
4053	 * Copyright (c) contributors
4054	 *
4055	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4056	 * file except in compliance with the License. You may obtain a copy of the License at:
4057	 * http://www.apache.org/licenses/LICENSE-2.0
4058	 *
4059	 * Unless required by applicable law or agreed to in writing, software distributed under
4060	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4061	 * ANY KIND, either express or implied. See the License for the specific language
4062	 * governing permissions and limitations under the License.
4063	 *
4064	 */
4065	function change_listener () {
4066	  addEvent(this.input, 'change', () => {
4067	    this.sync();
4068	  });
4069	}
4070
4071	/**
4072	 * Plugin: "restore_on_backspace" (Tom Select)
4073	 * Copyright (c) contributors
4074	 *
4075	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4076	 * file except in compliance with the License. You may obtain a copy of the License at:
4077	 * http://www.apache.org/licenses/LICENSE-2.0
4078	 *
4079	 * Unless required by applicable law or agreed to in writing, software distributed under
4080	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4081	 * ANY KIND, either express or implied. See the License for the specific language
4082	 * governing permissions and limitations under the License.
4083	 *
4084	 */
4085	function checkbox_options () {
4086	  var self = this;
4087	  var orig_onOptionSelect = self.onOptionSelect;
4088	  self.settings.hideSelected = false; // update the checkbox for an option
4089
4090	  var UpdateCheckbox = function UpdateCheckbox(option) {
4091	    setTimeout(() => {
4092	      var checkbox = option.querySelector('input');
4093
4094	      if (option.classList.contains('selected')) {
4095	        checkbox.checked = true;
4096	      } else {
4097	        checkbox.checked = false;
4098	      }
4099	    }, 1);
4100	  }; // add checkbox to option template
4101
4102
4103	  self.hook('after', 'setupTemplates', () => {
4104	    var orig_render_option = self.settings.render.option;
4105
4106	    self.settings.render.option = (data, escape_html) => {
4107	      var rendered = getDom(orig_render_option.call(self, data, escape_html));
4108	      var checkbox = document.createElement('input');
4109	      checkbox.addEventListener('click', function (evt) {
4110	        preventDefault(evt);
4111	      });
4112	      checkbox.type = 'checkbox';
4113	      const hashed = hash_key(data[self.settings.valueField]);
4114
4115	      if (hashed && self.items.indexOf(hashed) > -1) {
4116	        checkbox.checked = true;
4117	      }
4118
4119	      rendered.prepend(checkbox);
4120	      return rendered;
4121	    };
4122	  }); // uncheck when item removed
4123
4124	  self.on('item_remove', value => {
4125	    var option = self.getOption(value);
4126
4127	    if (option) {
4128	      // if dropdown hasn't been opened yet, the option won't exist
4129	      option.classList.remove('selected'); // selected class won't be removed yet
4130
4131	      UpdateCheckbox(option);
4132	    }
4133	  }); // remove items when selected option is clicked
4134
4135	  self.hook('instead', 'onOptionSelect', (evt, option) => {
4136	    if (option.classList.contains('selected')) {
4137	      option.classList.remove('selected');
4138	      self.removeItem(option.dataset.value);
4139	      self.refreshOptions();
4140	      preventDefault(evt, true);
4141	      return;
4142	    }
4143
4144	    orig_onOptionSelect.call(self, evt, option);
4145	    UpdateCheckbox(option);
4146	  });
4147	}
4148
4149	/**
4150	 * Plugin: "dropdown_header" (Tom Select)
4151	 * Copyright (c) contributors
4152	 *
4153	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4154	 * file except in compliance with the License. You may obtain a copy of the License at:
4155	 * http://www.apache.org/licenses/LICENSE-2.0
4156	 *
4157	 * Unless required by applicable law or agreed to in writing, software distributed under
4158	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4159	 * ANY KIND, either express or implied. See the License for the specific language
4160	 * governing permissions and limitations under the License.
4161	 *
4162	 */
4163	function clear_button (userOptions) {
4164	  const self = this;
4165	  const options = Object.assign({
4166	    className: 'clear-button',
4167	    title: 'Clear All',
4168	    html: data => {
4169	      return `<div class="${data.className}" title="${data.title}">&times;</div>`;
4170	    }
4171	  }, userOptions);
4172	  self.on('initialize', () => {
4173	    var button = getDom(options.html(options));
4174	    button.addEventListener('click', evt => {
4175	      self.clear();
4176
4177	      if (self.settings.mode === 'single' && self.settings.allowEmptyOption) {
4178	        self.addItem('');
4179	      }
4180
4181	      evt.preventDefault();
4182	      evt.stopPropagation();
4183	    });
4184	    self.control.appendChild(button);
4185	  });
4186	}
4187
4188	/**
4189	 * Plugin: "drag_drop" (Tom Select)
4190	 * Copyright (c) contributors
4191	 *
4192	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4193	 * file except in compliance with the License. You may obtain a copy of the License at:
4194	 * http://www.apache.org/licenses/LICENSE-2.0
4195	 *
4196	 * Unless required by applicable law or agreed to in writing, software distributed under
4197	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4198	 * ANY KIND, either express or implied. See the License for the specific language
4199	 * governing permissions and limitations under the License.
4200	 *
4201	 */
4202	function drag_drop () {
4203	  var self = this;
4204	  if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
4205	  if (self.settings.mode !== 'multi') return;
4206	  var orig_lock = self.lock;
4207	  var orig_unlock = self.unlock;
4208	  self.hook('instead', 'lock', () => {
4209	    var sortable = $(self.control).data('sortable');
4210	    if (sortable) sortable.disable();
4211	    return orig_lock.call(self);
4212	  });
4213	  self.hook('instead', 'unlock', () => {
4214	    var sortable = $(self.control).data('sortable');
4215	    if (sortable) sortable.enable();
4216	    return orig_unlock.call(self);
4217	  });
4218	  self.on('initialize', () => {
4219	    var $control = $(self.control).sortable({
4220	      items: '[data-value]',
4221	      forcePlaceholderSize: true,
4222	      disabled: self.isLocked,
4223	      start: (e, ui) => {
4224	        ui.placeholder.css('width', ui.helper.css('width'));
4225	        $control.css({
4226	          overflow: 'visible'
4227	        });
4228	      },
4229	      stop: () => {
4230	        $control.css({
4231	          overflow: 'hidden'
4232	        });
4233	        var values = [];
4234	        $control.children('[data-value]').each(function () {
4235	          if (this.dataset.value) values.push(this.dataset.value);
4236	        });
4237	        self.setValue(values);
4238	      }
4239	    });
4240	  });
4241	}
4242
4243	/**
4244	 * Plugin: "dropdown_header" (Tom Select)
4245	 * Copyright (c) contributors
4246	 *
4247	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4248	 * file except in compliance with the License. You may obtain a copy of the License at:
4249	 * http://www.apache.org/licenses/LICENSE-2.0
4250	 *
4251	 * Unless required by applicable law or agreed to in writing, software distributed under
4252	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4253	 * ANY KIND, either express or implied. See the License for the specific language
4254	 * governing permissions and limitations under the License.
4255	 *
4256	 */
4257	function dropdown_header (userOptions) {
4258	  const self = this;
4259	  const options = Object.assign({
4260	    title: 'Untitled',
4261	    headerClass: 'dropdown-header',
4262	    titleRowClass: 'dropdown-header-title',
4263	    labelClass: 'dropdown-header-label',
4264	    closeClass: 'dropdown-header-close',
4265	    html: data => {
4266	      return '<div class="' + data.headerClass + '">' + '<div class="' + data.titleRowClass + '">' + '<span class="' + data.labelClass + '">' + data.title + '</span>' + '<a class="' + data.closeClass + '">&times;</a>' + '</div>' + '</div>';
4267	    }
4268	  }, userOptions);
4269	  self.on('initialize', () => {
4270	    var header = getDom(options.html(options));
4271	    var close_link = header.querySelector('.' + options.closeClass);
4272
4273	    if (close_link) {
4274	      close_link.addEventListener('click', evt => {
4275	        preventDefault(evt, true);
4276	        self.close();
4277	      });
4278	    }
4279
4280	    self.dropdown.insertBefore(header, self.dropdown.firstChild);
4281	  });
4282	}
4283
4284	/**
4285	 * Plugin: "dropdown_input" (Tom Select)
4286	 * Copyright (c) contributors
4287	 *
4288	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4289	 * file except in compliance with the License. You may obtain a copy of the License at:
4290	 * http://www.apache.org/licenses/LICENSE-2.0
4291	 *
4292	 * Unless required by applicable law or agreed to in writing, software distributed under
4293	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4294	 * ANY KIND, either express or implied. See the License for the specific language
4295	 * governing permissions and limitations under the License.
4296	 *
4297	 */
4298	function caret_position () {
4299	  var self = this;
4300	  /**
4301	   * Moves the caret to the specified index.
4302	   *
4303	   * The input must be moved by leaving it in place and moving the
4304	   * siblings, due to the fact that focus cannot be restored once lost
4305	   * on mobile webkit devices
4306	   *
4307	   */
4308
4309	  self.hook('instead', 'setCaret', new_pos => {
4310	    if (self.settings.mode === 'single' || !self.control.contains(self.control_input)) {
4311	      new_pos = self.items.length;
4312	    } else {
4313	      new_pos = Math.max(0, Math.min(self.items.length, new_pos));
4314
4315	      if (new_pos != self.caretPos && !self.isPending) {
4316	        self.controlChildren().forEach((child, j) => {
4317	          if (j < new_pos) {
4318	            self.control_input.insertAdjacentElement('beforebegin', child);
4319	          } else {
4320	            self.control.appendChild(child);
4321	          }
4322	        });
4323	      }
4324	    }
4325
4326	    self.caretPos = new_pos;
4327	  });
4328	  self.hook('instead', 'moveCaret', direction => {
4329	    if (!self.isFocused) return; // move caret before or after selected items
4330
4331	    const last_active = self.getLastActive(direction);
4332
4333	    if (last_active) {
4334	      const idx = nodeIndex(last_active);
4335	      self.setCaret(direction > 0 ? idx + 1 : idx);
4336	      self.setActiveItem(); // move caret left or right of current position
4337	    } else {
4338	      self.setCaret(self.caretPos + direction);
4339	    }
4340	  });
4341	}
4342
4343	/**
4344	 * Plugin: "dropdown_input" (Tom Select)
4345	 * Copyright (c) contributors
4346	 *
4347	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4348	 * file except in compliance with the License. You may obtain a copy of the License at:
4349	 * http://www.apache.org/licenses/LICENSE-2.0
4350	 *
4351	 * Unless required by applicable law or agreed to in writing, software distributed under
4352	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4353	 * ANY KIND, either express or implied. See the License for the specific language
4354	 * governing permissions and limitations under the License.
4355	 *
4356	 */
4357	function dropdown_input () {
4358	  var self = this;
4359	  self.settings.shouldOpen = true; // make sure the input is shown even if there are no options to display in the dropdown
4360
4361	  self.hook('before', 'setup', () => {
4362	    self.focus_node = self.control;
4363	    addClasses(self.control_input, 'dropdown-input');
4364	    const div = getDom('<div class="dropdown-input-wrap">');
4365	    div.append(self.control_input);
4366	    self.dropdown.insertBefore(div, self.dropdown.firstChild);
4367	  });
4368	  self.on('initialize', () => {
4369	    // set tabIndex on control to -1, otherwise [shift+tab] will put focus right back on control_input
4370	    self.control_input.addEventListener('keydown', evt => {
4371	      //addEvent(self.control_input,'keydown' as const,(evt:KeyboardEvent) =>{
4372	      switch (evt.keyCode) {
4373	        case KEY_ESC:
4374	          if (self.isOpen) {
4375	            preventDefault(evt, true);
4376	            self.close();
4377	          }
4378
4379	          self.clearActiveItems();
4380	          return;
4381
4382	        case KEY_TAB:
4383	          self.focus_node.tabIndex = -1;
4384	          break;
4385	      }
4386
4387	      return self.onKeyDown.call(self, evt);
4388	    });
4389	    self.on('blur', () => {
4390	      self.focus_node.tabIndex = self.isDisabled ? -1 : self.tabIndex;
4391	    }); // give the control_input focus when the dropdown is open
4392
4393	    self.on('dropdown_open', () => {
4394	      self.control_input.focus();
4395	    }); // prevent onBlur from closing when focus is on the control_input
4396
4397	    const orig_onBlur = self.onBlur;
4398	    self.hook('instead', 'onBlur', evt => {
4399	      if (evt && evt.relatedTarget == self.control_input) return;
4400	      return orig_onBlur.call(self);
4401	    });
4402	    addEvent(self.control_input, 'blur', () => self.onBlur()); // return focus to control to allow further keyboard input
4403
4404	    self.hook('before', 'close', () => {
4405	      if (!self.isOpen) return;
4406	      self.focus_node.focus();
4407	    });
4408	  });
4409	}
4410
4411	/**
4412	 * Plugin: "input_autogrow" (Tom Select)
4413	 *
4414	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4415	 * file except in compliance with the License. You may obtain a copy of the License at:
4416	 * http://www.apache.org/licenses/LICENSE-2.0
4417	 *
4418	 * Unless required by applicable law or agreed to in writing, software distributed under
4419	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4420	 * ANY KIND, either express or implied. See the License for the specific language
4421	 * governing permissions and limitations under the License.
4422	 *
4423	 */
4424	function input_autogrow () {
4425	  var self = this;
4426	  self.on('initialize', () => {
4427	    var test_input = document.createElement('span');
4428	    var control = self.control_input;
4429	    test_input.style.cssText = 'position:absolute; top:-99999px; left:-99999px; width:auto; padding:0; white-space:pre; ';
4430	    self.wrapper.appendChild(test_input);
4431	    var transfer_styles = ['letterSpacing', 'fontSize', 'fontFamily', 'fontWeight', 'textTransform'];
4432
4433	    for (const style_name of transfer_styles) {
4434	      // @ts-ignore TS7015 https://stackoverflow.com/a/50506154/697576
4435	      test_input.style[style_name] = control.style[style_name];
4436	    }
4437	    /**
4438	     * Set the control width
4439	     *
4440	     */
4441
4442
4443	    var resize = () => {
4444	      if (self.items.length > 0) {
4445	        test_input.textContent = control.value;
4446	        control.style.width = test_input.clientWidth + 'px';
4447	      } else {
4448	        control.style.width = '';
4449	      }
4450	    };
4451
4452	    resize();
4453	    self.on('update item_add item_remove', resize);
4454	    addEvent(control, 'input', resize);
4455	    addEvent(control, 'keyup', resize);
4456	    addEvent(control, 'blur', resize);
4457	    addEvent(control, 'update', resize);
4458	  });
4459	}
4460
4461	/**
4462	 * Plugin: "input_autogrow" (Tom Select)
4463	 *
4464	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4465	 * file except in compliance with the License. You may obtain a copy of the License at:
4466	 * http://www.apache.org/licenses/LICENSE-2.0
4467	 *
4468	 * Unless required by applicable law or agreed to in writing, software distributed under
4469	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4470	 * ANY KIND, either express or implied. See the License for the specific language
4471	 * governing permissions and limitations under the License.
4472	 *
4473	 */
4474	function no_backspace_delete () {
4475	  var self = this;
4476	  var orig_deleteSelection = self.deleteSelection;
4477	  this.hook('instead', 'deleteSelection', evt => {
4478	    if (self.activeItems.length) {
4479	      return orig_deleteSelection.call(self, evt);
4480	    }
4481
4482	    return false;
4483	  });
4484	}
4485
4486	/**
4487	 * Plugin: "input_autogrow" (Tom Select)
4488	 *
4489	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4490	 * file except in compliance with the License. You may obtain a copy of the License at:
4491	 * http://www.apache.org/licenses/LICENSE-2.0
4492	 *
4493	 * Unless required by applicable law or agreed to in writing, software distributed under
4494	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4495	 * ANY KIND, either express or implied. See the License for the specific language
4496	 * governing permissions and limitations under the License.
4497	 *
4498	 */
4499	function no_active_items () {
4500	  this.hook('instead', 'setActiveItem', () => {});
4501	  this.hook('instead', 'selectAll', () => {});
4502	}
4503
4504	/**
4505	 * Plugin: "optgroup_columns" (Tom Select.js)
4506	 * Copyright (c) contributors
4507	 *
4508	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4509	 * file except in compliance with the License. You may obtain a copy of the License at:
4510	 * http://www.apache.org/licenses/LICENSE-2.0
4511	 *
4512	 * Unless required by applicable law or agreed to in writing, software distributed under
4513	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4514	 * ANY KIND, either express or implied. See the License for the specific language
4515	 * governing permissions and limitations under the License.
4516	 *
4517	 */
4518	function optgroup_columns () {
4519	  var self = this;
4520	  var orig_keydown = self.onKeyDown;
4521	  self.hook('instead', 'onKeyDown', evt => {
4522	    var index, option, options, optgroup;
4523
4524	    if (!self.isOpen || !(evt.keyCode === KEY_LEFT || evt.keyCode === KEY_RIGHT)) {
4525	      return orig_keydown.call(self, evt);
4526	    }
4527
4528	    optgroup = parentMatch(self.activeOption, '[data-group]');
4529	    index = nodeIndex(self.activeOption, '[data-selectable]');
4530
4531	    if (!optgroup) {
4532	      return;
4533	    }
4534
4535	    if (evt.keyCode === KEY_LEFT) {
4536	      optgroup = optgroup.previousSibling;
4537	    } else {
4538	      optgroup = optgroup.nextSibling;
4539	    }
4540
4541	    if (!optgroup) {
4542	      return;
4543	    }
4544
4545	    options = optgroup.querySelectorAll('[data-selectable]');
4546	    option = options[Math.min(options.length - 1, index)];
4547
4548	    if (option) {
4549	      self.setActiveOption(option);
4550	    }
4551	  });
4552	}
4553
4554	/**
4555	 * Plugin: "remove_button" (Tom Select)
4556	 * Copyright (c) contributors
4557	 *
4558	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4559	 * file except in compliance with the License. You may obtain a copy of the License at:
4560	 * http://www.apache.org/licenses/LICENSE-2.0
4561	 *
4562	 * Unless required by applicable law or agreed to in writing, software distributed under
4563	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4564	 * ANY KIND, either express or implied. See the License for the specific language
4565	 * governing permissions and limitations under the License.
4566	 *
4567	 */
4568	function remove_button (userOptions) {
4569	  const options = Object.assign({
4570	    label: '&times;',
4571	    title: 'Remove',
4572	    className: 'remove',
4573	    append: true
4574	  }, userOptions); //options.className = 'remove-single';
4575
4576	  var self = this; // override the render method to add remove button to each item
4577
4578	  if (!options.append) {
4579	    return;
4580	  }
4581
4582	  var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
4583	  self.hook('after', 'setupTemplates', () => {
4584	    var orig_render_item = self.settings.render.item;
4585
4586	    self.settings.render.item = (data, escape) => {
4587	      var rendered = getDom(orig_render_item.call(self, data, escape));
4588	      var close_button = getDom(html);
4589	      rendered.appendChild(close_button);
4590	      addEvent(close_button, 'mousedown', evt => {
4591	        preventDefault(evt, true);
4592	      });
4593	      addEvent(close_button, 'click', evt => {
4594	        // propagating will trigger the dropdown to show for single mode
4595	        preventDefault(evt, true);
4596	        if (self.isLocked) return;
4597	        var value = rendered.dataset.value;
4598	        self.removeItem(value);
4599	        self.refreshOptions(false);
4600	      });
4601	      return rendered;
4602	    };
4603	  });
4604	}
4605
4606	/**
4607	 * Plugin: "restore_on_backspace" (Tom Select)
4608	 * Copyright (c) contributors
4609	 *
4610	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4611	 * file except in compliance with the License. You may obtain a copy of the License at:
4612	 * http://www.apache.org/licenses/LICENSE-2.0
4613	 *
4614	 * Unless required by applicable law or agreed to in writing, software distributed under
4615	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4616	 * ANY KIND, either express or implied. See the License for the specific language
4617	 * governing permissions and limitations under the License.
4618	 *
4619	 */
4620	function restore_on_backspace (userOptions) {
4621	  const self = this;
4622	  const options = Object.assign({
4623	    text: option => {
4624	      return option[self.settings.labelField];
4625	    }
4626	  }, userOptions);
4627	  self.on('item_remove', function (value) {
4628	    if (self.control_input.value.trim() === '') {
4629	      var option = self.options[value];
4630
4631	      if (option) {
4632	        self.setTextboxValue(options.text.call(self, option));
4633	      }
4634	    }
4635	  });
4636	}
4637
4638	/**
4639	 * Plugin: "restore_on_backspace" (Tom Select)
4640	 * Copyright (c) contributors
4641	 *
4642	 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
4643	 * file except in compliance with the License. You may obtain a copy of the License at:
4644	 * http://www.apache.org/licenses/LICENSE-2.0
4645	 *
4646	 * Unless required by applicable law or agreed to in writing, software distributed under
4647	 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
4648	 * ANY KIND, either express or implied. See the License for the specific language
4649	 * governing permissions and limitations under the License.
4650	 *
4651	 */
4652	function virtual_scroll () {
4653	  const self = this;
4654	  const orig_canLoad = self.canLoad;
4655	  const orig_clearActiveOption = self.clearActiveOption;
4656	  const orig_loadCallback = self.loadCallback;
4657	  var pagination = {};
4658	  var dropdown_content;
4659	  var loading_more = false;
4660
4661	  if (!self.settings.firstUrl) {
4662	    throw 'virtual_scroll plugin requires a firstUrl() method';
4663	  } // in order for virtual scrolling to work,
4664	  // options need to be ordered the same way they're returned from the remote data source
4665
4666
4667	  self.settings.sortField = [{
4668	    field: '$order'
4669	  }, {
4670	    field: '$score'
4671	  }]; // can we load more results for given query?
4672
4673	  function canLoadMore(query) {
4674	    if (typeof self.settings.maxOptions === 'number' && dropdown_content.children.length >= self.settings.maxOptions) {
4675	      return false;
4676	    }
4677
4678	    if (query in pagination && pagination[query]) {
4679	      return true;
4680	    }
4681
4682	    return false;
4683	  } // set the next url that will be
4684
4685
4686	  self.setNextUrl = function (value, next_url) {
4687	    pagination[value] = next_url;
4688	  }; // getUrl() to be used in settings.load()
4689
4690
4691	  self.getUrl = function (query) {
4692	    if (query in pagination) {
4693	      const next_url = pagination[query];
4694	      pagination[query] = false;
4695	      return next_url;
4696	    } // if the user goes back to a previous query
4697	    // we need to load the first page again
4698
4699
4700	    pagination = {};
4701	    return self.settings.firstUrl(query);
4702	  }; // don't clear the active option (and cause unwanted dropdown scroll)
4703	  // while loading more results
4704
4705
4706	  self.hook('instead', 'clearActiveOption', () => {
4707	    if (loading_more) {
4708	      return;
4709	    }
4710
4711	    return orig_clearActiveOption.call(self);
4712	  }); // override the canLoad method
4713
4714	  self.hook('instead', 'canLoad', query => {
4715	    // first time the query has been seen
4716	    if (!(query in pagination)) {
4717	      return orig_canLoad.call(self, query);
4718	    }
4719
4720	    return canLoadMore(query);
4721	  }); // wrap the load
4722
4723	  self.hook('instead', 'loadCallback', (options, optgroups) => {
4724	    if (!loading_more) {
4725	      self.clearOptions();
4726	    }
4727
4728	    orig_loadCallback.call(self, options, optgroups);
4729	    loading_more = false;
4730	  }); // add templates to dropdown
4731	  //	loading_more if we have another url in the queue
4732	  //	no_more_results if we don't have another url in the queue
4733
4734	  self.hook('after', 'refreshOptions', () => {
4735	    const query = self.lastValue;
4736	    var option;
4737
4738	    if (canLoadMore(query)) {
4739	      option = self.render('loading_more', {
4740	        query: query
4741	      });
4742	      if (option) option.setAttribute('data-selectable', ''); // so that navigating dropdown with [down] keypresses can navigate to this node
4743	    } else if (query in pagination && !dropdown_content.querySelector('.no-results')) {
4744	      option = self.render('no_more_results', {
4745	        query: query
4746	      });
4747	    }
4748
4749	    if (option) {
4750	      addClasses(option, self.settings.optionClass);
4751	      dropdown_content.append(option);
4752	    }
4753	  }); // add scroll listener and default templates
4754
4755	  self.on('initialize', () => {
4756	    dropdown_content = self.dropdown_content; // default templates
4757
4758	    self.settings.render = Object.assign({}, {
4759	      loading_more: function () {
4760	        return `<div class="loading-more-results">Loading more results ... </div>`;
4761	      },
4762	      no_more_results: function () {
4763	        return `<div class="no-more-results">No more results</div>`;
4764	      }
4765	    }, self.settings.render); // watch dropdown content scroll position
4766
4767	    dropdown_content.addEventListener('scroll', function () {
4768	      const scroll_percent = dropdown_content.clientHeight / (dropdown_content.scrollHeight - dropdown_content.scrollTop);
4769
4770	      if (scroll_percent < 0.95) {
4771	        return;
4772	      } // !important: this will get checked again in load() but we still need to check here otherwise loading_more will be set to true
4773
4774
4775	      if (!canLoadMore(self.lastValue)) {
4776	        return;
4777	      } // don't call load() too much
4778
4779
4780	      if (loading_more) return;
4781	      loading_more = true;
4782	      self.load.call(self, self.lastValue);
4783	    });
4784	  });
4785	}
4786
4787	TomSelect.define('change_listener', change_listener);
4788	TomSelect.define('checkbox_options', checkbox_options);
4789	TomSelect.define('clear_button', clear_button);
4790	TomSelect.define('drag_drop', drag_drop);
4791	TomSelect.define('dropdown_header', dropdown_header);
4792	TomSelect.define('caret_position', caret_position);
4793	TomSelect.define('dropdown_input', dropdown_input);
4794	TomSelect.define('input_autogrow', input_autogrow);
4795	TomSelect.define('no_backspace_delete', no_backspace_delete);
4796	TomSelect.define('no_active_items', no_active_items);
4797	TomSelect.define('optgroup_columns', optgroup_columns);
4798	TomSelect.define('remove_button', remove_button);
4799	TomSelect.define('restore_on_backspace', restore_on_backspace);
4800	TomSelect.define('virtual_scroll', virtual_scroll);
4801
4802	return TomSelect;
4803
4804})));
4805var tomSelect=function(el,opts){return new TomSelect(el,opts);} 
4806//# sourceMappingURL=tom-select.complete.js.map