tom-select-rails 0.1.0

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