advanced_select 0.1.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.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
4
+ load "lib/tasks/advanced_select/tasks.rake"
@@ -0,0 +1,2 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../stylesheets .css
@@ -0,0 +1,220 @@
1
+ .ui-advanced-select {
2
+ box-sizing: border-box;
3
+ position: relative;
4
+ }
5
+
6
+ .ui-advanced-select * {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ .ui-advanced-select .hidden {
11
+ display: none !important;
12
+ }
13
+
14
+ .ui-advanced-select-trigger {
15
+ align-items: center;
16
+ background: #ffffff;
17
+ border: 1px solid #d1d5db;
18
+ border-radius: 0.75rem;
19
+ color: #374151;
20
+ cursor: pointer;
21
+ display: flex;
22
+ font: inherit;
23
+ font-size: 0.875rem;
24
+ gap: 0.5rem;
25
+ justify-content: space-between;
26
+ line-height: 1.25rem;
27
+ min-height: 2.5rem;
28
+ padding: 0.5rem 0.75rem;
29
+ text-align: left;
30
+ width: 100%;
31
+ }
32
+
33
+ .ui-advanced-select-trigger:focus {
34
+ outline: 2px solid #4f46e5;
35
+ outline-offset: 2px;
36
+ }
37
+
38
+ .ui-advanced-select-summary {
39
+ display: flex;
40
+ flex: 1 1 auto;
41
+ flex-wrap: wrap;
42
+ gap: 0.375rem;
43
+ min-width: 0;
44
+ }
45
+
46
+ .ui-advanced-select-placeholder {
47
+ color: #6b7280;
48
+ overflow: hidden;
49
+ text-overflow: ellipsis;
50
+ white-space: nowrap;
51
+ }
52
+
53
+ .ui-advanced-select-value {
54
+ color: #374151;
55
+ overflow: hidden;
56
+ text-overflow: ellipsis;
57
+ white-space: nowrap;
58
+ }
59
+
60
+ .ui-advanced-select-token {
61
+ background: #eef2ff;
62
+ border: 1px solid #e0e7ff;
63
+ border-radius: 0.5rem;
64
+ color: #4338ca;
65
+ font-size: 0.75rem;
66
+ font-weight: 500;
67
+ line-height: 1rem;
68
+ max-width: 100%;
69
+ overflow: hidden;
70
+ padding: 0.25rem 0.5rem;
71
+ text-overflow: ellipsis;
72
+ white-space: nowrap;
73
+ }
74
+
75
+ .ui-advanced-select-caret,
76
+ .ui-advanced-select-clear {
77
+ color: #9ca3af;
78
+ flex-shrink: 0;
79
+ }
80
+
81
+ .ui-advanced-select-clear {
82
+ font-size: 1.125rem;
83
+ line-height: 1.5rem;
84
+ }
85
+
86
+ .ui-advanced-select-dropdown {
87
+ background: #ffffff;
88
+ border: 1px solid #e5e7eb;
89
+ border-radius: 0.75rem;
90
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
91
+ box-sizing: border-box;
92
+ color: #374151;
93
+ font-size: 0.875rem;
94
+ left: 0;
95
+ line-height: 1.25rem;
96
+ margin-top: 0.25rem;
97
+ padding: 0.5rem;
98
+ position: absolute;
99
+ right: 0;
100
+ width: 100%;
101
+ z-index: 20;
102
+ }
103
+
104
+ .ui-advanced-select-search {
105
+ border: 1px solid #e5e7eb;
106
+ border-radius: 0.5rem;
107
+ box-sizing: border-box;
108
+ color: #374151;
109
+ font: inherit;
110
+ font-size: 0.875rem;
111
+ line-height: 1.25rem;
112
+ margin-bottom: 0.5rem;
113
+ padding: 0.5rem 0.75rem;
114
+ width: 100%;
115
+ }
116
+
117
+ .ui-advanced-select-search:focus {
118
+ outline: 2px solid #4f46e5;
119
+ outline-offset: 1px;
120
+ }
121
+
122
+ .ui-advanced-select-options {
123
+ max-height: 14rem;
124
+ overflow-y: auto;
125
+ }
126
+
127
+ .ui-advanced-select-option {
128
+ align-items: center;
129
+ background: transparent;
130
+ border: 0;
131
+ border-radius: 0.5rem;
132
+ color: #374151;
133
+ cursor: pointer;
134
+ display: flex;
135
+ font: inherit;
136
+ gap: 0.5rem;
137
+ padding: 0.5rem 0.75rem;
138
+ text-align: left;
139
+ width: 100%;
140
+ }
141
+
142
+ .ui-advanced-select-option:hover,
143
+ .ui-advanced-select-option-active {
144
+ background: #f9fafb;
145
+ color: #111827;
146
+ }
147
+
148
+ .ui-advanced-select-option[aria-selected="true"],
149
+ .ui-advanced-select-option-active[aria-selected="true"] {
150
+ background: #eef2ff;
151
+ color: #4338ca;
152
+ }
153
+
154
+ .ui-advanced-select-option-check {
155
+ color: #4f46e5;
156
+ flex-shrink: 0;
157
+ width: 1rem;
158
+ }
159
+
160
+ .ui-advanced-select-option-content {
161
+ display: flex;
162
+ flex-direction: column;
163
+ min-width: 0;
164
+ }
165
+
166
+ .ui-advanced-select-option-description {
167
+ color: #9ca3af;
168
+ font-size: 0.75rem;
169
+ line-height: 1rem;
170
+ overflow: hidden;
171
+ text-overflow: ellipsis;
172
+ white-space: nowrap;
173
+ }
174
+
175
+ .ui-advanced-select-group-label {
176
+ color: #9ca3af;
177
+ font-size: 0.6875rem;
178
+ font-weight: 600;
179
+ letter-spacing: 0.12em;
180
+ padding: 0.5rem 0.75rem 0.25rem;
181
+ text-transform: uppercase;
182
+ }
183
+
184
+ .ui-advanced-select-add-option {
185
+ align-items: center;
186
+ background: transparent;
187
+ border: 1px dashed #c7d2fe;
188
+ border-radius: 0.5rem;
189
+ color: #4338ca;
190
+ cursor: pointer;
191
+ display: flex;
192
+ font: inherit;
193
+ font-size: 0.875rem;
194
+ font-weight: 500;
195
+ line-height: 1.25rem;
196
+ margin-top: 0.25rem;
197
+ padding: 0.5rem 0.75rem;
198
+ text-align: left;
199
+ width: 100%;
200
+ }
201
+
202
+ .ui-advanced-select-add-option:hover,
203
+ .ui-advanced-select-add-option.ui-advanced-select-option-active {
204
+ background: #eef2ff;
205
+ }
206
+
207
+ .ui-advanced-select-empty,
208
+ .ui-advanced-select-loading {
209
+ color: #9ca3af;
210
+ font-size: 0.875rem;
211
+ line-height: 1.25rem;
212
+ padding: 0.5rem 0.75rem;
213
+ }
214
+
215
+ .ui-advanced-select-error {
216
+ color: #ef4444;
217
+ font-size: 0.875rem;
218
+ line-height: 1.25rem;
219
+ padding: 0.5rem 0.75rem;
220
+ }
@@ -0,0 +1,379 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { Turbo } from "@hotwired/turbo-rails"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["hiddenFields", "trigger", "summary", "dropdown", "search", "options", "caret", "clear"]
6
+ static values = {
7
+ addMode: Boolean,
8
+ delay: { type: Number, default: 200 },
9
+ dependentFields: Object,
10
+ errorText: String,
11
+ inputId: String,
12
+ loadingText: String,
13
+ multiple: Boolean,
14
+ name: String,
15
+ placeholder: String,
16
+ searchable: Boolean,
17
+ selected: Array,
18
+ targetId: String,
19
+ url: String
20
+ }
21
+
22
+ connect() {
23
+ this.timer = null
24
+ this.requestSequence = 0
25
+ this.activeIndex = -1
26
+ this.placeholderClass = this.element.dataset.advancedSelectPlaceholderClass || "ui-advanced-select-placeholder"
27
+ this.valueClass = this.element.dataset.advancedSelectValueClass || "ui-advanced-select-value"
28
+ this.tokenClass = this.element.dataset.advancedSelectTokenClass || "ui-advanced-select-token"
29
+ this.loadingClass = this.element.dataset.advancedSelectLoadingClass || "ui-advanced-select-loading"
30
+ this.errorClass = this.element.dataset.advancedSelectErrorClass || "ui-advanced-select-error"
31
+ this.optionActiveClasses = this.classList(this.element.dataset.advancedSelectOptionActiveClass || "ui-advanced-select-option-active")
32
+ this.addOptionActiveClasses = this.classList(this.element.dataset.advancedSelectAddOptionActiveClass || "")
33
+ this.optionSelectedClasses = this.classList(this.element.dataset.advancedSelectOptionSelectedClass || "")
34
+ this.selectedValue = this.selectedValue.map((option) => ({
35
+ ...option,
36
+ id: option.id.toString(),
37
+ displayLabel: option.displayLabel || option.label
38
+ }))
39
+ this.close = this.close.bind(this)
40
+ }
41
+
42
+ disconnect() {
43
+ window.clearTimeout(this.timer)
44
+ document.removeEventListener("click", this.close)
45
+ }
46
+
47
+ toggle(event) {
48
+ event.preventDefault()
49
+
50
+ if (event.target === this.clearTarget) {
51
+ return
52
+ }
53
+
54
+ this.expanded ? this.close() : this.open()
55
+ }
56
+
57
+ open() {
58
+ this.dropdownTarget.classList.remove("hidden")
59
+ this.triggerTarget.setAttribute("aria-expanded", "true")
60
+ document.addEventListener("click", this.close)
61
+ this.activate(-1)
62
+ this.fetchOptions({ selected: true })
63
+
64
+ if (this.searchableValue) {
65
+ requestAnimationFrame(() => this.searchTarget.focus())
66
+ }
67
+ }
68
+
69
+ close(event) {
70
+ if (event && this.element.contains(event.target)) {
71
+ return
72
+ }
73
+
74
+ this.dropdownTarget.classList.add("hidden")
75
+ this.triggerTarget.setAttribute("aria-expanded", "false")
76
+ document.removeEventListener("click", this.close)
77
+ this.clearSearch()
78
+ this.activate(-1)
79
+ }
80
+
81
+ search() {
82
+ window.clearTimeout(this.timer)
83
+ this.renderLoading()
84
+ this.activate(-1)
85
+ this.timer = window.setTimeout(() => this.fetchOptions(), this.delayValue)
86
+ }
87
+
88
+ choose(event) {
89
+ event.preventDefault()
90
+ this.selectOption(event.params.value, event.params.label, event.params.submitValue, { displayLabel: event.params.displayLabel })
91
+ }
92
+
93
+ add(event) {
94
+ event.preventDefault()
95
+ this.addOption(event.params.value, event.params.label, event.params.submitValue, event.params.displayLabel)
96
+ }
97
+
98
+ activateOption(event) {
99
+ this.activate(this.optionElements.indexOf(event.currentTarget))
100
+ }
101
+
102
+ clear(event) {
103
+ event.preventDefault()
104
+ event.stopPropagation()
105
+ this.selectedValue = []
106
+ this.renderSelection()
107
+ this.close()
108
+ }
109
+
110
+ keydown(event) {
111
+ if (event.key === "ArrowDown") {
112
+ event.preventDefault()
113
+ this.activate(this.activeIndex + 1)
114
+ } else if (event.key === "ArrowUp") {
115
+ event.preventDefault()
116
+ this.activate(this.activeIndex < 0 ? this.optionElements.length - 1 : this.activeIndex - 1)
117
+ } else if (event.key === "Enter" && this.activeOption) {
118
+ event.preventDefault()
119
+ this.chooseActiveOption()
120
+ } else if (event.key === "Escape") {
121
+ this.close()
122
+ }
123
+ }
124
+
125
+ clearSearch() {
126
+ if (this.searchableValue) {
127
+ this.searchTarget.value = ""
128
+ }
129
+ }
130
+
131
+ fetchOptions({ selected = false } = {}) {
132
+ if (!this.urlValue) {
133
+ return
134
+ }
135
+
136
+ const requestSequence = this.requestSequence + 1
137
+ this.requestSequence = requestSequence
138
+ const url = new URL(this.urlValue, window.location.origin)
139
+ url.searchParams.set("target", this.targetIdValue)
140
+ url.searchParams.set("add_mode", this.addModeValue ? "1" : "0")
141
+ this.selectedValue.forEach((option) => url.searchParams.append("selected_ids[]", option.id))
142
+
143
+ if (selected && !this.multipleValue && this.selectedValue[0]) {
144
+ url.searchParams.set("selected_id", this.selectedValue[0].id)
145
+ } else if (this.searchableValue) {
146
+ url.searchParams.set("query", this.searchTarget.value)
147
+ }
148
+
149
+ Object.entries(this.dependentFieldsValue).forEach(([name, selector]) => {
150
+ const field = document.querySelector(selector)
151
+
152
+ if (field) {
153
+ url.searchParams.set(name, field.value)
154
+ }
155
+ })
156
+
157
+ fetch(url, { headers: { Accept: "text/vnd.turbo-stream.html" } })
158
+ .then((response) => {
159
+ if (!response.ok) {
160
+ throw new Error("Advanced select options request failed")
161
+ }
162
+
163
+ return response.text()
164
+ })
165
+ .then((html) => {
166
+ if (!this.expanded || requestSequence !== this.requestSequence) {
167
+ return
168
+ }
169
+
170
+ Turbo.renderStreamMessage(html)
171
+ requestAnimationFrame(() => {
172
+ if (!this.expanded || requestSequence !== this.requestSequence) {
173
+ return
174
+ }
175
+
176
+ this.currentOptionsTarget.setAttribute("aria-busy", "false")
177
+ this.renderOptionsState()
178
+ this.activate(-1)
179
+ })
180
+ })
181
+ .catch(() => {
182
+ if (!this.expanded || requestSequence !== this.requestSequence) {
183
+ return
184
+ }
185
+
186
+ this.renderError()
187
+ })
188
+ }
189
+
190
+ renderLoading() {
191
+ this.currentOptionsTarget.setAttribute("aria-busy", "true")
192
+ this.currentOptionsTarget.replaceChildren(this.textElement("div", this.loadingClass, this.loadingTextValue))
193
+ }
194
+
195
+ renderError() {
196
+ this.currentOptionsTarget.setAttribute("aria-busy", "false")
197
+ this.currentOptionsTarget.replaceChildren(this.textElement("div", this.errorClass, this.errorTextValue))
198
+ }
199
+
200
+ addOption(value, label, submitValue = value, displayLabel = label) {
201
+ this.selectOption(value, label, submitValue, { displayLabel, refreshOptions: false })
202
+
203
+ if (this.multipleValue) {
204
+ this.clearSearch()
205
+ this.fetchOptions()
206
+ }
207
+ }
208
+
209
+ selectOption(value, label, submitValue = value, { displayLabel = label, refreshOptions = this.multipleValue } = {}) {
210
+ value = value.toString()
211
+ submitValue = submitValue.toString()
212
+
213
+ if (this.multipleValue) {
214
+ if (this.selectedValue.some((option) => option.id === value)) {
215
+ this.selectedValue = this.selectedValue.filter((option) => option.id !== value)
216
+ } else {
217
+ this.selectedValue = this.selectedValue.concat({ id: value, value: submitValue, label, displayLabel })
218
+ }
219
+ } else {
220
+ this.selectedValue = [{ id: value, value: submitValue, label, displayLabel }]
221
+ }
222
+
223
+ this.renderSelection()
224
+ if (refreshOptions) {
225
+ this.fetchOptions()
226
+ }
227
+
228
+ if (!this.multipleValue) {
229
+ this.close()
230
+ }
231
+ }
232
+
233
+ renderSelection() {
234
+ this.hiddenFieldsTarget.replaceChildren(...this.hiddenFieldElements)
235
+ this.summaryTarget.replaceChildren(...this.selectionElements)
236
+ this.renderOptionsState()
237
+ this.caretTarget.classList.toggle("hidden", this.selectedValue.length > 0)
238
+ this.clearTarget.classList.toggle("hidden", this.selectedValue.length === 0)
239
+ }
240
+
241
+ renderOptionsState() {
242
+ const selectedIds = new Set(this.selectedValue.map((option) => option.id))
243
+
244
+ this.optionElements.forEach((option) => {
245
+ const selected = selectedIds.has(option.dataset.advancedSelectValueParam)
246
+ option.setAttribute("aria-selected", selected.toString())
247
+ this.toggleClasses(option, this.optionSelectedClasses, selected)
248
+
249
+ const check = option.querySelector("[data-advanced-select-option-check]")
250
+ if (check) {
251
+ check.textContent = selected ? "\u2713" : ""
252
+ }
253
+ })
254
+ }
255
+
256
+ chooseActiveOption() {
257
+ if (this.activeOption.hasAttribute("data-advanced-select-add-option")) {
258
+ this.addOption(
259
+ this.activeOption.dataset.advancedSelectValueParam,
260
+ this.activeOption.dataset.advancedSelectLabelParam,
261
+ this.activeOption.dataset.advancedSelectSubmitValueParam,
262
+ this.activeOption.dataset.advancedSelectDisplayLabelParam
263
+ )
264
+ } else {
265
+ this.selectOption(
266
+ this.activeOption.dataset.advancedSelectValueParam,
267
+ this.activeOption.dataset.advancedSelectLabelParam,
268
+ this.activeOption.dataset.advancedSelectSubmitValueParam || this.activeOption.dataset.advancedSelectValueParam,
269
+ { displayLabel: this.activeOption.dataset.advancedSelectDisplayLabelParam }
270
+ )
271
+ }
272
+ }
273
+
274
+ activate(index) {
275
+ this.optionElements.forEach((option) => {
276
+ this.removeClasses(option, this.optionActiveClasses)
277
+ this.removeClasses(option, this.addOptionActiveClasses)
278
+ })
279
+
280
+ if (index < 0 || this.optionElements.length === 0) {
281
+ this.activeIndex = -1
282
+ return
283
+ }
284
+
285
+ this.activeIndex = (index + this.optionElements.length) % this.optionElements.length
286
+ this.addClasses(this.activeOption, this.optionActiveClasses)
287
+ if (this.activeOption.hasAttribute("data-advanced-select-add-option")) {
288
+ this.addClasses(this.activeOption, this.addOptionActiveClasses)
289
+ }
290
+ this.activeOption.scrollIntoView({ block: "nearest" })
291
+ }
292
+
293
+ get optionElements() {
294
+ return Array.from(this.currentOptionsTarget.querySelectorAll("[data-advanced-select-option], [data-advanced-select-add-option]")).filter((option) => !option.classList.contains("hidden"))
295
+ }
296
+
297
+ get activeOption() {
298
+ return this.optionElements[this.activeIndex]
299
+ }
300
+
301
+ get currentOptionsTarget() {
302
+ return document.getElementById(this.targetIdValue) || this.optionsTarget
303
+ }
304
+
305
+ get hiddenFieldElements() {
306
+ const options = this.multipleValue ? this.selectedValue : [this.selectedValue[0]]
307
+
308
+ return options.map((option) => {
309
+ const input = document.createElement("input")
310
+ input.type = "hidden"
311
+ input.name = this.nameValue
312
+ input.value = option ? option.value || option.id : ""
313
+
314
+ if (!this.multipleValue) {
315
+ input.id = this.inputIdValue
316
+ }
317
+
318
+ return input
319
+ })
320
+ }
321
+
322
+ get selectionElements() {
323
+ if (this.selectedValue.length === 0) {
324
+ return [this.textElement("span", this.placeholderClass, this.placeholderValue)]
325
+ }
326
+
327
+ if (!this.multipleValue) {
328
+ return [this.textElement("span", this.valueClass, this.displayLabel(this.selectedValue[0]))]
329
+ }
330
+
331
+ const tokens = this.selectedValue.slice(0, 2).map((option) => this.textElement("span", this.tokenClass, this.displayLabel(option)))
332
+
333
+ if (this.selectedValue.length > 2) {
334
+ tokens.push(this.textElement("span", this.tokenClass, `& +${this.selectedValue.length - 2}`))
335
+ }
336
+
337
+ return tokens
338
+ }
339
+
340
+ displayLabel(option) {
341
+ return option.displayLabel || option.label
342
+ }
343
+
344
+ textElement(tagName, className, text) {
345
+ const element = document.createElement(tagName)
346
+ element.className = className
347
+ element.textContent = text
348
+
349
+ return element
350
+ }
351
+
352
+ classList(className) {
353
+ return className.trim().split(/\s+/).filter(Boolean)
354
+ }
355
+
356
+ addClasses(element, classNames) {
357
+ if (classNames.length > 0) {
358
+ element.classList.add(...classNames)
359
+ }
360
+ }
361
+
362
+ removeClasses(element, classNames) {
363
+ if (classNames.length > 0) {
364
+ element.classList.remove(...classNames)
365
+ }
366
+ }
367
+
368
+ toggleClasses(element, classNames, force) {
369
+ if (force) {
370
+ this.addClasses(element, classNames)
371
+ } else {
372
+ this.removeClasses(element, classNames)
373
+ }
374
+ }
375
+
376
+ get expanded() {
377
+ return this.triggerTarget.getAttribute("aria-expanded") === "true"
378
+ }
379
+ }
@@ -0,0 +1 @@
1
+ export { default as AdvancedSelectController } from "./advanced_select_controller"
@@ -0,0 +1,6 @@
1
+ <span class="<%= advanced_select_class(class_map, :option_content) %>">
2
+ <span><%= advanced_select_option_label(option) %></span>
3
+ <% if advanced_select_option_description(option).present? %>
4
+ <span class="<%= advanced_select_class(class_map, :option_description) %>"><%= advanced_select_option_description(option) %></span>
5
+ <% end %>
6
+ </span>
@@ -0,0 +1,18 @@
1
+ <% selected = advanced_select_option_selected?(option, selected_options) %>
2
+ <button type="button"
3
+ class="<%= advanced_select_class(class_map, :option, (:option_selected if selected)) %>"
4
+ role="option"
5
+ aria-selected="<%= selected %>"
6
+ data-advanced-select-option
7
+ data-action="mouseenter->advanced-select#activateOption mousedown->advanced-select#choose"
8
+ data-advanced-select-value-param="<%= option.fetch(:id) %>"
9
+ data-advanced-select-submit-value-param="<%= advanced_select_option_value(option) %>"
10
+ data-advanced-select-label-param="<%= advanced_select_option_label(option) %>"
11
+ data-advanced-select-display-label-param="<%= advanced_select_option_display_label(option) %>">
12
+ <span class="<%= advanced_select_class(class_map, :option_check) %>" data-advanced-select-option-check><% if selected %>&#10003;<% end %></span>
13
+ <% if option_content_partial.present? %>
14
+ <%= render partial: option_content_partial, locals: { option: option } %>
15
+ <% else %>
16
+ <%= render partial: "advanced_select/default_option_content", locals: { option: option, class_map: class_map } %>
17
+ <% end %>
18
+ </button>
@@ -0,0 +1,31 @@
1
+ <div id="<%= target_id %>"
2
+ class="<%= advanced_select_class(class_map, :options) %>"
3
+ data-advanced-select-target="options"
4
+ aria-busy="false"
5
+ <% if local_assigns[:multiple] %>aria-multiselectable="true"<% end %>
6
+ role="listbox">
7
+ <% advanced_select_option_groups(options).each do |group| %>
8
+ <% if group[:label].present? %>
9
+ <div class="<%= advanced_select_class(class_map, :group_label) %>"><%= group.fetch(:label) %></div>
10
+ <% end %>
11
+
12
+ <% group.fetch(:options).each do |option| %>
13
+ <%= render partial: "advanced_select/option", locals: { option: option, selected_options: selected_options, option_content_partial: option_content_partial, class_map: class_map } %>
14
+ <% end %>
15
+ <% end %>
16
+
17
+ <% if advanced_select_add_option?(options, selected_options, add_mode, query) %>
18
+ <button type="button"
19
+ class="<%= advanced_select_class(class_map, :add_option) %>"
20
+ data-advanced-select-add-option
21
+ data-action="mouseenter->advanced-select#activateOption mousedown->advanced-select#add"
22
+ data-advanced-select-value-param="<%= "__new__:#{query}" %>"
23
+ data-advanced-select-submit-value-param="<%= "__new__:#{query}" %>"
24
+ data-advanced-select-label-param="<%= query %>"
25
+ data-advanced-select-display-label-param="<%= query %>">
26
+ <%= t("shared.advanced_select.add_option", query: query) %>
27
+ </button>
28
+ <% elsif advanced_select_options_empty?(options) %>
29
+ <div class="<%= advanced_select_class(class_map, :empty) %>"><%= t("shared.advanced_select.empty") %></div>
30
+ <% end %>
31
+ </div>