tail-select-rails 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,603 +1,1647 @@
1
- /**
2
- * tail.select - The vanilla JavaScript solution to make your <select> fields awesome!
3
- *
4
- * @author Ciprian Popescu <getbutterfly@gmail.com>
5
- * @version 1.0.2
6
- * @url https://getbutterfly.com/tail-select/
7
- * @github https://github.com/wolffe/tail.select.js
8
- * @license MIT License
9
- * @copyright Copyright 2020 - 2024 Ciprian Popescu <getbutterfly@gmail.com>
1
+ /*
2
+ | tail.select - The vanilla solution to make your HTML select fields AWESOME!
3
+ | @file ./js/tail.select.js
4
+ | @author SamBrishes <sam@pytes.net>
5
+ | @version 0.5.16 - Beta
6
+ |
7
+ | @website https://github.com/pytesNET/tail.select
8
+ | @license X11 / MIT License
9
+ | @copyright Copyright © 2014 - 2019 SamBrishes, pytesNET <info@pytes.net>
10
10
  */
11
- const tail = {
12
- select: function (selector, options = {}) {
13
- // Default options
14
- const defaultOptions = {
15
- multiTags: false,
16
- multiCounter: true,
17
- theme: 'light', // light|dark
18
- classNames: 'tail-default',
19
- strings: {
20
- all: "All",
21
- none: "None",
22
- placeholder: "Select an option...",
23
- search: "Type in to search...",
24
- }
25
- };
26
-
27
- // Merge default options with provided options
28
- const opts = { ...defaultOptions, ...options };
29
-
30
- // Extract options
31
- const { multiTags, multiCounter, theme, classNames, strings } = opts;
32
-
33
- //
34
- const originalSelects = document.querySelectorAll(selector);
35
-
36
- originalSelects.forEach((originalSelect) => {
37
- // Hide original dropdown
38
- originalSelect.style.display = "none";
39
-
40
- // Create custom dropdown container
41
- const customDropdown = document.createElement("div");
42
- customDropdown.classList.add("tail-select");
43
- customDropdown.classList.add(originalSelect.id);
44
- customDropdown.classList.add(opts.classNames);
45
- customDropdown.dataset.theme = `tail-theme--${opts.theme}`;
46
-
47
- if (originalSelect.multiple) {
48
- customDropdown.classList.add("tail--multiple");
11
+ ;(function(root, factory){
12
+ if(typeof define === "function" && define.amd){
13
+ define(function(){ return factory(root); });
14
+ } else if(typeof module === "object" && module.exports){
15
+ module.exports = factory(root);
16
+ } else {
17
+ if(typeof root.tail === "undefined"){
18
+ root.tail = {};
19
+ }
20
+ root.tail.select = factory(root);
21
+
22
+ // jQuery Support
23
+ if(typeof jQuery !== "undefined"){
24
+ jQuery.fn.tailselect = function(o){
25
+ var r = [], i;
26
+ this.each(function(){ if((i = tail.select(this, o)) !== false){ r.push(i); } });
27
+ return (r.length === 1)? r[0]: (r.length === 0)? false: r;
28
+ };
29
+ }
30
+
31
+ // MooTools Support
32
+ if(typeof MooTools !== "undefined"){
33
+ Element.implement({ tailselect: function(o){ return new tail.select(this, o); } });
34
+ }
35
+ }
36
+ }(window, function(root){
37
+ "use strict";
38
+ var w = root, d = root.document;
39
+
40
+ // Internal Helper Methods
41
+ function cHAS(el, name){
42
+ return (el && "classList" in el)? el.classList.contains(name): false;
43
+ }
44
+ function cADD(el, name){
45
+ return (el && "classList" in el)? el.classList.add(name): undefined;
46
+ }
47
+ function cREM(el, name){
48
+ return (el && "classList" in el)? el.classList.remove(name): undefined;
49
+ }
50
+ function trigger(el, event, opt){
51
+ if(CustomEvent && CustomEvent.name){
52
+ var ev = new CustomEvent(event, opt);
53
+ } else {
54
+ var ev = d.createEvent("CustomEvent");
55
+ ev.initCustomEvent(event, !!opt.bubbles, !!opt.cancelable, opt.detail);
56
+ }
57
+ return el.dispatchEvent(ev);
58
+ }
59
+ function clone(obj, rep){
60
+ if(typeof Object.assign === "function"){
61
+ return Object.assign({}, obj, rep || {});
62
+ }
63
+ var clone = Object.constructor();
64
+ for(var key in obj){
65
+ clone[key] = (key in rep)? rep[key]: obj[key];
66
+ }
67
+ return clone;
68
+ }
69
+ function create(tag, classes){
70
+ var r = d.createElement(tag);
71
+ r.className = (classes && classes.join)? classes.join(" "): classes || "";
72
+ return r;
73
+ }
74
+
75
+ /*
76
+ | SELECT CONSTRUCTOR
77
+ | @since 0.5.16 [0.3.0]
78
+ */
79
+ var select = function(el, config){
80
+ el = (typeof el === "string")? d.querySelectorAll(el): el;
81
+ if(el instanceof NodeList || el instanceof HTMLCollection || el instanceof Array){
82
+ for(var _r = [], l = el.length, i = 0; i < l; i++){
83
+ _r.push(new select(el[i], clone(config, {})));
84
+ }
85
+ return (_r.length === 1)? _r[0]: ((_r.length === 0)? false: _r);
86
+ }
87
+ if(!(el instanceof Element) || !(this instanceof select)){
88
+ return !(el instanceof Element)? false: new select(el, config);
89
+ }
90
+
91
+ // Check Element
92
+ if(select.inst[el.getAttribute("data-tail-select")]){
93
+ return select.inst[el.getAttribute("data-tail-select")];
94
+ }
95
+ if(el.getAttribute("data-select")){
96
+ var test = JSON.parse(el.getAttribute("data-select").replace(/\'/g, '"'));
97
+ if(test instanceof Object){
98
+ config = clone(config, test); // This is a unofficial function ;3
99
+ }
100
+ }
101
+
102
+ // Get Element Options
103
+ var placeholder = el.getAttribute("placeholder") || el.getAttribute("data-placeholder"),
104
+ fb1 = "bindSourceSelect", fb2 = "sourceHide"; // Fallbacks
105
+ config = (config instanceof Object)? config: {};
106
+ config.multiple = ("multiple" in config)? config.multiple: el.multiple;
107
+ config.disabled = ("disabled" in config)? config.disabled: el.disabled;
108
+ config.placeholder = placeholder || config.placeholder || null;
109
+ config.width = (config.width === "auto")? el.offsetWidth + 50: config.width;
110
+ config.sourceBind = (fb1 in config)? config[fb1]: config.sourceBind || false;
111
+ config.sourceHide = (fb2 in config)? config[fb2]: config.sourceHide || true;
112
+ config.multiLimit = (config.multiLimit >= 0)? config.multiLimit: Infinity;
113
+
114
+
115
+ // Init Instance
116
+ this.e = el;
117
+ this.id = ++select.count;
118
+ this.con = clone(select.defaults, config);
119
+ this.events = {};
120
+ select.inst["tail-" + this.id] = this;
121
+ return this.init().bind();
122
+ }, options;
123
+ select.version = "0.5.16";
124
+ select.status = "beta";
125
+ select.count = 0;
126
+ select.inst = {};
127
+
128
+ /*
129
+ | STORAGE :: DEFAULT OPTIONS
130
+ */
131
+ select.defaults = {
132
+ animate: true, // [0.3.0] Boolean
133
+ classNames: null, // [0.2.0] Boolean, String, Array, null
134
+ csvOutput: false, // [0.3.4] Boolean
135
+ csvSeparator: ",", // [0.3.4] String
136
+ descriptions: false, // [0.3.0] Boolean
137
+ deselect: false, // [0.3.0] Boolean
138
+ disabled: false, // [0.5.0] Boolean
139
+ height: 350, // [0.2.0] Integer, null
140
+ hideDisabled: false, // [0.3.0] Boolean
141
+ hideSelected: false, // [0.3.0] Boolean
142
+ items: {}, // [0.3.0] Object
143
+ locale: "en", // [0.5.0] String
144
+ linguisticRules: { // [0.5.9] Object
145
+ "е": "ё",
146
+ "a": "ä",
147
+ "o": "ö",
148
+ "u": "ü",
149
+ "ss": "ß"
150
+ },
151
+ multiple: false, // [0.2.0] Boolean
152
+ multiLimit: Infinity, // [0.3.0] Integer, Infinity
153
+ multiPinSelected: false, // [0.5.0] Boolean
154
+ multiContainer: false, // [0.3.0] Boolean, String
155
+ multiShowCount: true, // [0.3.0] Boolean
156
+ multiShowLimit: false, // [0.5.0] Boolean
157
+ multiSelectAll: false, // [0.4.0] Boolean
158
+ multiSelectGroup: true, // [0.4.0] Boolean
159
+ openAbove: null, // [0.3.0] Boolean, null
160
+ placeholder: null, // [0.2.0] String, null
161
+ search: false, // [0.3.0] Boolean
162
+ searchConfig: [ // [0.5.13] Array
163
+ "text", "value"
164
+ ],
165
+ searchFocus: true, // [0.3.0] Boolean
166
+ searchMarked: true, // [0.3.0] Boolean
167
+ searchMinLength: 1, // [0.5.13] Integer
168
+ searchDisabled: true, // [0.5.5] Boolean
169
+ sortItems: false, // [0.3.0] String, Function, false
170
+ sortGroups: false, // [0.3.0] String, Function, false
171
+ sourceBind: false, // [0.5.0] Boolean
172
+ sourceHide: true, // [0.5.0] Boolean
173
+ startOpen: false, // [0.3.0] Boolean
174
+ stayOpen: false, // [0.3.0] Boolean
175
+ width: null, // [0.2.0] Integer, String, null
176
+ cbComplete: undefined, // [0.5.0] Function
177
+ cbEmpty: undefined, // [0.5.0] Function
178
+ cbLoopItem: undefined, // [0.4.0] Function
179
+ cbLoopGroup: undefined // [0.4.0] Function
180
+ };
181
+
182
+ /*
183
+ | STORAGE :: STRINGS
184
+ */
185
+ select.strings = {
186
+ de: {
187
+ all: "Alle",
188
+ none: "Keine",
189
+ empty: "Keine Optionen verfügbar",
190
+ emptySearch: "Keine Optionen gefunden",
191
+ limit: "Keine weiteren Optionen wählbar",
192
+ placeholder: "Wähle eine Option...",
193
+ placeholderMulti: "Wähle bis zu :limit Optionen...",
194
+ search: "Tippen zum suchen",
195
+ disabled: "Dieses Feld ist deaktiviert"
196
+ },
197
+ en: {
198
+ all: "All",
199
+ none: "None",
200
+ empty: "No Options available",
201
+ emptySearch: "No Options found",
202
+ limit: "You can't select more Options",
203
+ placeholder: "Select an Option...",
204
+ placeholderMulti: "Select up to :limit Options...",
205
+ search: "Type in to search...",
206
+ disabled: "This Field is disabled"
207
+ },
208
+ es: {
209
+ all: "Todos",
210
+ none: "Ninguno",
211
+ empty: "No hay opciones disponibles",
212
+ emptySearch: "No se encontraron opciones",
213
+ limit: "No puedes seleccionar mas opciones",
214
+ placeholder: "Selecciona una opción...",
215
+ placeholderMulti: "Selecciona hasta :límite de opciones...",
216
+ search: "Escribe dentro para buscar...",
217
+ disabled: "Este campo esta deshabilitado"
218
+ },
219
+ fi: {
220
+ all: "Kaikki",
221
+ none: "Ei mitään",
222
+ empty: "Ei vaihtoehtoja",
223
+ emptySearch: "Etsimääsi vaihtoehtoa ei löytynyt",
224
+ limit: "Muita vaihtoehtoja ei voi valita",
225
+ placeholder: "Valitse...",
226
+ placeholderMulti: "Valitse maksimissaan :limit...",
227
+ search: "Hae tästä...",
228
+ disabled: "Kenttä on poissa käytöstä"
229
+ },
230
+ fr: {
231
+ all: "Tous",
232
+ none: "Aucun",
233
+ empty: "Aucune option disponible",
234
+ emptySearch: "Aucune option trouvée",
235
+ limit: "Aucune autre option sélectionnable",
236
+ placeholder: "Choisissez une option...",
237
+ placeholderMulti: "Choisissez jusqu'à :limit option(s)...",
238
+ search: "Rechercher...",
239
+ disabled: "Ce champs est désactivé"
240
+ },
241
+ it: {
242
+ all: "Tutti",
243
+ none: "Nessuno",
244
+ empty: "Nessuna voce disponibile",
245
+ emptySearch: "Nessuna voce trovata",
246
+ limit: "Non puoi selezionare più Voci",
247
+ placeholder: "Seleziona una Voce",
248
+ placeholderMulti: "Selezione limitata a :limit Voci...",
249
+ search: "Digita per cercare...",
250
+ disabled: "Questo Campo è disabilitato"
251
+ },
252
+ no: {
253
+ all: "Alle",
254
+ none: "Ingen",
255
+ empty: "Ingen valg tilgjengelig",
256
+ emptySearch: "Ingen valg funnet",
257
+ limit: "Du kan ikke velge flere",
258
+ placeholder: "Velg...",
259
+ placeholderMulti: "Velg opptil :limit...",
260
+ search: "Søk...",
261
+ disabled: "Dette feltet er deaktivert"
262
+ },
263
+ pt_BR: {
264
+ all: "Todas",
265
+ none: "Nenhuma",
266
+ empty: "Nenhuma opção disponível",
267
+ emptySearch: "Nenhuma opção encontrada",
268
+ limit: "Não é possível selecionar outra opção",
269
+ placeholder: "Escolha uma opção ...",
270
+ placeholderMulti: "Escolha até: :limit opção(ões) ...",
271
+ search: "Buscar ...",
272
+ disabled: "Campo desativado"
273
+ },
274
+ ru: {
275
+ all: "Все",
276
+ none: "Ничего",
277
+ empty: "Нет доступных вариантов",
278
+ emptySearch: "Ничего не найдено",
279
+ limit: "Вы не можете выбрать больше вариантов",
280
+ placeholder: "Выберите вариант...",
281
+ placeholderMulti: function(args){
282
+ var strings = ["варианта", "вариантов", "вариантов"], cases = [2, 0, 1, 1, 1, 2], num = args[":limit"];
283
+ var string = strings[(num%100 > 4 && num%100 < 20)? 2: cases[(num%10 < 5)? num%10: 5]];
284
+ return "Выбор до :limit " + string + " ...";
285
+ },
286
+ search: "Начните набирать для поиска ...",
287
+ disabled: "Поле отключено"
288
+ },
289
+ tr: {
290
+ all: "Tümü",
291
+ none: "Hiçbiri",
292
+ empty: "Seçenek yok",
293
+ emptySearch: "Seçenek bulunamadı",
294
+ limit: "Daha fazla Seçenek seçemezsiniz",
295
+ placeholder: "Bir Seçenek seçin...",
296
+ placeholderMulti: "En fazla :limit Seçenek seçin...",
297
+ search: "Aramak için yazın...",
298
+ disabled: "Bu Alan kullanılamaz"
299
+ },
300
+ modify: function(locale, id, string){
301
+ if(!(locale in this)){
302
+ return false;
303
+ }
304
+ if((id instanceof Object)){
305
+ for(var key in id){
306
+ this.modify(locale, key, id[key]);
307
+ }
49
308
  } else {
50
- customDropdown.classList.add("tail--single");
309
+ this[locale][id] = (typeof string === "string")? string: this[locale][id];
310
+ }
311
+ return true;
312
+ },
313
+ register: function(locale, object){
314
+ if(typeof locale !== "string" || !(object instanceof Object)){
315
+ return false;
316
+ }
317
+ this[locale] = object;
318
+ return true;
319
+ }
320
+ };
321
+
322
+ /*
323
+ | TAIL.SELECT HANDLER
324
+ */
325
+ select.prototype = {
326
+ /*
327
+ | INERNAL :: TRANSLATE
328
+ | @since 0.5.8 [0.5.8]
329
+ */
330
+ _e: function(string, replace, def){
331
+ if(!(string in this.__)){
332
+ return (!def)? string: def;
51
333
  }
52
334
 
53
- // Create search input
54
- const searchInput = document.createElement("input");
55
- searchInput.type = "text";
56
- searchInput.classList.add('tail--search');
57
- searchInput.placeholder = strings.placeholder || "Select an option...";
335
+ var string = this.__[string];
336
+ if(typeof string === "function"){
337
+ string = string.call(this, replace);
338
+ }
339
+ if(typeof replace === "object"){
340
+ for(var key in replace){
341
+ string = string.replace(key, replace[key]);
342
+ }
343
+ }
344
+ return string;
345
+ },
346
+
347
+ /*
348
+ | INTERNAL :: INIT SELECT FIELD
349
+ | @since 0.5.13 [0.3.0]
350
+ */
351
+ init: function(){
352
+ var self = this, classes = ["tail-select"], con = this.con,
353
+ regexp = /^[0-9.]+(?:cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|\%)$/i;
354
+
355
+ // Init ClassNames
356
+ var c = (con.classNames === true)? this.e.className: con.classNames;
357
+ classes.push((c && c.push)? c.join(" "): (c && c.split)? c: "no-classes");
358
+ if(con.hideSelected){ classes.push("hide-selected"); }
359
+ if(con.hideDisabled){ classes.push("hide-disabled"); }
360
+ if(con.multiLimit == 0){ classes.push("disabled"); }
361
+ if(con.multiple){ classes.push("multiple"); }
362
+ if(con.deselect){ classes.push("deselect"); }
363
+ if(con.disabled){ classes.push("disabled"); }
364
+
365
+ // Init Variables
366
+ this.__ = clone(select.strings.en, select.strings[con.locale] || {});
367
+ this._init = true;
368
+ this._query = false;
369
+ this.select = create("DIV", classes);
370
+ this.label = create("DIV", "select-label");
371
+ this.dropdown = create("DIV", "select-dropdown");
372
+ this.search = create("DIV", "dropdown-search");
373
+ this.csvInput = create("INPUT", "select-search");
374
+
375
+ // Build :: Select
376
+ if(this.e.getAttribute("tabindex") !== null){
377
+ this.select.setAttribute("tabindex", this.e.getAttribute("tabindex"));
378
+ } else {
379
+ this.select.setAttribute("tabindex", 0);
380
+ }
381
+ if(con.width && regexp.test(con.width)){
382
+ this.select.style.width = con.width;
383
+ } else if(con.width && !isNaN(parseFloat(con.width, 10))){
384
+ this.select.style.width = con.width + "px";
385
+ }
58
386
 
59
- // Add focus event to change the placeholder
60
- searchInput.addEventListener("focus", () => {
61
- searchInput.placeholder = strings.search || "Type in to search...";
387
+ // Build :: Label
388
+ this.label.addEventListener("click", function(event){
389
+ self.toggle.call(self, self.con.animate);
62
390
  });
391
+ this.select.appendChild(this.label);
63
392
 
64
- // Add blur event to revert the placeholder when not focused
65
- searchInput.addEventListener("blur", () => {
66
- searchInput.placeholder = strings.placeholder || "Select an option...";
67
- });
393
+ // Build :: Dropdown
394
+ if(!isNaN(parseInt(con.height, 10))){
395
+ this.dropdown.style.maxHeight = parseInt(con.height, 10) + "px";
396
+ }
397
+ if(con.search){
398
+ this.search.innerHTML = '<input type="text" class="search-input" />';
399
+ this.search.children[0].placeholder = this._e("search");
400
+ this.search.children[0].addEventListener("input", function(event){
401
+ self.query.call(self, (this.value.length > con.searchMinLength)? this.value: undefined);
402
+ });
403
+ this.dropdown.appendChild(this.search);
404
+ }
405
+ this.select.appendChild(this.dropdown);
406
+
407
+ // Build :: CSV Input
408
+ this.csvInput.type = "hidden";
409
+ if(con.csvOutput){
410
+ this.csvInput.name = this.e.name;
411
+ this.e.removeAttribute("name");
412
+ this.select.appendChild(this.csvInput);
413
+ }
68
414
 
69
- // Add input event to change the placeholder
70
- searchInput.addEventListener("input", () =>
71
- filterOptions(originalSelect, searchInput)
72
- );
73
-
74
- // Create floating toolbar
75
- const tailFloatingToolbar = document.createElement("div");
76
- tailFloatingToolbar.classList.add("tail--toolbar");
77
-
78
- // Create toggle-all checkbox
79
- const toggleAllCheckbox = document.createElement("input");
80
- toggleAllCheckbox.type = "checkbox";
81
- toggleAllCheckbox.value = strings.all || "All";
82
- toggleAllCheckbox.addEventListener("change", () =>
83
- toggleAll(originalSelect, toggleAllCheckbox)
84
- );
85
-
86
- const toggleAllLabel = document.createElement("label");
87
- toggleAllLabel.textContent = strings.all || "All";
88
- toggleAllLabel.classList.add("all");
89
- toggleAllLabel.appendChild(toggleAllCheckbox);
90
-
91
- // Create uncheck-all button
92
- const uncheckAllButton = document.createElement("button");
93
- uncheckAllButton.type = 'button';
94
- uncheckAllButton.textContent = strings.none || "None";
95
- uncheckAllButton.classList.add("uncheck");
96
- uncheckAllButton.addEventListener("click", () =>
97
- uncheckAll(originalSelect)
98
- );
99
-
100
- if (opts.multiCounter) {
101
- // Create counter
102
- const counter = document.createElement("span");
103
- counter.textContent = "0";
104
- counter.classList.add("tail--counter");
105
-
106
- customDropdown.appendChild(counter);
107
- }
108
-
109
- // Create nested list
110
- const nestedList = document.createElement("div");
111
- nestedList.classList.add("tail--nested-dropdown");
112
- nestedList.style.display = "none"; // Initially hide the list
113
-
114
- customDropdown.appendChild(searchInput);
115
- customDropdown.appendChild(tailFloatingToolbar);
116
- customDropdown.appendChild(nestedList);
117
-
118
- tailFloatingToolbar.appendChild(toggleAllLabel);
119
- tailFloatingToolbar.appendChild(uncheckAllButton);
120
-
121
- // Insert custom dropdown after the original select
122
- originalSelect.insertAdjacentElement("afterend", customDropdown);
123
-
124
- // Create ul element for displaying selected options as pills
125
- const selectedOptionsList = document.createElement("ul");
126
- selectedOptionsList.classList.add("tail--selected-options-list");
127
-
128
- if (opts.multiTags) {
129
- if (originalSelect.multiple) {
130
- // Insert selectedOptionsList as the next sibling of customDropdown
131
- customDropdown.insertAdjacentElement(
132
- "afterend",
133
- selectedOptionsList
134
- );
135
- }
136
- }
137
- //
138
-
139
- function buildNestedList() {
140
- const fragment = document.createDocumentFragment();
141
-
142
- const optgroups = originalSelect.getElementsByTagName(
143
- "optgroup"
144
- );
145
-
146
- if (optgroups.length > 0) {
147
- for (let i = 0; i < optgroups.length; i++) {
148
- const optgroup = optgroups[i];
149
- const optgroupItem = document.createElement("div");
150
- optgroupItem.classList.add("tail--optgroup");
151
-
152
- // Create label for optgroup
153
- const optgroupLabel = document.createElement("label");
154
-
155
- // Create checkbox for optgroup
156
- const optgroupCheckbox = document.createElement(
157
- "input"
158
- );
159
- optgroupCheckbox.type = "checkbox";
160
- optgroupCheckbox.value = optgroup.label;
161
- optgroupCheckbox.addEventListener("change", () =>
162
- toggleOptgroup(optgroupCheckbox)
163
- );
164
- optgroupLabel.appendChild(optgroupCheckbox);
165
-
166
- // Label text for optgroup
167
- const optgroupLabelText = document.createElement(
168
- "span"
169
- );
170
- optgroupLabelText.textContent = optgroup.label;
171
- optgroupLabelText.classList.add("tail--optgroup-label");
172
- optgroupLabel.appendChild(optgroupLabelText);
173
-
174
- optgroupItem.appendChild(optgroupLabel);
175
-
176
- // Nested options list
177
- const nestedOptionsList = document.createElement("div");
178
-
179
- // Add ARIA attributes to the nested options list
180
- nestedOptionsList.setAttribute("role", "listbox");
181
- nestedOptionsList.classList.add("tail--nested-dropdown-list");
182
- const options = optgroup.getElementsByTagName("option");
183
-
184
- for (let j = 0; j < options.length; j++) {
185
- const option = options[j];
186
- const optionItem = document.createElement("div");
187
- optionItem.classList.add("tail--nested-dropdown-item");
188
-
189
- // Create checkbox for option
190
- const optionCheckbox = document.createElement(
191
- "input"
192
- );
193
- optionCheckbox.type = "checkbox";
194
- optionCheckbox.value = option.textContent;
195
-
196
- // Create label for option
197
- const optionLabel = document.createElement("label");
198
-
199
- // Label for option text
200
- const optionLabelText = document.createElement(
201
- "span"
202
- );
203
- optionLabelText.textContent = option.textContent;
204
-
205
- // Option description
206
- if (option.dataset.description) {
207
- optionLabelText.innerHTML += `<small>${option.dataset.description}</small>`;
208
- }
209
-
210
- // Check it
211
- if (option.selected && option.hasAttribute('selected')) {
212
- optionCheckbox.checked = true;
213
- updateCounter(originalSelect);
214
- updateCustomTextInput(originalSelect);
215
- }
216
-
217
- //
218
-
219
- optionLabel.appendChild(optionCheckbox);
220
- optionLabel.appendChild(optionLabelText);
221
-
222
- optionItem.appendChild(optionLabel);
223
-
224
- nestedOptionsList.appendChild(optionItem);
225
- }
415
+ // Prepare Container
416
+ if(con.multiple && con.multiContainer){
417
+ if(d.querySelector(con.multiContainer)){
418
+ this.container = d.querySelector(con.multiContainer);
419
+ this.container.className += " tail-select-container";
420
+ } else if(con.multiContainer === true){
421
+ this.container = this.label;
422
+ this.container.className += " tail-select-container";
423
+ }
424
+ }
226
425
 
227
- optgroupItem.appendChild(nestedOptionsList);
228
- nestedList.appendChild(optgroupItem);
229
- }
426
+ // Prepare Options
427
+ this.options = new options(this.e, this);
428
+ for(var l = this.e.options.length, i = 0; i < l; i++){
429
+ this.options.set(this.e.options[i], false);
430
+ }
431
+ for(var key in con.items){
432
+ if(typeof con.items[key] === "string"){
433
+ con.items[key] = {value: con.items[key]};
434
+ }
435
+ this.options.add(con.items[key].key || key, con.items[key].value,
436
+ con.items[key].group, con.items[key].selected,
437
+ con.items[key].disabled, con.items[key].description);
438
+ }
439
+ this.query();
440
+
441
+ // Append and Return
442
+ if(this.e.nextElementSibling){
443
+ this.e.parentElement.insertBefore(this.select, this.e.nextElementSibling);
444
+ } else {
445
+ this.e.parentElement.appendChild(this.select);
446
+ }
447
+ if(con.sourceHide){
448
+ if(this.e.style.display == "none"){
449
+ this.select.style.display = "none";
450
+ this.e.setAttribute("data-select-hidden", "display");
451
+ } else if(this.e.style.visibility == "hidden"){
452
+ this.select.style.visibiltiy = "hidden";
453
+ this.e.setAttribute("data-select-hidden", "visibility");
230
454
  } else {
231
- const options = originalSelect.getElementsByTagName(
232
- "option"
233
- );
234
-
235
- for (let j = 0; j < options.length; j++) {
236
- const option = options[j];
237
- const optionItem = document.createElement("div");
238
- optionItem.classList.add("tail--nested-dropdown-item");
239
-
240
- // Create checkbox for option
241
- const optionCheckbox = document.createElement("input");
242
- optionCheckbox.type = "checkbox";
243
- optionCheckbox.value = option.textContent;
244
-
245
- // Create label for option
246
- const optionLabel = document.createElement("label");
247
-
248
- // Label for option text
249
- const optionLabelText = document.createElement("span");
250
- optionLabelText.textContent = option.textContent;
251
-
252
- // Option description
253
- if (option.dataset.description) {
254
- optionLabelText.innerHTML += `<small>${option.dataset.description}</small>`;
255
- }
455
+ this.e.style.display = "none";
456
+ this.e.setAttribute("data-select-hidden", "0");
457
+ }
458
+ }
459
+ this.e.setAttribute("data-tail-select", "tail-" + this.id);
460
+ if(self.con.startOpen){
461
+ this.open(con.animate);
462
+ }
463
+ (con.cbComplete || function(){ }).call(this, this.select);
464
+ return (this._init = false)? this: this;
465
+ },
466
+
467
+ /*
468
+ | INTERNAL :: EVENT LISTENER
469
+ | @since 0.5.13 [0.3.0]
470
+ */
471
+ bind: function(){
472
+ var self = this;
473
+
474
+ // Keys Listener
475
+ d.addEventListener("keydown", function(event){
476
+ var key = (event.keyCode || event.which), opt, inner, e, temp, tmp;
477
+ var space = (key == 32 && self.select === document.activeElement);
478
+ if(!space && (!cHAS(self.select, "active") || [13, 27, 38, 40].indexOf(key) < 0)){
479
+ return false;
480
+ }
481
+ event.preventDefault();
482
+ event.stopPropagation();
256
483
 
257
- // Check it
258
- if (option.selected && option.hasAttribute('selected')) {
259
- optionCheckbox.checked = true;
260
- updateCounter(originalSelect);
261
- updateCustomTextInput(originalSelect);
262
- }
263
- //
484
+ // Space
485
+ if(key === 32){
486
+ return self.open(self.con.animate);
487
+ }
264
488
 
265
- optionLabel.appendChild(optionCheckbox);
266
- optionLabel.appendChild(optionLabelText);
489
+ // Enter || Escape
490
+ if(key == 13){
491
+ if((opt = self.dropdown.querySelector(".dropdown-option.hover:not(.disabled)"))){
492
+ self.options.select.call(self.options, opt);
493
+ }
494
+ }
495
+ if(key == 27 || key == 13){
496
+ return self.close(self.con.animate);
497
+ }
267
498
 
268
- optionItem.appendChild(optionLabel);
499
+ // Top || Down
500
+ if((opt = self.dropdown.querySelector(".dropdown-option.hover:not(.disabled)"))){
501
+ cREM(opt, "hover"); e = [((key == 40)? "next": "previous") + "ElementSibling"];
502
+ do {
503
+ if((temp = opt[e]) !== null && opt.tagName == "LI"){
504
+ opt = temp;
505
+ } else if((temp = opt.parentElement[e]) !== null && temp.children.length > 0 && temp.tagName == "UL"){
506
+ opt = temp.children[(key == 40)? 0: temp.children.length-1];
507
+ } else {
508
+ opt = false;
509
+ }
510
+ if(opt && (!cHAS(opt, "dropdown-option") || cHAS(opt, "disabled"))){
511
+ continue;
512
+ }
513
+ break;
514
+ } while(true);
515
+ }
516
+ if(!opt && key == 40){
517
+ opt = self.dropdown.querySelector(".dropdown-option:not(.disabled)");
518
+ } else if(!opt && key == 38){
519
+ tmp = self.dropdown.querySelectorAll(".dropdown-option:not(.disabled)");
520
+ opt = tmp[tmp.length - 1];
521
+ }
522
+ if(opt && (inner = self.dropdown.querySelector(".dropdown-inner"))){
523
+ var pos = (function(el){
524
+ var _r = {top: el.offsetTop, height: el.offsetHeight};
525
+ while((el = el.parentElement) != inner){
526
+ _r.top += el.offsetTop;
527
+ }
528
+ return _r;
529
+ })(opt);
530
+ cADD(opt, "hover");
531
+ inner.scrollTop = Math.max(0, pos.top - (pos.height * 2));
532
+ }
533
+ return true;
534
+ });
535
+
536
+ // Close
537
+ d.addEventListener("click", function(ev){
538
+ if(!cHAS(self.select, "active") || cHAS(self.select, "idle")){ return false; }
539
+ if(self.con.stayOpen === true){ return false; }
269
540
 
270
- nestedList.appendChild(optionItem);
541
+ var targets = [self.e, self.select, self.container];
542
+ for(var l = targets.length, i = 0; i < l; i++){
543
+ if(targets[i] && (targets[i].contains(ev.target) || targets[i] == ev.target)){
544
+ return false;
271
545
  }
546
+ if(!ev.target.parentElement){ return false; }
272
547
  }
548
+ return self.close.call(self, self.con.animate);
549
+ });
273
550
 
274
- // Add ARIA attributes to the custom dropdown container
275
- customDropdown.setAttribute("role", "combobox");
276
- customDropdown.setAttribute("aria-haspopup", "true");
277
- customDropdown.setAttribute("aria-expanded", "false");
278
-
279
- attachOptionCheckboxListeners();
551
+ // Bind Source Select
552
+ if(!this.con.sourceBind){
553
+ return true;
554
+ }
555
+ this.e.addEventListener("change", function(event){
556
+ if(event.detail != undefined){
557
+ return false;
558
+ }
559
+ event.preventDefault();
560
+ event.stopPropagation();
561
+ if(!this.multiple && this.selectedIndex){
562
+ self.options.select.call(self.options, this.options[this.selectedIndex]);
563
+ } else {
564
+ var u = [].concat(self.options.selected);
565
+ var s = [].filter.call(this.querySelectorAll("option:checked"), function(item){
566
+ if(u.indexOf(item) >= 0){
567
+ u.splice(u.indexOf(item), 1);
568
+ return false;
569
+ }
570
+ return true;
571
+ });
572
+ self.options.walk.call(self.options, "unselect", u);
573
+ self.options.walk.call(self.options, "select", s);
574
+ }
575
+ });
576
+ return true;
577
+ },
578
+
579
+ /*
580
+ | INTERNAL :: INTERNAL CALLBACK
581
+ | @since 0.5.14 [0.3.0]
582
+ */
583
+ callback: function(item, state, _force){
584
+ var rkey = item.key.replace(/('|\\)/g, "\\$1"),
585
+ rgrp = item.group.replace(/('|\\)/g, "\\$1"),
586
+ rsel = "[data-key='" + rkey + "'][data-group='" + rgrp + "']";
587
+ if(state == "rebuild"){ return this.query(); }
588
+
589
+ // Set Element-Item States
590
+ var element = this.dropdown.querySelector(rsel);
591
+ if(element && ["select", "disable"].indexOf(state) >= 0){
592
+ cADD(element, (state == "select"? "selected": "disabled"));
593
+ } else if(element && ["unselect", "enable"].indexOf(state) >= 0){
594
+ cREM(element, (state == "unselect"? "selected": "disabled"));
595
+ }
280
596
 
281
- // Append the fragment to the DOM once all changes are made
282
- nestedList.appendChild(fragment);
597
+ // Handle
598
+ this.update(item);
599
+ return (_force === true)? true: this.trigger("change", item, state);
600
+ },
601
+
602
+ /*
603
+ | INTERNAL :: TRIGGER EVENT HANDLER
604
+ | @since 0.5.2 [0.4.0]
605
+ */
606
+ trigger: function(event){
607
+ if(this._init){ return false; }
608
+ var obj = {bubbles: false, cancelable: true, detail: {args: arguments, self: this}};
609
+ if(event == "change" && arguments[2] && arguments[2].indexOf("select") >= 0){
610
+ trigger(this.e, "input", obj);
611
+ trigger(this.e, "change", obj);
283
612
  }
613
+ trigger(this.select, "tail::" + event, obj);
284
614
 
285
- function toggleAll(originalSelect, toggleAllCheckbox) {
286
- const isChecked = toggleAllCheckbox.checked;
287
- const optionCheckboxes = nestedList.querySelectorAll(
288
- 'input[type="checkbox"]'
289
- );
615
+ var args = [], pass;
616
+ Array.prototype.map.call(arguments, function(item, i){
617
+ if(i > 0){ args.push(item); }
618
+ });
619
+ (this.events[event] || []).forEach(function(item){
620
+ pass = [].concat(args);
621
+ pass.push(item.args || null);
622
+ (item.cb || function(){ }).apply(obj.detail.self, pass);
623
+ });
624
+ return true;
625
+ },
626
+
627
+ /*
628
+ | INTERNAL :: CALCULATE DROPDOWN
629
+ | @since 0.5.4 [0.5.0]
630
+ */
631
+ calc: function(){
632
+ var clone = this.dropdown.cloneNode(true), height = this.con.height, search = 0,
633
+ inner = this.dropdown.querySelector(".dropdown-inner");
634
+
635
+ // Calculate Dropdown Height
636
+ clone = this.dropdown.cloneNode(true);
637
+ clone.style.cssText = "height:auto;min-height:auto;max-height:none;opacity:0;display:block;visibility:hidden;";
638
+ clone.style.maxHeight = this.con.height + "px";
639
+ clone.className += " cloned";
640
+ this.dropdown.parentElement.appendChild(clone);
641
+ height = (height > clone.clientHeight)? clone.clientHeight: height;
642
+ if(this.con.search){
643
+ search = clone.querySelector(".dropdown-search").clientHeight;
644
+ }
645
+ this.dropdown.parentElement.removeChild(clone);
646
+
647
+ // Calculate Viewport
648
+ var pos = this.select.getBoundingClientRect(),
649
+ bottom = w.innerHeight-(pos.top+pos.height),
650
+ view = ((height+search) > bottom)? pos.top > bottom: false;
651
+ if(this.con.openAbove === true || (this.con.openAbove !== false && view)){
652
+ view = true, height = Math.min((height), pos.top-10);
653
+ cADD(this.select, "open-top");
654
+ } else {
655
+ view = false, height = Math.min((height), bottom-10);
656
+ cREM(this.select, "open-top");
657
+ }
658
+ if(inner){
659
+ this.dropdown.style.maxHeight = height + "px";
660
+ inner.style.maxHeight = (height-search) + "px";
661
+ }
662
+ return this;
663
+ },
664
+
665
+ /*
666
+ | API :: QUERY OPTIONS
667
+ | @since 0.5.13 [0.5.0]
668
+ */
669
+ query: function(search, conf){
670
+ var item, tp, ul, li, a1, a2; // Pre-Definition
671
+ var self = this, con = this.con, g = "getAttribute"; // Shorties
672
+ var root = create("DIV", "dropdown-inner"), // Contexts
673
+ func = (!search)? "walker": "finder",
674
+ args = (!search)? [con.sortItems, con.sortGroups]: [search, conf];
675
+
676
+ // Option Walker
677
+ this._query = (typeof search === "string")? search: false;
678
+ while(item = this.options[func].apply(this.options, args)){
679
+ if(!ul || (ul && ul[g]("data-group") !== item.group)){
680
+ tp = (con.cbLoopGroup || this.cbGroup).call(this, item.group, search, root);
681
+ if(tp instanceof Element){
682
+ ul = tp;
683
+ ul.setAttribute("data-group", item.group);
684
+ root.appendChild(ul);
685
+ } else { break; }
686
+ }
290
687
 
291
- optionCheckboxes.forEach((checkbox) => {
292
- checkbox.checked = isChecked;
293
- updateOriginalOptionState(originalSelect, checkbox);
688
+ // Create Item
689
+ if((li = (con.cbLoopItem || this.cbItem).call(this, item, ul, search, root)) === null){
690
+ continue;
691
+ }
692
+ if(li === false){ break; }
693
+ li.setAttribute("data-key", item.key);
694
+ li.setAttribute("data-group", item.group);
695
+ li.addEventListener("click", function(event){
696
+ if(!this.hasAttribute("data-key")){ return false; }
697
+ var key = this[g]("data-key"), group = this[g]("data-group") || "#";
698
+ if(self.options.toggle.call(self.options, key, group)){
699
+ if(self.con.stayOpen === false && !self.con.multiple){
700
+ self.close.call(self, self.con.animate);
701
+ }
702
+ }
294
703
  });
704
+ ul.appendChild(li);
705
+ }
706
+
707
+ // Empty
708
+ var count = root.querySelectorAll("*[data-key]").length;
709
+ if(count == 0){
710
+ (this.con.cbEmpty || function(element){
711
+ var li = create("SPAN", "dropdown-empty");
712
+ li.innerText = this._e("empty");
713
+ element.appendChild(li);
714
+ }).call(this, root, search);
295
715
  }
296
716
 
297
- function uncheckAll(originalSelect) {
298
- const optionCheckboxes = nestedList.querySelectorAll(
299
- 'input[type="checkbox"]'
300
- );
717
+ // Select All
718
+ if(count > 0 && con.multiple && con.multiLimit == Infinity && con.multiSelectAll){
719
+ a1 = create("BUTTON", "tail-all"), a2 = create("BUTTON", "tail-none");
720
+ a1.innerText = this._e("all");
721
+ a1.addEventListener("click", function(event){
722
+ event.preventDefault();
723
+ var options = self.dropdown.querySelectorAll(".dropdown-inner .dropdown-option");
724
+ self.options.walk.call(self.options, "select", options);
725
+ })
726
+ a2.innerText = this._e("none");
727
+ a2.addEventListener("click", function(event){
728
+ event.preventDefault();
729
+ var options = self.dropdown.querySelectorAll(".dropdown-inner .dropdown-option");
730
+ self.options.walk.call(self.options, "unselect", options);
731
+ })
732
+
733
+ // Add Element
734
+ li = create("SPAN", "dropdown-action");
735
+ li.appendChild(a1);
736
+ li.appendChild(a2);
737
+ root.insertBefore(li, root.children[0]);
738
+ }
301
739
 
302
- optionCheckboxes.forEach((checkbox) => {
303
- checkbox.checked = false;
304
- updateOriginalOptionState(originalSelect, checkbox);
740
+ // Add and Return
741
+ var data = this.dropdown.querySelector(".dropdown-inner");
742
+ this.dropdown[(data? "replace": "append") + "Child"](root, data);
743
+ if(cHAS(this.select, "active")){
744
+ this.calc();
745
+ }
746
+ return this.updateCSV().updateLabel();
747
+ },
748
+
749
+ /*
750
+ | API :: CALLBACK -> CREATE GROUP
751
+ | @since 0.5.8 [0.4.0]
752
+ */
753
+ cbGroup: function(group, search){
754
+ var ul = create("UL", "dropdown-optgroup"), self = this, a1, a2;
755
+ if(group == "#"){ return ul; }
756
+ ul.innerHTML = '<li class="optgroup-title"><b>' + group + '</b></li>';
757
+ if(this.con.multiple && this.con.multiLimit == Infinity && this.con.multiSelectAll){
758
+ a1 = create("BUTTON", "tail-none"), a2 = create("BUTTON", "tail-all");
759
+ a1.innerText = this._e("none");
760
+ a1.addEventListener("click", function(event){
761
+ event.preventDefault();
762
+ var grp = this.parentElement.parentElement.getAttribute("data-group");
763
+ self.options.all.call(self.options, "unselect", grp);
764
+ });
765
+ a2.innerText = this._e("all");
766
+ a2.addEventListener("click", function(event){
767
+ event.preventDefault();
768
+ var grp = this.parentElement.parentElement.getAttribute("data-group");
769
+ self.options.all.call(self.options, "select", grp);
305
770
  });
771
+ ul.children[0].appendChild(a1);
772
+ ul.children[0].appendChild(a2);
773
+ }
774
+ return ul;
775
+ },
776
+
777
+ /*
778
+ | API :: CALLBACK -> CREATE ITEM
779
+ | @since 0.5.13 [0.4.0]
780
+ */
781
+ cbItem: function(item, optgroup, search){
782
+ var li = create("LI", "dropdown-option" + (item.selected? " selected": "") + (item.disabled? " disabled": ""));
783
+
784
+ // Inner Text
785
+ if(search && search.length > 0 && this.con.searchMarked){
786
+ search = this.options.applyLinguisticRules(search);
787
+ li.innerHTML = item.value.replace(new RegExp("(" + search + ")", "i"), "<mark>$1</mark>");
788
+ } else {
789
+ li.innerText = item.value;
790
+ }
306
791
 
307
- // Uncheck the original <select> options
308
- const originalOptions = originalSelect.getElementsByTagName(
309
- "option"
310
- );
311
- for (let i = 0; i < originalOptions.length; i++) {
312
- originalOptions[i].selected = false;
792
+ // Inner Description
793
+ if(this.con.descriptions && item.description){
794
+ li.innerHTML += '<span class="option-description">' + item.description + '</span>';
795
+ }
796
+ return li;
797
+ },
798
+
799
+ /*
800
+ | API :: UPDATE EVERYTHING
801
+ | @since 0.5.0 [0.5.0]
802
+ */
803
+ update: function(item){
804
+ return this.updateLabel().updateContainer(item).updatePin(item).updateCSV(item);
805
+ },
806
+
807
+ /*
808
+ | API :: UPDATE LABEL
809
+ | @since 0.5.8 [0.5.0]
810
+ */
811
+ updateLabel: function(label){
812
+ if(this.container == this.label && this.options.selected.length > 0){
813
+ if(this.label.querySelector(".label-inner")){
814
+ this.label.removeChild(this.label.querySelector(".label-inner"));
815
+ }
816
+ if(this.label.querySelector(".label-count")){
817
+ this.label.removeChild(this.label.querySelector(".label-count"));
313
818
  }
819
+ return this;
314
820
  }
315
-
316
- function toggleOption(checkbox) {
317
- if (originalSelect.multiple) {
318
- updateOriginalOptionState(originalSelect, checkbox);
821
+ var c = this.con, len = this.options.selected.length, limit;
822
+ if(typeof label !== "string"){
823
+ if(c.disabled){
824
+ label = "disabled";
825
+ } else if(this.dropdown.querySelectorAll("*[data-key]").length == 0){
826
+ label = "empty" + (cHAS(this.select, "in-search")? "Search": "");
827
+ } else if(c.multiLimit <= len){
828
+ label = "limit";
829
+ } else if(!c.multiple && this.options.selected.length > 0){
830
+ label = this.options.selected[0].innerText;
831
+ } else if(typeof c.placeholder === "string"){
832
+ label = c.placeholder;
319
833
  } else {
320
- // For single-select, uncheck all and check the current one
321
- const optionCheckboxes = nestedList.querySelectorAll(
322
- '.tail--nested-dropdown-item input[type="checkbox"]'
323
- );
324
- optionCheckboxes.forEach((cb) => (cb.checked = false));
325
- checkbox.checked = true;
326
- updateOriginalOptionState(originalSelect, checkbox);
327
- }
328
- }
329
-
330
- function toggleOptgroup(optgroupCheckbox) {
331
- const isChecked = optgroupCheckbox.checked;
332
- const nestedOptionsList = optgroupCheckbox
333
- .closest(".tail--optgroup")
334
- .querySelector(".tail--nested-dropdown-list");
335
- const optionCheckboxes = nestedOptionsList.querySelectorAll(
336
- 'input[type="checkbox"]'
337
- );
338
-
339
- optionCheckboxes.forEach((checkbox) => {
340
- checkbox.checked = isChecked;
341
- toggleOption(checkbox); // Call toggleOption for individual options
342
- });
343
-
344
- if (!originalSelect.multiple) {
345
- // For single-select, uncheck all other checkboxes in the same optgroup
346
- const customDropdown = originalSelect.closest(
347
- ".tail-select"
348
- );
349
- if (customDropdown) {
350
- const otherOptgroupCheckboxes = customDropdown.querySelectorAll(
351
- '.tail--nested-dropdown-item input[type="checkbox"]'
352
- );
353
-
354
- otherOptgroupCheckboxes.forEach((cb) => {
355
- if (cb !== optgroupCheckbox) {
356
- cb.checked = false;
357
- updateOriginalOptionState(originalSelect, cb);
358
- }
359
- });
360
- }
834
+ label = "placeholder" + (c.multiple && c.multiLimit < Infinity? "Multi": "");
361
835
  }
362
-
363
- updateOriginalOptionState(originalSelect, optgroupCheckbox);
364
836
  }
365
837
 
366
- function attachOptionCheckboxListeners() {
367
- const optionCheckboxes = nestedList.querySelectorAll(
368
- '.tail--nested-dropdown-item input[type="checkbox"]'
369
- );
838
+ // Set HTML
839
+ label = this._e(label, {":limit": c.multiLimit}, label);
840
+ label = '<span class="label-inner">' + label + '</span>',
841
+ limit = (c.multiShowLimit && c.multiLimit < Infinity);
842
+ if(c.multiple && c.multiShowCount){
843
+ label = '<span class="label-count">:c</span>' + label;
844
+ label = label.replace(":c", len + (limit? (" / " + c.multiLimit): ""));
845
+ }
846
+ this.label.innerHTML = label;
847
+ return this;
848
+ },
849
+
850
+ /*
851
+ | API :: UPDATE CONTAINER
852
+ | @since 0.5.0 [0.5.0]
853
+ */
854
+ updateContainer: function(item){
855
+ if(!this.container || !this.con.multiContainer){
856
+ return this;
857
+ }
858
+ var s = "[data-group='" + item.group + "'][data-key='" + item.key + "']";
859
+ if(this.container.querySelector(s)){
860
+ if(!item.selected){
861
+ this.container.removeChild(this.container.querySelector(s));
862
+ }
863
+ return this;
864
+ }
370
865
 
371
- optionCheckboxes.forEach((checkbox) => {
372
- checkbox.addEventListener("change", () =>
373
- toggleOption(checkbox)
374
- );
866
+ // Create Item
867
+ if(item.selected){
868
+ var self = this, hndl = create("DIV", "select-handle");
869
+ hndl.innerText = item.value;
870
+ hndl.setAttribute("data-key", item.key);
871
+ hndl.setAttribute("data-group", item.group);
872
+ hndl.addEventListener("click", function(event){
873
+ event.preventDefault();
874
+ event.stopPropagation();
875
+ var key = this.getAttribute("data-key"), grp = this.getAttribute("data-group");
876
+ self.options.unselect.call(self.options, key, grp);
375
877
  });
878
+ this.container.appendChild(hndl);
879
+ }
880
+ return this;
881
+ },
882
+
883
+ /*
884
+ | API :: UPDATE PIN POSITION
885
+ | @since 0.5.3 [0.5.0]
886
+ */
887
+ updatePin: function(item){
888
+ var inner = this.dropdown.querySelector(".dropdown-inner ul"),
889
+ option = "li[data-key='" + item.key + "'][data-group='" + item.group + "']";
890
+ if(!this.con.multiPinSelected || !inner || this._query !== false){
891
+ return this;
376
892
  }
377
893
 
378
- function updateOriginalOptionState(
379
- originalSelect,
380
- checkbox,
381
- customDropdown
382
- ) {
383
- const optionValue = checkbox.value;
384
- const option = Array.from(originalSelect.options).find(
385
- (opt) =>
386
- opt.value === optionValue ||
387
- opt.textContent === optionValue
388
- );
389
-
390
- if (option) {
391
- if (checkbox.checked) {
392
- option.selected = true;
393
- } else {
394
- option.selected = false;
894
+ // Create Item
895
+ option = this.dropdown.querySelector(option);
896
+ if(item.selected){
897
+ inner.insertBefore(option, inner.children[0]);
898
+ } else {
899
+ var grp = this.dropdown.querySelector("ul[data-group='" + item.group + "']"),
900
+ prev = this.options[item.index-1], found = false;
901
+ while(prev && prev.group == item.group){
902
+ if(found = grp.querySelector("li[data-key='" + prev.key + "']")){
903
+ break;
395
904
  }
396
-
397
- // Trigger change event for the original select
398
- const event = new Event("change", { bubbles: true });
399
- originalSelect.dispatchEvent(event);
905
+ prev = this.options[prev.index-1];
400
906
  }
401
-
402
- // Get all selected options
403
- const selectedOptions = Array.from(
404
- originalSelect.options
405
- ).filter((opt) => opt.selected);
406
-
407
- // Update the search input value with the selected option text
408
- if (!originalSelect.multiple) {
409
- if (selectedOptions.length > 0 && searchInput) {
410
- searchInput.value = selectedOptions[0].textContent;
411
- } else {
412
- searchInput.value = ""; // Clear the search input if no option is selected
413
- }
907
+ if(found && found.nextElementSibling){
908
+ grp.insertBefore(option, found.nextElementSibling);
414
909
  } else {
415
- // Update searchInput value with selected options
416
- searchInput.value = selectedOptions
417
- .map((opt) => opt.textContent)
418
- .join(", ");
419
- }
420
-
421
- if (opts.multiTags) {
422
- if (originalSelect.multiple) {
423
- // Update the selected options list
424
- updateSelectedOptionsList(
425
- selectedOptionsList,
426
- selectedOptions
427
- );
910
+ grp.appendChild(option);
911
+ }
912
+ }
913
+ return this;
914
+ },
915
+
916
+ /*
917
+ | API :: UPDATE CSV INPUT
918
+ | @since 0.5.0 [0.5.0]
919
+ */
920
+ updateCSV: function(item){
921
+ if(!this.csvInput || !this.con.csvOutput){
922
+ return this;
923
+ }
924
+ for(var selected = [], l = this.options.selected.length, i = 0; i < l; i++){
925
+ selected.push(this.options.selected[i].value);
926
+ }
927
+ this.csvInput.value = selected.join(this.con.csvSeparator || ",");
928
+ return this;
929
+ },
930
+
931
+ /*
932
+ | PUBLIC :: OPEN DROPDOWN
933
+ | @since 0.5.0 [0.3.0]
934
+ */
935
+ open: function(animate){
936
+ if(cHAS(this.select, "active") || cHAS(this.select, "idle") || this.con.disabled){
937
+ return false;
938
+ }
939
+ this.calc();
940
+
941
+ // Final Function
942
+ var final = function(){
943
+ cADD(self.select, "active");
944
+ cREM(self.select, "idle");
945
+ this.dropdown.style.height = "auto";
946
+ this.dropdown.style.overflow = "visible";
947
+ this.label.removeAttribute("style");
948
+ if(this.con.search && this.con.searchFocus){
949
+ this.dropdown.querySelector("input").focus();
950
+ }
951
+ this.trigger.call(this, "open");
952
+ }, self = this, e = this.dropdown.style;
953
+
954
+ // Open
955
+ if(animate !== false){
956
+ this.label.style.zIndex = 25;
957
+ this.dropdown.style.cssText += "height:0;display:block;overflow:hidden;";
958
+ cADD(self.select, "idle");
959
+ (function animate(){
960
+ var h = parseInt(e.height, 10), m = parseInt(e.maxHeight, 10);
961
+ if(h >= m){
962
+ return final.call(self);
963
+ }
964
+ e.height = ((h+50 > m)? m: h+50) + "px";
965
+ setTimeout(animate, 20);
966
+ })();
967
+ } else {
968
+ e.cssText = "height:" + e.maxHeight + ";display:block;overflow:hidden;";
969
+ final.call(this);
970
+ }
971
+ return this;
972
+ },
973
+
974
+ /*
975
+ | PUBLIC :: CLOSE DROPDOWN
976
+ | @since 0.5.0 [0.3.0]
977
+ */
978
+ close: function(animate){
979
+ if(!cHAS(this.select, "active") || cHAS(this.select, "idle")){
980
+ return false;
981
+ }
982
+ var final = function(){
983
+ cREM(this.select, "active");
984
+ cREM(this.select, "idle");
985
+ this.dropdown.removeAttribute("style");
986
+ this.dropdown.querySelector(".dropdown-inner").removeAttribute("style");
987
+ this.trigger.call(this, "close");
988
+ }, self = this, e = this.dropdown;
989
+
990
+ // Close
991
+ if(animate !== false){
992
+ cADD(this.select, "idle");
993
+ this.dropdown.style.overflow = "hidden";
994
+ (function animate(){
995
+ if((parseInt(e.offsetHeight, 10)-50) <= 0){
996
+ return final.call(self);
428
997
  }
998
+ e.style.height = (parseInt(e.offsetHeight, 10)-50) + "px";
999
+ setTimeout(animate, 20);
1000
+ })();
1001
+ } else {
1002
+ final.call(this);
1003
+ }
1004
+ return this;
1005
+ },
1006
+
1007
+ /*
1008
+ | PUBLIC :: TOGGLE DROPDOWN
1009
+ | @since 0.5.0 [0.3.0]
1010
+ */
1011
+ toggle: function(animate){
1012
+ if(cHAS(this.select, "active")){
1013
+ return this.close(animate);
1014
+ }
1015
+ return !cHAS(this.select, "idle")? this.open(animate): this;
1016
+ },
1017
+
1018
+ /*
1019
+ | PUBLIC :: REMOVE SELECT
1020
+ | @since 0.5.3 [0.3.0]
1021
+ */
1022
+ remove: function(){
1023
+ this.e.removeAttribute("data-tail-select");
1024
+ if(this.e.hasAttribute("data-select-hidden")){
1025
+ if(this.e.getAttribute("data-select-hidden") == "0"){
1026
+ this.e.style.removeProperty("display");
429
1027
  }
430
-
431
- // Convert selected options to an array of values
432
- const selectedValues = selectedOptions.map((opt) => opt.value);
433
- // Log the selected values to the console
434
- // console.log(selectedValues);
435
-
436
- var options = originalSelect.options,
437
- count = 0;
438
- for (var i = 0; i < options.length; i++) {
439
- if (options[i].selected) count++;
1028
+ this.e.removeAttribute("data-select-hidden");
1029
+ }
1030
+ Array.prototype.map.call(this.e.querySelectorAll("[data-select-option='add']"), function(item){
1031
+ item.parentElement.removeChild(item);
1032
+ })
1033
+ Array.prototype.map.call(this.e.querySelectorAll("[data-select-optgroup='add']"), function(item){
1034
+ item.parentElement.removeChild(item);
1035
+ })
1036
+ this.e.name = (this.csvInput.hasAttribute("name"))? this.csvInput.name: this.e.name;
1037
+ if(this.select.parentElement){
1038
+ this.select.parentElement.removeChild(this.select);
1039
+ }
1040
+ if(this.container){
1041
+ var handles = this.container.querySelectorAll(".select-handle");
1042
+ for(var l = handles.length, i = 0; i < l; i++){
1043
+ this.container.removeChild(handles[i]);
440
1044
  }
1045
+ }
1046
+ return this;
1047
+ },
1048
+
1049
+ /*
1050
+ | PUBLIC :: RELOAD SELECT
1051
+ | @since 0.5.0 [0.3.0]
1052
+ */
1053
+ reload: function(){
1054
+ return this.remove().init();
1055
+ },
1056
+
1057
+ /*
1058
+ | PUBLIC :: GET|SET CONFIG
1059
+ | @since 0.5.15 [0.4.0]
1060
+ */
1061
+ config: function(key, value, rebuild){
1062
+ if(key instanceof Object){
1063
+ for(var k in key){ this.config(k, key[k], false); }
1064
+ return this.reload()? this.con: this.con;
1065
+ }
1066
+ if(key === void 0){
1067
+ return this.con;
1068
+ } else if(!(key in this.con)){
1069
+ return false;
1070
+ }
441
1071
 
442
- if (opts.multiCounter) {
443
- // Update the counter element
444
- let customId = originalSelect.id;
445
- if (customId) {
446
- let counterElement = document
447
- .querySelector(`.${customId}`)
448
- .querySelector(".tail--counter");
1072
+ // Set | Return
1073
+ if(value === void 0){
1074
+ return this.con[key];
1075
+ }
1076
+ this.con[key] = value;
1077
+ if(rebuild !== false){
1078
+ this.reload();
1079
+ }
1080
+ return this;
1081
+ },
1082
+ enable: function(update){
1083
+ cREM(this.select, "disabled");
1084
+ this.e.disabled = false;
1085
+ this.con.disabled = false;
1086
+ return (update === false)? this: this.reload();
1087
+ },
1088
+ disable: function(update){
1089
+ cADD(this.select, "disabled");
1090
+ this.e.disabled = true;
1091
+ this.con.disabled = true;
1092
+ return (update === false)? this: this.reload();
1093
+ },
1094
+
1095
+ /*
1096
+ | PUBLIC :: CUSTOM EVENT LISTENER
1097
+ | @since 0.5.0 [0.4.0]
1098
+ */
1099
+ on: function(event, callback, args){
1100
+ if(["open", "close", "change"].indexOf(event) < 0 || typeof callback !== "function"){
1101
+ return false;
1102
+ }
1103
+ if(!(event in this.events)){
1104
+ this.events[event] = [];
1105
+ }
1106
+ this.events[event].push({cb: callback, args: (args instanceof Array)? args: []});
1107
+ return this;
1108
+ },
1109
+
1110
+ /*
1111
+ | PUBLIC :: VALUE
1112
+ | @since 0.5.13 [0.5.13]
1113
+ */
1114
+ value: function(){
1115
+ if(this.options.selected.length == 0){
1116
+ return null;
1117
+ }
1118
+ if(this.con.multiple){
1119
+ return this.options.selected.map(function(opt){
1120
+ return opt.value;
1121
+ });
1122
+ }
1123
+ return this.options.selected[0].value;
1124
+ }
1125
+ };
1126
+
1127
+ /*
1128
+ | OPTIONS CONSTRUCTOR
1129
+ | @since 0.5.12 [0.3.0]
1130
+ */
1131
+ options = select.options = function(select, parent){
1132
+ if(!(this instanceof options)){
1133
+ return new options(select, parent);
1134
+ }
1135
+ this.self = parent;
1136
+ this.element = select;
1137
+ this.length = 0;
1138
+ this.selected = [];
1139
+ this.disabled = [];
1140
+ this.items = {"#": {}};
1141
+ this.groups = {};
1142
+ return this;
1143
+ }
449
1144
 
450
- if (counterElement) {
451
- counterElement.textContent = count;
452
- }
453
- }
1145
+ /*
1146
+ | TAIL.OPTIONS HANDLER
1147
+ */
1148
+ options.prototype = {
1149
+ /*
1150
+ | INTERNAL :: REPLACE TYPOs
1151
+ | @since 0.5.0 [0.3.0]
1152
+ */
1153
+ _r: function(state){
1154
+ return state.replace("disabled", "disable").replace("enabled", "enable")
1155
+ .replace("selected", "select").replace("unselected", "unselect");
1156
+ },
1157
+
1158
+ /*
1159
+ | GET AN EXISTING OPTION
1160
+ | @since 0.5.7 [0.3.0]
1161
+ */
1162
+ get: function(key, grp){
1163
+ var g = "getAttribute";
1164
+ if(typeof key === "object" && key.key && key.group){
1165
+ grp = key.group || grp;
1166
+ key = key.key;
1167
+ } else if(key instanceof Element){
1168
+ if(key.tagName == "OPTION"){
1169
+ grp = key.parentElement.label || "#";
1170
+ key = key.value || key.innerText;
1171
+ } else if(key.hasAttribute("data-key")){
1172
+ grp = key[g]("data-group") || key.parentElement[g]("data-group") || "#";
1173
+ key = key[g]("data-key");
454
1174
  }
1175
+ } else if(typeof key !== "string"){
1176
+ return false;
455
1177
  }
1178
+ key = (/^[0-9]+$/.test(key))? "_" + key: key;
1179
+ return (grp in this.items)? this.items[grp][key]: false;
1180
+ },
1181
+
1182
+ /*
1183
+ | SET AN EXISTING OPTION
1184
+ | @since 0.5.15 [0.3.0]
1185
+ */
1186
+ set: function(opt, rebuild){
1187
+ var key = opt.value || opt.innerText, grp = opt.parentElement.label || "#";
1188
+ if(!(grp in this.items)){
1189
+ this.items[grp] = {};
1190
+ this.groups[grp] = opt.parentElement;
1191
+ }
1192
+ if(key in this.items[grp]){
1193
+ return false;
1194
+ }
1195
+ var id = (/^[0-9]+$/.test(key))? "_" + key: key;
456
1196
 
457
- function filterOptions(originalSelect, searchInput) {
458
- const searchTerm = searchInput.value.trim().toLowerCase();
459
- const optionItems = nestedList.querySelectorAll("div");
1197
+ // Validate Selection
1198
+ var con = this.self.con;
1199
+ if(con.multiple && this.selected.length >= con.multiLimit){
1200
+ opt.selected = false;
1201
+ }
1202
+ if(opt.selected && con.deselect && (!opt.hasAttribute("selected") || con.multiLimit == 0)){
1203
+ opt.selected = false;
1204
+ opt.parentElement.selectedIndex = -1;
1205
+ }
460
1206
 
461
- optionItems.forEach((optionItem) => {
462
- const optionCheckbox = optionItem.querySelector(
463
- 'input[type="checkbox"]'
464
- );
465
- const optionLabel = optionCheckbox.nextElementSibling.textContent.toLowerCase();
466
- const optgroupItem = optionItem.closest("div");
1207
+ // Sanitize Description
1208
+ if(opt.hasAttribute("data-description")){
1209
+ var span = create("SPAN");
1210
+ span.innerHTML = opt.getAttribute("data-description");
1211
+ opt.setAttribute("data-description", span.innerHTML);
1212
+ }
467
1213
 
468
- // Hide or show options based on the search term
469
- optionCheckbox.style.display = optionLabel.includes(
470
- searchTerm
471
- )
472
- ? "inline-block"
473
- : "none";
474
- });
1214
+ // Add Item
1215
+ this.items[grp][id] = {
1216
+ key: key,
1217
+ value: opt.text,
1218
+ description: opt.getAttribute("data-description") || null,
1219
+ group: grp,
1220
+ option: opt,
1221
+ optgroup: (grp != "#")? this.groups[grp]: undefined,
1222
+ selected: opt.selected,
1223
+ disabled: opt.disabled,
1224
+ hidden: opt.hidden || false
1225
+ };
1226
+ this.length++;
1227
+ if(opt.selected){ this.select(this.items[grp][id]); }
1228
+ if(opt.disabled){ this.disable(this.items[grp][id]); }
1229
+ return (rebuild)? this.self.callback(this.items[grp][key], "rebuild"): true;
1230
+ },
1231
+
1232
+ /*
1233
+ | CREATE A NEW OPTION
1234
+ | @since 0.5.13 [0.3.0]
1235
+ */
1236
+ add: function(key, value, group, selected, disabled, description, rebuild){
1237
+ if(key instanceof Object){
1238
+ for(var k in key){
1239
+ this.add(key[k].key || k, key[k].value, key[k].group, key[k].selected, key[k].disabled, key[k].description, false);
1240
+ }
1241
+ return this.self.query();
1242
+ }
1243
+ if(this.get(key, group)){
1244
+ return false;
1245
+ }
475
1246
 
476
- optionItems.forEach((optionItem) => {
477
- const optionCheckbox = optionItem.querySelector(
478
- 'input[type="checkbox"]'
479
- );
480
- const optgroupItem = optionItem.closest("div");
481
-
482
- // Check if there are visible checkboxes within the nested list
483
- const nestedCheckboxes = optionItem.querySelectorAll(
484
- 'div input[type="checkbox"]:not([style="display: none;"])'
485
- );
486
- const hasVisibleNestedCheckboxes =
487
- nestedCheckboxes.length > 0;
488
-
489
- // Show the parent li only if the checkbox or a visible nested checkbox is present
490
- optgroupItem.style.display =
491
- optionCheckbox.style.display === "inline-block" ||
492
- hasVisibleNestedCheckboxes
493
- ? "block"
494
- : "none";
495
- });
1247
+ // Check Group
1248
+ group = (typeof group === "string")? group: "#";
1249
+ if(group !== "#" && !(group in this.groups)){
1250
+ var optgroup = create("OPTGROUP");
1251
+ optgroup.label = group;
1252
+ optgroup.setAttribute("data-select-optgroup", "add");
1253
+ this.element.appendChild(optgroup);
1254
+ this.items[group] = {};
1255
+ this.groups[group] = optgroup;
496
1256
  }
497
1257
 
498
- function updateSelectedOptionsList(
499
- selectedOptionsList,
500
- selectedOptions
501
- ) {
502
- // Clear existing list items
503
- selectedOptionsList.innerHTML = "";
1258
+ // Validate Selection
1259
+ if(this.self.con.multiple && this.selected.length >= this.self.con.multiLimit){
1260
+ selected = false;
1261
+ }
1262
+ disabled = !!disabled;
1263
+
1264
+ // Create Option
1265
+ var option = d.createElement("OPTION");
1266
+ option.value = key;
1267
+ option.selected = selected;
1268
+ option.disabled = disabled;
1269
+ option.innerText = value;
1270
+ option.setAttribute("data-select-option", "add");
1271
+ if(description && description.length > 0){
1272
+ option.setAttribute("data-description", description);
1273
+ }
504
1274
 
505
- // Create list items for each selected option
506
- selectedOptions.forEach((opt) => {
507
- const listItem = document.createElement("li");
508
- listItem.textContent = opt.textContent;
509
- selectedOptionsList.appendChild(listItem);
510
- });
1275
+ // Add Option and Return
1276
+ ((group == "#")? this.element: this.groups[group]).appendChild(option);
1277
+ return this.set(option, rebuild);
1278
+ },
1279
+
1280
+ /*
1281
+ | MOVE AN EXISTING OPTION
1282
+ | @since 0.5.0 [0.5.0]
1283
+ */
1284
+ move: function(item, group, new_group, rebuild){
1285
+ if(!(item = this.get(item, group))){ return false; }
1286
+
1287
+ // Create Group
1288
+ if(new_group !== "#" && !(new_group in this.groups)){
1289
+ var optgroup = create("OPTGROUP");
1290
+ optgroup.label = new_group;
1291
+ this.element.appendChild(optgroup);
1292
+ this.items[new_group] = {};
1293
+ this.groups[new_group] = optgroup;
1294
+ this.groups[new_group].appendChild(item.option);
511
1295
  }
512
1296
 
513
- function updateCustomTextInput(originalSelect) {
514
- // Get all selected options
515
- const selectedOptions = Array.from(originalSelect.options).filter((opt) => {
516
- // Check if the option is selected and has the 'selected' attribute
517
- return opt.selected && opt.hasAttribute('selected');
518
- });
1297
+ // Move To Group
1298
+ delete this.items[item.group][item.key];
1299
+ item.group = new_group;
1300
+ item.optgroup = this.groups[new_group] || undefined;
1301
+ this.items[new_group][item.key] = item;
1302
+ return (rebuild)? this.self.query(): true;
1303
+ },
1304
+
1305
+ /*
1306
+ | REMOVE AN EXISTING OPTION
1307
+ | @since 0.5.7 [0.3.0]
1308
+ */
1309
+ remove: function(item, group, rebuild){
1310
+ if(!(item = this.get(item, group))){ return false; }
1311
+ if(item.selected){ this.unselect(item); }
1312
+ if(item.disabled){ this.enable(item); }
1313
+
1314
+ // Remove Data
1315
+ item.option.parentElement.removeChild(item.option);
1316
+ var id = (/^[0-9]+$/.test(item.key))? "_" + item.key: item.key;
1317
+ delete this.items[item.group][id];
1318
+ this.length--;
1319
+
1320
+ // Remove Optgroup
1321
+ if(Object.keys(this.items[item.group]).length === 0){
1322
+ delete this.items[item.group];
1323
+ delete this.groups[item.group];
1324
+ }
1325
+ return (rebuild)? this.self.query(): true;
1326
+ },
1327
+
1328
+ /*
1329
+ | CHECK AN EXISTING OPTION
1330
+ | @since 0.5.0 [0.3.0]
1331
+ */
1332
+ is: function(state, key, group){
1333
+ var state = this._r(state), item = this.get(key, group);
1334
+ if(!item || ["select", "unselect", "disable", "enable"].indexOf(state) < 0){
1335
+ return null;
1336
+ }
1337
+ if(state == "disable" || state == "enable"){
1338
+ return (state == "disable")? item.disabled: !item.disabled;
1339
+ } else if(state == "select" || state == "unselect"){
1340
+ return (state == "select")? item.selected: !item.selected;
1341
+ }
1342
+ return false;
1343
+ },
1344
+
1345
+ /*
1346
+ | INTERACT WITH AN OPTION
1347
+ | @since 0.5.3 [0.3.0]
1348
+ */
1349
+ handle: function(state, key, group, _force){
1350
+ var item = this.get(key, group), state = this._r(state);
1351
+ if(!item || ["select", "unselect", "disable", "enable"].indexOf(state) < 0){
1352
+ return null;
1353
+ }
519
1354
 
1355
+ // Disable || Enable
1356
+ if(state == "disable" || state == "enable"){
1357
+ if(!(item.option in this.disabled) && state == "disable"){
1358
+ this.disabled.push(item.option);
1359
+ } else if((item.option in this.disabled) && state == "enable"){
1360
+ this.disabled.splice(this.disabled.indexOf(item.option), 1);
1361
+ }
1362
+ item.disabled = (state == "disable");
1363
+ item.option.disabled = (state == "disable");
1364
+ return this.self.callback.call(this.self, item, state);
1365
+ }
520
1366
 
521
- // Update the search input value with the selected option text
522
- if (!originalSelect.multiple) {
523
- if (selectedOptions.length > 0 && searchInput) {
524
- searchInput.value = selectedOptions[0].textContent;
525
- } else {
526
- searchInput.value = ""; // Clear the search input if no option is selected
1367
+ // Select || Unselect
1368
+ var dis = (cHAS(this.self.select, "disabled") || item.disabled || item.option.disabled),
1369
+ lmt = (this.self.con.multiple && this.self.con.multiLimit <= this.selected.length),
1370
+ sgl = (!this.self.con.multiple && this.selected.indexOf(item.option) > 0),
1371
+ del = (this.self.con.multiLimit == 0 && this.self.con.deselect == true),
1372
+ uns = (!this.self.con.multiple && !this.self.con.deselect && _force !== true);
1373
+ if(state == "select"){
1374
+ if(dis || lmt || del || sgl){
1375
+ return false;
1376
+ }
1377
+ if(!this.self.con.multiple){
1378
+ for(var i in this.selected){
1379
+ this.unselect(this.selected[i], undefined, true);
527
1380
  }
1381
+ }
1382
+ if(this.selected.indexOf(item.option) < 0){
1383
+ this.selected.push(item.option);
1384
+ }
1385
+ } else if(state == "unselect"){
1386
+ if(dis || uns){
1387
+ return false;
1388
+ }
1389
+ this.selected.splice(this.selected.indexOf(item.option), 1);
1390
+ }
1391
+ item.selected = (state == "select");
1392
+ item.option.selected = (state == "select");
1393
+ item.option[(state.length > 6? "remove": "set") + "Attribute"]("selected", "selected");
1394
+ return this.self.callback.call(this.self, item, state, _force);
1395
+ },
1396
+ enable: function(key, group){
1397
+ return this.handle("enable", key, group, false);
1398
+ },
1399
+ disable: function(key, group){
1400
+ return this.handle("disable", key, group, false);
1401
+ },
1402
+ select: function(key, group){
1403
+ return this.handle("select", key, group, false);
1404
+ },
1405
+ unselect: function(key, group, _force){
1406
+ return this.handle("unselect", key, group, _force);
1407
+ },
1408
+ toggle: function(item, group){
1409
+ if(!(item = this.get(item, group))){ return false; }
1410
+ return this.handle((item.selected? "unselect": "select"), item, group, false);
1411
+ },
1412
+
1413
+ /*
1414
+ | INVERT CURRENT <STATE>
1415
+ | @since 0.5.15 [0.3.0]
1416
+ */
1417
+ invert: function(state){
1418
+ state = this._r(state);
1419
+ if(["enable", "disable"].indexOf(state) >= 0){
1420
+ var invert = this.disabled, action = (state == "enable")? "disable": "enable";
1421
+ } else if(["select", "unselect"].indexOf(state) >= 0){
1422
+ var invert = this.selected, action = (state == "select")? "unselect": "select";
1423
+ }
1424
+ var convert = Array.prototype.filter.call(this, function(element){
1425
+ return !(element in invert);
1426
+ }), self = this;
1427
+
1428
+ // Loop
1429
+ [].concat(invert).forEach(function(item){
1430
+ self.handle.call(self, action, item);
1431
+ });
1432
+ [].concat(convert).forEach(function(item){
1433
+ self.handle.call(self, state, item);
1434
+ });
1435
+ return true;
1436
+ },
1437
+
1438
+ /*
1439
+ | SET <STATE> ON ALL OPTIONs
1440
+ | @since 0.5.0 [0.5.0]
1441
+ */
1442
+ all: function(state, group){
1443
+ var self = this, list = this;
1444
+ if(group in this.items){
1445
+ list = Object.keys(this.items[group]);
1446
+ } else if(["unselect", "enable"].indexOf(state) >= 0){
1447
+ list = [].concat((state == "unselect")? this.selected: this.disabled);
1448
+ }
1449
+ Array.prototype.forEach.call(list, function(item){
1450
+ self.handle.call(self, state, item, group, false);
1451
+ });
1452
+ return true;
1453
+ },
1454
+
1455
+ /*
1456
+ | SET <STATE> FOR A BUNCH OF OPTIONs
1457
+ | @since 0.5.4 [0.5.3]
1458
+ */
1459
+ walk: function(state, items, args){
1460
+ if(items instanceof Array || items.length){
1461
+ for(var l = items.length, i = 0; i < l; i++){
1462
+ this.handle.apply(this, [state, items[i], null].concat(args));
1463
+ }
1464
+ } else if(items instanceof Object){
1465
+ var self = this;
1466
+ if(items.forEach){
1467
+ items.forEach(function(value){
1468
+ self.handle.apply(self, [state, value, null].concat(args));
1469
+ });
528
1470
  } else {
529
- // Update searchInput value with selected options
530
- searchInput.value = selectedOptions
531
- .map((opt) => opt.textContent)
532
- .join(", ");
533
- }
534
-
535
- if (opts.multiTags) {
536
- if (originalSelect.multiple) {
537
- // Update the selected options list
538
- updateSelectedOptionsList(
539
- selectedOptionsList,
540
- selectedOptions
541
- );
1471
+ for(var key in items){
1472
+ if(typeof items[key] !== "string" && typeof items[key] !== "number" && !(items[key] instanceof Element)){
1473
+ continue;
1474
+ }
1475
+ this.handle.apply(this, [state, items[key], (key in this.items? key: null)]).concat(args);
542
1476
  }
543
1477
  }
544
1478
  }
1479
+ return this;
1480
+ },
1481
+
1482
+ /*
1483
+ | APPLY LINGUSTIC RULES
1484
+ | @since 0.5.13 [0.5.13]
1485
+ */
1486
+ applyLinguisticRules: function(search, casesensitive){
1487
+ var rules = this.self.con.linguisticRules, values = [];
1488
+
1489
+ // Prepare Rules
1490
+ Object.keys(rules).forEach(function(key){
1491
+ values.push("(" + key + "|[" + rules[key] + "])");
1492
+ });
1493
+ if(casesensitive){
1494
+ values = values.concat(values.map(function(s){ return s.toUpperCase(); }));
1495
+ }
545
1496
 
1497
+ return search.replace(new RegExp(values.join("|"), (casesensitive)? "g": "ig"), function(m){
1498
+ return values[[].indexOf.call(arguments, m, 1) - 1];
1499
+ });
1500
+ },
546
1501
 
547
- function updateCounter(originalSelect) {
548
- // Get the custom ID for the current original select
549
- let customId = originalSelect.id;
550
-
551
- if (customId) {
552
- // Get the counter element
553
- let counterElement = document
554
- .querySelector(`.${customId}`)
555
- .querySelector(".tail--counter");
556
1502
 
557
- if (counterElement) {
558
- // Get the count of selected options
559
- const count = Array.from(originalSelect.options).filter(
560
- (opt) => opt.selected
561
- ).length;
1503
+ /*
1504
+ | FIND SOME OPTIONs - ARRAY EDITION
1505
+ | @since 0.5.15 [0.3.0]
1506
+ */
1507
+ find: function(search, config){
1508
+ var self = this, matches, has = {};
562
1509
 
563
- // Update the counter element
564
- counterElement.textContent = count;
565
- }
566
- }
1510
+ // Get Config
1511
+ if(!config){
1512
+ config = this.self.con.searchConfig;
567
1513
  }
568
1514
 
569
- function toggleDropdownVisibility() {
570
- nestedList.style.display = "block";
571
- customDropdown.setAttribute("aria-expanded", "true");
1515
+ // Config Callback
1516
+ if(typeof config === "function"){
1517
+ matches = config.bind(this, search);
572
1518
  }
573
1519
 
574
- function hideDropdown() {
575
- nestedList.style.display = "none";
576
- customDropdown.setAttribute("aria-expanded", "false");
577
- }
1520
+ // Config Handler
1521
+ else {
1522
+ config = (config instanceof Array)? config: [config];
1523
+ config.forEach(function(c){
1524
+ if(typeof c === "string"){ has[c] = true; }
1525
+ });
1526
+ has.any = (!has.any)? has.attributes && has.value: has.any;
578
1527
 
579
- function handleClickOutside(event) {
580
- if (!customDropdown.contains(event.target)) {
581
- hideDropdown();
1528
+ // Cleanup & Prepare
1529
+ if(!has.regex || has.text){
1530
+ search = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1531
+ }
1532
+ if(!has.exactglyphes){
1533
+ search = this.self.options.applyLinguisticRules(search, has.case);
1534
+ }
1535
+ if(has.word){
1536
+ search = '\\b' + search + '\\b';
582
1537
  }
583
- }
584
1538
 
585
- function handleKeyDown(event) {
586
- if (event.key === "Escape") {
587
- hideDropdown();
1539
+ // Search
1540
+ var regex = new RegExp(search, (!has.case)? "mi": "m"),
1541
+ sfunc = function(opt){ return regex.test(opt.text || opt.value); };
1542
+
1543
+ // Handle
1544
+ if(has.any){
1545
+ matches = function(opt){ return sfunc(opt) || [].some.call(opt.attributes, sfunc); };
1546
+ } else if(has.attributes){
1547
+ matches = function(opt){ return [].some.call(opt.attributes, sfunc); };
1548
+ } else {
1549
+ matches = sfunc;
1550
+ }
1551
+
1552
+ if(!this.self.con.searchDisabled){
1553
+ var temp = matches;
1554
+ matches = function(opt){ return !opt.disabled && temp(opt); };
588
1555
  }
589
1556
  }
590
1557
 
591
- // Show the dropdown when the input field is focused
592
- searchInput.addEventListener("focus", toggleDropdownVisibility);
1558
+ // Hammer Time
1559
+ return [].filter.call(this.self.e.options, matches).map(function(opt){
1560
+ return opt.hidden? false: self.get(opt)
1561
+ });
1562
+ },
1563
+
1564
+ /*
1565
+ | FIND SOME OPTIONs - WALKER EDITION
1566
+ | @since 0.5.5 [0.3.0]
1567
+ */
1568
+ finder: function(search, config){
1569
+ if(this._finderLoop === undefined){
1570
+ this._finderLoop = this.find(search, config);
1571
+ }
1572
+ var item;
1573
+ while((item = this._finderLoop.shift()) !== undefined){
1574
+ return item;
1575
+ }
1576
+ delete this._finderLoop;
1577
+ return false;
1578
+ },
1579
+
1580
+ /*
1581
+ | NEW OPTIONS WALKER
1582
+ | @since 0.5.15 [0.4.0]
1583
+ */
1584
+ walker: function(orderi, orderg){
1585
+ if(typeof this._inLoop !== "undefined" && this._inLoop){
1586
+ if(this._inItems.length > 0){
1587
+ do {
1588
+ var temp = this.items[this._inGroup][this._inItems.shift()];
1589
+ } while(temp.hidden === true);
1590
+ return temp;
1591
+ }
593
1592
 
594
- // Hide the dropdown when clicking outside of it
595
- document.addEventListener("click", handleClickOutside);
1593
+ // Sort Items
1594
+ if(this._inGroups.length > 0){
1595
+ while(this._inGroups.length > 0){
1596
+ var group = this._inGroups.shift();
1597
+ if(!(group in this.items)){
1598
+ return false;
1599
+ }
596
1600
 
597
- // Hide the dropdown when pressing the ESC key
598
- document.addEventListener("keydown", handleKeyDown);
1601
+ var keys = Object.keys(this.items[group]);
1602
+ if(keys.length > 0){
1603
+ break;
1604
+ }
1605
+ }
1606
+ if(orderi == "ASC"){
1607
+ keys.sort();
1608
+ } else if(orderi == "DESC"){
1609
+ keys.sort().reverse();
1610
+ } else if(typeof orderi === "function"){
1611
+ keys = orderi.call(this, keys);
1612
+ }
1613
+ this._inItems = keys;
1614
+ this._inGroup = group;
1615
+ return this.walker(null, null);
1616
+ }
599
1617
 
600
- buildNestedList();
601
- });
602
- }
603
- };
1618
+ // Delete and Exit
1619
+ delete this._inLoop;
1620
+ delete this._inItems;
1621
+ delete this._inGroup;
1622
+ delete this._inGroups;
1623
+ return false;
1624
+ }
1625
+
1626
+ // Sort Groups
1627
+ var groups = Object.keys(this.groups) || [];
1628
+ if(orderg == "ASC"){
1629
+ groups.sort();
1630
+ } else if(orderg == "DESC"){
1631
+ groups.sort().reverse();
1632
+ } else if(typeof orderg === "function"){
1633
+ groups = orderg.call(this, groups);
1634
+ }
1635
+ groups.unshift("#");
1636
+
1637
+ // Init Loop
1638
+ this._inLoop = true;
1639
+ this._inItems = [];
1640
+ this._inGroups = groups;
1641
+ return this.walker(orderi, null);
1642
+ }
1643
+ };
1644
+
1645
+ // Return
1646
+ return select;
1647
+ }));