tail-select-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bb5f0cae3224dd89b8818fd901f9f9d55bbf5f496f27e179b217416bb492fcc4
4
+ data.tar.gz: c5eb92f904c0113c43aa619996216907f4928a44d5fab1b453f255fd6ab8cda8
5
+ SHA512:
6
+ metadata.gz: 709ce7b81c61db1aae91b4cf86beca85f76e07b15f37928eeb7088ea44a1e60b061239d9b3255062fdc99dc074a49d421a86b3f703f8d88ea67fb3124843bcc7
7
+ data.tar.gz: fca58fd169a1ddc2fa8b86f9f8ad2954ff1d08ffa84e934be22d8eff2e3d240df72f95d46ce29dda4e7304b9693464a0f3e6347cfda10752e1785b9d4af75409
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Gajendra Jena <gaju.mca@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge as the original js library is opensource
6
+ , to any person obtaining a copy of this software and associated documentation files
7
+ (the "Software"), to deal in the Software without restriction, including without
8
+ limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
+ and/or sell copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/Readme.md ADDED
@@ -0,0 +1,42 @@
1
+ tail-select-rails
2
+ =================
3
+
4
+ A gem to easily integrate [tail-select](https://getbutterfly.com/tail-select/) with the rails 6.1+ asset pipeline.
5
+
6
+ Currently this gem brings support for tail-select **v1.0.2**.
7
+
8
+ ## Installation
9
+
10
+ First add the following lines to your applications `Gemfile`:
11
+
12
+ ``` ruby
13
+ gem 'tail-select-rails'
14
+ ```
15
+
16
+ Run `bundle install`.
17
+
18
+ Add the following lines to `app/assets/javascripts/application.js`:
19
+
20
+ ``` javascript
21
+ //= require jquery
22
+ //= require tail-select-rails
23
+ ```
24
+
25
+ Finalize the setup by adding these lines to `app/assets/stylesheets/application.css`:
26
+
27
+ ``` css
28
+ /*
29
+ *= require tail-select-rails
30
+ */
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ Please refer to [https://getbutterfly.com/tail-select/](https://getbutterfly.com/tail-select/)
36
+ for all the details on how to use.
37
+
38
+ ### Credits
39
+
40
+ * tail-select-rails is (c) Copyright Gajendra Jena
41
+ * This gem is maintained by [@gajendrajena](https://www.twitter.com/gajendrajena)
42
+
@@ -0,0 +1,6 @@
1
+ require 'rails'
2
+
3
+ module TailSelectRails
4
+ class Engine < Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,603 @@
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>
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");
49
+ } else {
50
+ customDropdown.classList.add("tail--single");
51
+ }
52
+
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...";
58
+
59
+ // Add focus event to change the placeholder
60
+ searchInput.addEventListener("focus", () => {
61
+ searchInput.placeholder = strings.search || "Type in to search...";
62
+ });
63
+
64
+ // Add blur event to revert the placeholder when not focused
65
+ searchInput.addEventListener("blur", () => {
66
+ searchInput.placeholder = strings.placeholder || "Select an option...";
67
+ });
68
+
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
+ }
226
+
227
+ optgroupItem.appendChild(nestedOptionsList);
228
+ nestedList.appendChild(optgroupItem);
229
+ }
230
+ } 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
+ }
256
+
257
+ // Check it
258
+ if (option.selected && option.hasAttribute('selected')) {
259
+ optionCheckbox.checked = true;
260
+ updateCounter(originalSelect);
261
+ updateCustomTextInput(originalSelect);
262
+ }
263
+ //
264
+
265
+ optionLabel.appendChild(optionCheckbox);
266
+ optionLabel.appendChild(optionLabelText);
267
+
268
+ optionItem.appendChild(optionLabel);
269
+
270
+ nestedList.appendChild(optionItem);
271
+ }
272
+ }
273
+
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();
280
+
281
+ // Append the fragment to the DOM once all changes are made
282
+ nestedList.appendChild(fragment);
283
+ }
284
+
285
+ function toggleAll(originalSelect, toggleAllCheckbox) {
286
+ const isChecked = toggleAllCheckbox.checked;
287
+ const optionCheckboxes = nestedList.querySelectorAll(
288
+ 'input[type="checkbox"]'
289
+ );
290
+
291
+ optionCheckboxes.forEach((checkbox) => {
292
+ checkbox.checked = isChecked;
293
+ updateOriginalOptionState(originalSelect, checkbox);
294
+ });
295
+ }
296
+
297
+ function uncheckAll(originalSelect) {
298
+ const optionCheckboxes = nestedList.querySelectorAll(
299
+ 'input[type="checkbox"]'
300
+ );
301
+
302
+ optionCheckboxes.forEach((checkbox) => {
303
+ checkbox.checked = false;
304
+ updateOriginalOptionState(originalSelect, checkbox);
305
+ });
306
+
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;
313
+ }
314
+ }
315
+
316
+ function toggleOption(checkbox) {
317
+ if (originalSelect.multiple) {
318
+ updateOriginalOptionState(originalSelect, checkbox);
319
+ } 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
+ }
361
+ }
362
+
363
+ updateOriginalOptionState(originalSelect, optgroupCheckbox);
364
+ }
365
+
366
+ function attachOptionCheckboxListeners() {
367
+ const optionCheckboxes = nestedList.querySelectorAll(
368
+ '.tail--nested-dropdown-item input[type="checkbox"]'
369
+ );
370
+
371
+ optionCheckboxes.forEach((checkbox) => {
372
+ checkbox.addEventListener("change", () =>
373
+ toggleOption(checkbox)
374
+ );
375
+ });
376
+ }
377
+
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;
395
+ }
396
+
397
+ // Trigger change event for the original select
398
+ const event = new Event("change", { bubbles: true });
399
+ originalSelect.dispatchEvent(event);
400
+ }
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
+ }
414
+ } 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
+ );
428
+ }
429
+ }
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++;
440
+ }
441
+
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");
449
+
450
+ if (counterElement) {
451
+ counterElement.textContent = count;
452
+ }
453
+ }
454
+ }
455
+ }
456
+
457
+ function filterOptions(originalSelect, searchInput) {
458
+ const searchTerm = searchInput.value.trim().toLowerCase();
459
+ const optionItems = nestedList.querySelectorAll("div");
460
+
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");
467
+
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
+ });
475
+
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
+ });
496
+ }
497
+
498
+ function updateSelectedOptionsList(
499
+ selectedOptionsList,
500
+ selectedOptions
501
+ ) {
502
+ // Clear existing list items
503
+ selectedOptionsList.innerHTML = "";
504
+
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
+ });
511
+ }
512
+
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
+ });
519
+
520
+
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
527
+ }
528
+ } 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
+ );
542
+ }
543
+ }
544
+ }
545
+
546
+
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
+
557
+ if (counterElement) {
558
+ // Get the count of selected options
559
+ const count = Array.from(originalSelect.options).filter(
560
+ (opt) => opt.selected
561
+ ).length;
562
+
563
+ // Update the counter element
564
+ counterElement.textContent = count;
565
+ }
566
+ }
567
+ }
568
+
569
+ function toggleDropdownVisibility() {
570
+ nestedList.style.display = "block";
571
+ customDropdown.setAttribute("aria-expanded", "true");
572
+ }
573
+
574
+ function hideDropdown() {
575
+ nestedList.style.display = "none";
576
+ customDropdown.setAttribute("aria-expanded", "false");
577
+ }
578
+
579
+ function handleClickOutside(event) {
580
+ if (!customDropdown.contains(event.target)) {
581
+ hideDropdown();
582
+ }
583
+ }
584
+
585
+ function handleKeyDown(event) {
586
+ if (event.key === "Escape") {
587
+ hideDropdown();
588
+ }
589
+ }
590
+
591
+ // Show the dropdown when the input field is focused
592
+ searchInput.addEventListener("focus", toggleDropdownVisibility);
593
+
594
+ // Hide the dropdown when clicking outside of it
595
+ document.addEventListener("click", handleClickOutside);
596
+
597
+ // Hide the dropdown when pressing the ESC key
598
+ document.addEventListener("keydown", handleKeyDown);
599
+
600
+ buildNestedList();
601
+ });
602
+ }
603
+ };
@@ -0,0 +1,11 @@
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>
10
+ */
11
+ const tail={select:function(selector,options={}){const defaultOptions={multiTags:!1,multiCounter:!0,theme:"light",classNames:"tail-default",strings:{all:"All",none:"None",placeholder:"Select an option...",search:"Type in to search..."}},opts={...defaultOptions,...options},{multiTags:multiTags,multiCounter:multiCounter,theme:theme,classNames:classNames,strings:strings}=opts,originalSelects=document.querySelectorAll(selector);originalSelects.forEach(originalSelect=>{originalSelect.style.display="none";const customDropdown=document.createElement("div");customDropdown.classList.add("tail-select"),customDropdown.classList.add(originalSelect.id),customDropdown.classList.add(opts.classNames),customDropdown.dataset.theme=`tail-theme--${opts.theme}`,originalSelect.multiple?customDropdown.classList.add("tail--multiple"):customDropdown.classList.add("tail--single");const searchInput=document.createElement("input");searchInput.type="text",searchInput.classList.add("tail--search"),searchInput.placeholder=strings.placeholder||"Select an option...",searchInput.addEventListener("focus",()=>{searchInput.placeholder=strings.search||"Type in to search..."}),searchInput.addEventListener("blur",()=>{searchInput.placeholder=strings.placeholder||"Select an option..."}),searchInput.addEventListener("input",()=>filterOptions(originalSelect,searchInput));const tailFloatingToolbar=document.createElement("div");tailFloatingToolbar.classList.add("tail--toolbar");const toggleAllCheckbox=document.createElement("input");toggleAllCheckbox.type="checkbox",toggleAllCheckbox.value=strings.all||"All",toggleAllCheckbox.addEventListener("change",()=>toggleAll(originalSelect,toggleAllCheckbox));const toggleAllLabel=document.createElement("label");toggleAllLabel.textContent=strings.all||"All",toggleAllLabel.classList.add("all"),toggleAllLabel.appendChild(toggleAllCheckbox);const uncheckAllButton=document.createElement("button");if(uncheckAllButton.type="button",uncheckAllButton.textContent=strings.none||"None",uncheckAllButton.classList.add("uncheck"),uncheckAllButton.addEventListener("click",()=>uncheckAll(originalSelect)),opts.multiCounter){const counter=document.createElement("span");counter.textContent="0",counter.classList.add("tail--counter"),customDropdown.appendChild(counter)}const nestedList=document.createElement("div");nestedList.classList.add("tail--nested-dropdown"),nestedList.style.display="none",customDropdown.appendChild(searchInput),customDropdown.appendChild(tailFloatingToolbar),customDropdown.appendChild(nestedList),tailFloatingToolbar.appendChild(toggleAllLabel),tailFloatingToolbar.appendChild(uncheckAllButton),originalSelect.insertAdjacentElement("afterend",customDropdown);const selectedOptionsList=document.createElement("ul");function buildNestedList(){const fragment=document.createDocumentFragment(),optgroups=originalSelect.getElementsByTagName("optgroup");if(optgroups.length>0)for(let i=0;i<optgroups.length;i++){const optgroup=optgroups[i],optgroupItem=document.createElement("div");optgroupItem.classList.add("tail--optgroup");const optgroupLabel=document.createElement("label"),optgroupCheckbox=document.createElement("input");optgroupCheckbox.type="checkbox",optgroupCheckbox.value=optgroup.label,optgroupCheckbox.addEventListener("change",()=>toggleOptgroup(optgroupCheckbox)),optgroupLabel.appendChild(optgroupCheckbox);const optgroupLabelText=document.createElement("span");optgroupLabelText.textContent=optgroup.label,optgroupLabelText.classList.add("tail--optgroup-label"),optgroupLabel.appendChild(optgroupLabelText),optgroupItem.appendChild(optgroupLabel);const nestedOptionsList=document.createElement("div");nestedOptionsList.setAttribute("role","listbox"),nestedOptionsList.classList.add("tail--nested-dropdown-list");const options=optgroup.getElementsByTagName("option");for(let j=0;j<options.length;j++){const option=options[j],optionItem=document.createElement("div");optionItem.classList.add("tail--nested-dropdown-item");const optionCheckbox=document.createElement("input");optionCheckbox.type="checkbox",optionCheckbox.value=option.textContent;const optionLabel=document.createElement("label"),optionLabelText=document.createElement("span");optionLabelText.textContent=option.textContent,option.dataset.description&&(optionLabelText.innerHTML+=`<small>${option.dataset.description}</small>`),option.selected&&option.hasAttribute("selected")&&(optionCheckbox.checked=!0,updateCounter(originalSelect),updateCustomTextInput(originalSelect)),optionLabel.appendChild(optionCheckbox),optionLabel.appendChild(optionLabelText),optionItem.appendChild(optionLabel),nestedOptionsList.appendChild(optionItem)}optgroupItem.appendChild(nestedOptionsList),nestedList.appendChild(optgroupItem)}else{const options=originalSelect.getElementsByTagName("option");for(let j=0;j<options.length;j++){const option=options[j],optionItem=document.createElement("div");optionItem.classList.add("tail--nested-dropdown-item");const optionCheckbox=document.createElement("input");optionCheckbox.type="checkbox",optionCheckbox.value=option.textContent;const optionLabel=document.createElement("label"),optionLabelText=document.createElement("span");optionLabelText.textContent=option.textContent,option.dataset.description&&(optionLabelText.innerHTML+=`<small>${option.dataset.description}</small>`),option.selected&&option.hasAttribute("selected")&&(optionCheckbox.checked=!0,updateCounter(originalSelect),updateCustomTextInput(originalSelect)),optionLabel.appendChild(optionCheckbox),optionLabel.appendChild(optionLabelText),optionItem.appendChild(optionLabel),nestedList.appendChild(optionItem)}}customDropdown.setAttribute("role","combobox"),customDropdown.setAttribute("aria-haspopup","true"),customDropdown.setAttribute("aria-expanded","false"),attachOptionCheckboxListeners(),nestedList.appendChild(fragment)}function toggleAll(originalSelect,toggleAllCheckbox){const isChecked=toggleAllCheckbox.checked,optionCheckboxes=nestedList.querySelectorAll('input[type="checkbox"]');optionCheckboxes.forEach(checkbox=>{checkbox.checked=isChecked,updateOriginalOptionState(originalSelect,checkbox)})}function uncheckAll(originalSelect){const optionCheckboxes=nestedList.querySelectorAll('input[type="checkbox"]');optionCheckboxes.forEach(checkbox=>{checkbox.checked=!1,updateOriginalOptionState(originalSelect,checkbox)});const originalOptions=originalSelect.getElementsByTagName("option");for(let i=0;i<originalOptions.length;i++)originalOptions[i].selected=!1}function toggleOption(checkbox){if(originalSelect.multiple)updateOriginalOptionState(originalSelect,checkbox);else{const optionCheckboxes=nestedList.querySelectorAll('.tail--nested-dropdown-item input[type="checkbox"]');optionCheckboxes.forEach(cb=>cb.checked=!1),checkbox.checked=!0,updateOriginalOptionState(originalSelect,checkbox)}}function toggleOptgroup(optgroupCheckbox){const isChecked=optgroupCheckbox.checked,nestedOptionsList=optgroupCheckbox.closest(".tail--optgroup").querySelector(".tail--nested-dropdown-list"),optionCheckboxes=nestedOptionsList.querySelectorAll('input[type="checkbox"]');if(optionCheckboxes.forEach(checkbox=>{checkbox.checked=isChecked,toggleOption(checkbox)}),!originalSelect.multiple){const customDropdown=originalSelect.closest(".tail-select");if(customDropdown){const otherOptgroupCheckboxes=customDropdown.querySelectorAll('.tail--nested-dropdown-item input[type="checkbox"]');otherOptgroupCheckboxes.forEach(cb=>{cb!==optgroupCheckbox&&(cb.checked=!1,updateOriginalOptionState(originalSelect,cb))})}}updateOriginalOptionState(originalSelect,optgroupCheckbox)}function attachOptionCheckboxListeners(){const optionCheckboxes=nestedList.querySelectorAll('.tail--nested-dropdown-item input[type="checkbox"]');optionCheckboxes.forEach(checkbox=>{checkbox.addEventListener("change",()=>toggleOption(checkbox))})}function updateOriginalOptionState(originalSelect,checkbox,customDropdown){const optionValue=checkbox.value,option=Array.from(originalSelect.options).find(opt=>opt.value===optionValue||opt.textContent===optionValue);if(option){checkbox.checked?option.selected=!0:option.selected=!1;const event=new Event("change",{bubbles:!0});originalSelect.dispatchEvent(event)}const selectedOptions=Array.from(originalSelect.options).filter(opt=>opt.selected);originalSelect.multiple?searchInput.value=selectedOptions.map(opt=>opt.textContent).join(", "):selectedOptions.length>0&&searchInput?searchInput.value=selectedOptions[0].textContent:searchInput.value="",opts.multiTags&&originalSelect.multiple&&updateSelectedOptionsList(selectedOptionsList,selectedOptions);const selectedValues=selectedOptions.map(opt=>opt.value);for(var options=originalSelect.options,count=0,i=0;i<options.length;i++)options[i].selected&&count++;if(opts.multiCounter){let customId=originalSelect.id;if(customId){let counterElement=document.querySelector(`.${customId}`).querySelector(".tail--counter");counterElement&&(counterElement.textContent=count)}}}function filterOptions(originalSelect,searchInput){const searchTerm=searchInput.value.trim().toLowerCase(),optionItems=nestedList.querySelectorAll("div");optionItems.forEach(optionItem=>{const optionCheckbox=optionItem.querySelector('input[type="checkbox"]'),optionLabel=optionCheckbox.nextElementSibling.textContent.toLowerCase(),optgroupItem=optionItem.closest("div");optionCheckbox.style.display=optionLabel.includes(searchTerm)?"inline-block":"none"}),optionItems.forEach(optionItem=>{const optionCheckbox=optionItem.querySelector('input[type="checkbox"]'),optgroupItem=optionItem.closest("div"),nestedCheckboxes=optionItem.querySelectorAll('div input[type="checkbox"]:not([style="display: none;"])'),hasVisibleNestedCheckboxes=nestedCheckboxes.length>0;optgroupItem.style.display="inline-block"===optionCheckbox.style.display||hasVisibleNestedCheckboxes?"block":"none"})}function updateSelectedOptionsList(selectedOptionsList,selectedOptions){selectedOptionsList.innerHTML="",selectedOptions.forEach(opt=>{const listItem=document.createElement("li");listItem.textContent=opt.textContent,selectedOptionsList.appendChild(listItem)})}function updateCustomTextInput(originalSelect){const selectedOptions=Array.from(originalSelect.options).filter(opt=>opt.selected&&opt.hasAttribute("selected"));originalSelect.multiple?searchInput.value=selectedOptions.map(opt=>opt.textContent).join(", "):selectedOptions.length>0&&searchInput?searchInput.value=selectedOptions[0].textContent:searchInput.value="",opts.multiTags&&originalSelect.multiple&&updateSelectedOptionsList(selectedOptionsList,selectedOptions)}function updateCounter(originalSelect){let customId=originalSelect.id;if(customId){let counterElement=document.querySelector(`.${customId}`).querySelector(".tail--counter");if(counterElement){const count=Array.from(originalSelect.options).filter(opt=>opt.selected).length;counterElement.textContent=count}}}function toggleDropdownVisibility(){nestedList.style.display="block",customDropdown.setAttribute("aria-expanded","true")}function hideDropdown(){nestedList.style.display="none",customDropdown.setAttribute("aria-expanded","false")}function handleClickOutside(event){customDropdown.contains(event.target)||hideDropdown()}function handleKeyDown(event){"Escape"===event.key&&hideDropdown()}selectedOptionsList.classList.add("tail--selected-options-list"),opts.multiTags&&originalSelect.multiple&&customDropdown.insertAdjacentElement("afterend",selectedOptionsList),searchInput.addEventListener("focus",toggleDropdownVisibility),document.addEventListener("click",handleClickOutside),document.addEventListener("keydown",handleKeyDown),buildNestedList()})}};
@@ -0,0 +1,254 @@
1
+ :root {
2
+ --option-size: 14px;
3
+
4
+ --tail-select--text: #000000;
5
+ --tail-select--meta: #d1d5db;
6
+ --tail-select--muted: #b3b3b3;
7
+ --tail-select--background: #ffffff;
8
+
9
+ --tail-select--accent: #3742fa;
10
+
11
+ --tail-select--selection: rgba(0, 0, 0, 0.05);
12
+ --tail-select--selection-text: #ffffff;
13
+ --tail-select--selection-muted: rgb(147 197 253 / 1);
14
+ }
15
+
16
+ [data-theme="tail-theme--light"] {
17
+ color-scheme: light;
18
+ }
19
+
20
+ [data-theme="tail-theme--dark"] {
21
+ color-scheme: dark;
22
+
23
+ --tail-select--text: #ffffff;
24
+ --tail-select--meta: #30343b;
25
+ --tail-select--muted: #4d4d4d;
26
+ --tail-select--background: #191b1f;
27
+
28
+ --tail-select--accent: #3742fa;
29
+
30
+ --tail-select--selection: rgba(0, 0, 0, 0.15);
31
+ --tail-select--selection-text: #ffffff;
32
+ --tail-select--selection-muted: #353b48;
33
+ }
34
+
35
+ .tail-select * {
36
+ box-sizing: border-box;
37
+ }
38
+
39
+ .tail-select,
40
+ .tail-select.tail--single,
41
+ .tail-select.tail--multiple {
42
+ position: relative;
43
+ display: flex;
44
+ align-items: center;
45
+ user-select: none;
46
+
47
+ width: fit-content;
48
+ height: fit-content;
49
+ font-size: 14px;
50
+ line-height: normal;
51
+ font-family: inherit;
52
+
53
+ cursor: default;
54
+ color: var(--tail-select--text);
55
+ padding: 6px 6px 6px 6px;
56
+ text-align: left;
57
+ background-color: var(--tail-select--background);
58
+ border: 1px solid var(--tail-select--meta);
59
+ border-radius: 3px;
60
+ box-shadow: 0 1px 1px 1px rgba(0, 0, 0, 0.05);
61
+
62
+ transition: border 0.4s ease-out;
63
+ }
64
+ .tail-select:hover {
65
+ border-color: var(--tail-select--accent);
66
+ }
67
+
68
+ .tail-select .tail--search,
69
+ .tail-select input[type="text"].tail--search {
70
+ font-family: inherit;
71
+ color: var(--tail-select--text);
72
+ border: 0 none;
73
+ border-radius: initial;
74
+ border-width: 0;
75
+ border-color: transparent;
76
+ background-color: transparent;
77
+ display: inline-block;
78
+ width: 100%;
79
+ outline: 0;
80
+ font-size: 13px;
81
+ margin: 0;
82
+ padding: 2px 72px 2px 2px;
83
+ cursor: text;
84
+ }
85
+ .tail-select .tail--search:hover,
86
+ .tail-select input[type="text"].tail--search:hover {
87
+ border: 0 none;
88
+ }
89
+
90
+ .tail-select.tail--single .tail--search,
91
+ .tail-select.tail--single input[type="text"].tail--search {
92
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='%23808080' d='M5.7 9.71a1 1 0 0 0 0 1.41l4.9 4.9a2 2 0 0 0 2.83 0l4.89-4.9a1 1 0 1 0-1.42-1.41l-4.18 4.18a1 1 0 0 1-1.42 0L7.12 9.71a1 1 0 0 0-1.41 0Z'/%3E%3C/svg%3E");
93
+ background-position: 100%, 100% 0;
94
+ background-size: 1.5em 1.5em;
95
+ background-repeat: no-repeat;
96
+ }
97
+ .tail-select.tail--single .tail--search:focus,
98
+ .tail-select.tail--single input[type="text"].tail--search:focus {
99
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='%23808080' d='M18.3 15.29a1 1 0 0 0 0-1.41l-4.9-4.9a2 2 0 0 0-2.83 0l-4.89 4.9a1 1 0 1 0 1.42 1.41l4.18-4.18a1 1 0 0 1 1.42 0l4.18 4.18a1 1 0 0 0 1.41 0Z'/%3E%3C/svg%3E");
100
+ }
101
+ .tail-select.tail--multiple .tail--search,
102
+ .tail-select.tail--multiple input[type="text"].tail--search {
103
+ width: calc(100% - 32px);
104
+ }
105
+
106
+ .tail-select .tail--toolbar {
107
+ position: absolute;
108
+ top: 50%;
109
+ right: 6px;
110
+ transform: translateY(-50%);
111
+ }
112
+
113
+ .tail-select .tail--counter {
114
+ font-size: 12px;
115
+ text-align: center;
116
+ color: var(--tail-select--text);
117
+ font-weight: 700;
118
+ width: 24px;
119
+ display: inline-block;
120
+ border-right: 1px solid var(--tail-select--muted);
121
+ padding: 0 0px 0 0;
122
+ margin: 0 8px 0 0;
123
+ }
124
+
125
+ .tail-select label.all,
126
+ .tail-select button.uncheck {
127
+ width: auto;
128
+ height: auto;
129
+ margin: 0 2px;
130
+ padding: 2px 6px;
131
+ display: inline-block;
132
+ font-family: inherit;
133
+ font-size: 10px;
134
+ line-height: 14px;
135
+ text-shadow: none;
136
+ letter-spacing: 0;
137
+ text-transform: none;
138
+ vertical-align: top;
139
+ border-width: 1px;
140
+ border-style: solid;
141
+ border-color: transparent;
142
+ border-radius: 3px;
143
+ box-shadow: none;
144
+
145
+ color: var(--tail-select--text);
146
+ border-color: var(--tail-select--meta);
147
+ background-color: transparent;
148
+ backdrop-filter: blur(2px);
149
+ }
150
+ .tail-select label.all input[type="checkbox"] {
151
+ display: none;
152
+ }
153
+ .tail-select label.all:hover,
154
+ .tail-select button.uncheck:hover {
155
+ color: var(--tail-select--text);
156
+ border-color: var(--tail-select--text);
157
+ }
158
+
159
+
160
+ .tail-select > .tail--nested-dropdown {
161
+ position: absolute;
162
+ top: 100%;
163
+ left: 0;
164
+ z-index: 1;
165
+ background-color: var(--tail-select--background);
166
+ border: 1px solid var(--tail-select--meta);
167
+ border-radius: 3px;
168
+ box-shadow: 0 1px 1px 1px rgba(0, 0, 0, 0.05);
169
+
170
+ padding: 8px 0;
171
+ margin: 6px 0 0 0;
172
+ max-height: 200px;
173
+ overflow: auto;
174
+ width: 100%;
175
+ }
176
+
177
+ .tail-select .tail--optgroup {
178
+ padding: 0;
179
+ cursor: default;
180
+ }
181
+ .tail-select .tail--nested-dropdown-list {
182
+ padding: 0;
183
+ }
184
+ .tail-select .tail--nested-dropdown label {
185
+ display: flex;
186
+ padding: 6px 12px;
187
+ align-items: start;
188
+ }
189
+ .tail-select .tail--nested-dropdown label input[type="checkbox"] {
190
+ display: block;
191
+ }
192
+
193
+ .tail-select .tail--nested-dropdown-item {
194
+ color: var(--tail-select--text);
195
+ list-style: none;
196
+ padding: 0;
197
+ display: block;
198
+ cursor: default;
199
+ }
200
+ .tail-select .tail--nested-dropdown-item span {
201
+ font-size: 14px;
202
+ line-height: 1.5;
203
+ }
204
+ .tail-select .tail--nested-dropdown-item span small {
205
+ display: block;
206
+ font-size: 12px;
207
+ }
208
+
209
+ .tail-select .tail--nested-dropdown-item:hover {
210
+ background-color: var(--tail-select--selection);
211
+ }
212
+
213
+
214
+ .tail--optgroup-label {
215
+ font-weight: 700;
216
+ }
217
+
218
+ .tail-select input[type="checkbox"] {
219
+ height: var(--option-size);
220
+ width: var(--option-size);
221
+ padding: 0;
222
+ margin: 4px 0.5rem 0 0;
223
+
224
+ accent-color: #3742fa;
225
+ }
226
+
227
+ .tail-select input[type="checkbox"]:focus {
228
+ outline: 3px solid rgb(0 87 255 / 50%);
229
+ outline: 3px solid rgb(147 197 253 / 1);
230
+ outline-offset: 0px;
231
+ }
232
+
233
+ .tail-select.tail--single .tail--counter,
234
+ .tail--single .tail--optgroup > label > input[type="checkbox"],
235
+ .tail--single .tail--toolbar {
236
+ display: none;
237
+ }
238
+
239
+ .tail--selected-options-list {
240
+ padding: 0;
241
+ margin: 0;
242
+ display: flex;
243
+ gap: 2px;
244
+ }
245
+ .tail--selected-options-list li {
246
+ list-style: none;
247
+ padding: 8px 12px;
248
+ font-size: 13px;
249
+ line-height: 1;
250
+ margin: 0;
251
+ border-radius: 48px;
252
+ background-color: var(--tail-select--accent);
253
+ color: var(--tail-select--selection-text);
254
+ }
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tail-select-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Gajendra Jena
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-06-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jquery-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sass-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: uglifier
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Rails gem for easy tail-select-rails (v1.0.2) integration. See https://getbutterfly.com/tail-select/
70
+ for more information on how to use tail.select
71
+ email: gaju.mca@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - Readme.md
78
+ - lib/tail_select_rails.rb
79
+ - vendor/assets/javascripts/tail-select-rails.js
80
+ - vendor/assets/javascripts/tail-select-rails.min.js
81
+ - vendor/assets/stylesheets/tail-select-rails.css
82
+ homepage: http://github.com/gajendrajena/tail-select-rails
83
+ licenses: []
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.2.3
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Rails gem for easy tail-select-rails integration.
104
+ test_files: []