tail-select-rails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []