tom-select-rails 0.1.0

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