tail-select-rails 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }));