x
1
2
3
4
5
<select name="fruits[]" id="fruits" multiple="multiple" data-controller="combobox"><option value="">Select an option...</option><option value="Apple">Apple</option>
<option value="Banana">Banana</option>
<option value="Blueberry">Blueberry</option>
<option value="Grapes">Grapes</option>
<option value="Pineaple">Pineaple</option></select>
1
<%= select_tag :fruits, fruit_options, include_blank: "Select an option...", multiple: true, data: { controller: "combobox" } %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
.ts-control {
align-items: center;
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--rounded-md);
min-block-size: var(--size-10);
color: var(--color-text);
display: inline-flex;
font-size: var(--text-sm);
gap: var(--size-1);
line-height: inherit;
padding: var(--size-2) var(--size-3);
> input {
color: inherit; font-size: inherit;
}
}
.ts-dropdown {
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--rounded-md);
box-shadow: var(--shadow-md);
color: var(--color-text);
font-size: var(--text-sm);
line-height: inherit;
.ts-dropdown-content:not(:has(.optgroup)) {
padding: var(--size-1);
}
.optgroup:not(:first-child) {
border-block-start-width: var(--border);
}
.optgroup {
padding: var(--size-1);
}
.optgroup-header {
background-color: inherit;
color: var(--color-text-subtle);
font-size: var(--text-xs);
padding: var(--size-1_5) var(--size-2);
}
.create {
padding: var(--size-1_5) var(--size-2);
}
.option {
border: 1px solid transparent;
border-radius: var(--rounded-md);
padding: var(--size-1_5) var(--size-2);
}
.active {
background-color: var(--color-secondary);
color: inherit !important;
}
.highlight {
background-color: transparent !important;
}
.spinner {
margin: var(--size-1_5) 0 0;
}
.spinner::after {
border-block-color: var(--color-border-dark);
}
}
.ts-wrapper.single .ts-control {
background-color: var(--color-bg) !important;
background-image: url("select-arrow.svg") !important;
background-position: center right var(--size-2) !important;
background-repeat: no-repeat !important;
background-size: var(--size-4) auto !important;
}
.ts-wrapper.multi .ts-control > .item {
background: var(--color-secondary);
border-radius: var(--rounded-md);
color: inherit;
line-height: var(--leading-tight);
margin: 0;
}
.disabled .ts-control {
opacity: var(--opacity-50);
}
.disabled .ts-control * {
cursor: not-allowed !important;
}
.invalid .ts-control {
border-color: var(--color-negative);
}
[data-controller*="combobox"] {
clip: rect(0 0 0 0);
position: absolute;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { Controller } from "@hotwired/stimulus"
import { get } from "https://cdn.skypack.dev/@rails/request.js@0.0.11?min"
import TomSelect from "https://cdn.skypack.dev/tom-select@2.3.1?min"
export default class extends Controller {
static values = { url: String, optionCreate: { type: String, default: "Add" }, noResults: { type: String, default: "No results found" } }
initialize() {
this.load = this.load.bind(this)
}
connect() {
if (this.element.nodeName === "INPUT") {
this.tomSelect = new TomSelect(this.element, this.#inputSettings)
} else {
this.tomSelect = new TomSelect(this.element, this.#selectSettings)
}
}
disconnect() {
this.tomSelect.destroy()
}
async load(query, callback) {
const response = await get(this.urlValue, { responseKind: "json", query: { q: query } })
const jsonResponse = await response.json
callback(jsonResponse)
}
get #inputSettings() {
return { render: this.#render, load: this.#loadSetting, persist: false, createOnBlur: true, create: true }
}
get #selectSettings() {
return { render: this.#render, load: this.#loadSetting }
}
get #render() {
return {
option_create: (data, escape) => {
return `<div class="create">${this.optionCreateValue} <b>${escape(data.input)}</b>...</div>`
},
no_results: () => {
return `<div class="no-results">${this.noResultsValue}</div>`
}
}
}
get #loadSetting() {
return this.hasUrlValue && this.load
}
}