advanced_select 0.1.4 → 0.1.5
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 +4 -4
- data/README.md +87 -0
- data/app/assets/stylesheets/advanced_select/advanced_select.css +34 -0
- data/app/javascript/advanced_select/advanced_select_controller.js +61 -1
- data/app/views/advanced_select/_select.html.erb +26 -2
- data/app/views/advanced_select/_summary.html.erb +10 -5
- data/config/locales/en.yml +1 -0
- data/config/locales/tr.yml +1 -0
- data/lib/advanced_select/class_map.rb +3 -0
- data/lib/advanced_select/helper.rb +4 -1
- data/lib/advanced_select/version.rb +1 -1
- data/lib/generators/advanced_select/install/templates/advanced_select.css +34 -0
- data/lib/generators/advanced_select/install/templates/advanced_select_controller.js +61 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43e9d7178d6a064d18c4243d1fad3ec15fbb935834eb2d07da8cd89d33c955a1
|
|
4
|
+
data.tar.gz: 389f74b51ecd1615a2d3b11c2794e10c57557ce6085595ebe06f3a296b88874a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: faf098b583a4d1ea48aeb788943b3da2400fdca7946f32f02419aef63ca34af371c7ce9cd97368205ff6dcb96e55b5e3044794e5f8f5cf8237faf0e53dd7e62b
|
|
7
|
+
data.tar.gz: 5c8fea29044aa4e5a9da1cb804b93f0124e9e302dbc82c0f63d8a216f767e2e13d518c5768704285874596ef6a809750f3033e7ff759176c35a4991f04ee87f0
|
data/README.md
CHANGED
|
@@ -16,6 +16,8 @@ AdvancedSelect is a small Rails engine for rendering an advanced select input wi
|
|
|
16
16
|
- [Basic Local Select](#basic-local-select)
|
|
17
17
|
- [Remote Search](#remote-search)
|
|
18
18
|
- [Multiple Select](#multiple-select)
|
|
19
|
+
- [Count Summary](#count-summary)
|
|
20
|
+
- [Selection Tooltip](#selection-tooltip)
|
|
19
21
|
- [Add Mode](#add-mode)
|
|
20
22
|
- [Dependent Fields](#dependent-fields)
|
|
21
23
|
- [Custom Option Content](#custom-option-content)
|
|
@@ -428,6 +430,79 @@ Pass `include_hidden: false` to opt out — for example, when your controller re
|
|
|
428
430
|
) %>
|
|
429
431
|
```
|
|
430
432
|
|
|
433
|
+
### Count Summary
|
|
434
|
+
|
|
435
|
+
By default a multiple select collapses its trigger to the first two selected labels plus an `& +N` token. For long selections — or when the detail belongs in a [tooltip](#selection-tooltip) — pass `summary_mode: :count` to render a single `"N selected"` label instead:
|
|
436
|
+
|
|
437
|
+
```erb
|
|
438
|
+
<%= advanced_select_tag(
|
|
439
|
+
"record[item_ids][]",
|
|
440
|
+
id: "record_item_ids",
|
|
441
|
+
selected: selected_options,
|
|
442
|
+
options: options,
|
|
443
|
+
placeholder: t(".items_placeholder"),
|
|
444
|
+
multiple: true,
|
|
445
|
+
summary_mode: :count
|
|
446
|
+
) %>
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
The label text comes from the `shared.advanced_select.selected_count` translation (`"%{count} selected"`), so override it per locale to localize or reword it. `summary_mode` only affects multiple selects; single selects always show the selected label.
|
|
450
|
+
|
|
451
|
+
### Selection Tooltip
|
|
452
|
+
|
|
453
|
+
Multiple selects can show an optional tooltip when the user hovers the trigger — handy together with `summary_mode: :count`, where the trigger only shows a count and the tooltip reveals the detail. The tooltip is hover-driven (no info icon) and is positioned with CSS below the trigger.
|
|
454
|
+
|
|
455
|
+
Pass `tooltip: true` for a built-in list of the selected display labels:
|
|
456
|
+
|
|
457
|
+
```erb
|
|
458
|
+
<%= advanced_select_tag(
|
|
459
|
+
"record[item_ids][]",
|
|
460
|
+
id: "record_item_ids",
|
|
461
|
+
selected: selected_options,
|
|
462
|
+
options: options,
|
|
463
|
+
placeholder: t(".items_placeholder"),
|
|
464
|
+
multiple: true,
|
|
465
|
+
summary_mode: :count,
|
|
466
|
+
tooltip: true
|
|
467
|
+
) %>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
The built-in list stays in sync as the selection changes on the client.
|
|
471
|
+
|
|
472
|
+
For richer layouts — a table, badges, compatibility columns, etc. — pass `tooltip_partial:` instead. The partial receives `selected_options` and `options` as locals and may render any markup:
|
|
473
|
+
|
|
474
|
+
```erb
|
|
475
|
+
<%= advanced_select_tag(
|
|
476
|
+
"record[alternative_ids][]",
|
|
477
|
+
id: "record_alternative_ids",
|
|
478
|
+
selected: selected_options,
|
|
479
|
+
options: options,
|
|
480
|
+
placeholder: t(".alternatives_placeholder"),
|
|
481
|
+
multiple: true,
|
|
482
|
+
summary_mode: :count,
|
|
483
|
+
tooltip_partial: "alternatives/tooltip"
|
|
484
|
+
) %>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
```erb
|
|
488
|
+
<%# app/views/alternatives/_tooltip.html.erb %>
|
|
489
|
+
<table>
|
|
490
|
+
<thead>
|
|
491
|
+
<tr><th>Code & Name</th><th>Type</th></tr>
|
|
492
|
+
</thead>
|
|
493
|
+
<tbody>
|
|
494
|
+
<% selected_options.each do |option| %>
|
|
495
|
+
<tr>
|
|
496
|
+
<td><%= option.fetch(:display_label) %></td>
|
|
497
|
+
<td><%= option.fetch(:value) %></td>
|
|
498
|
+
</tr>
|
|
499
|
+
<% end %>
|
|
500
|
+
</tbody>
|
|
501
|
+
</table>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
A custom `tooltip_partial` is rendered once on the server, so it is best for display-oriented content. If your selection changes on the client and the custom tooltip must reflect it live, re-render the field (for example via a Turbo Stream) after the change. The built-in `tooltip: true` list updates client-side automatically.
|
|
505
|
+
|
|
431
506
|
### Add Mode
|
|
432
507
|
|
|
433
508
|
Set `add_mode: true` when users may submit a new typed value:
|
|
@@ -675,12 +750,19 @@ advanced_select_tag(
|
|
|
675
750
|
include_hidden: true,
|
|
676
751
|
auto_select_single: true,
|
|
677
752
|
eager: true,
|
|
753
|
+
summary_mode: :tokens,
|
|
754
|
+
tooltip: false,
|
|
755
|
+
tooltip_partial: nil,
|
|
678
756
|
option_content_partial: nil,
|
|
679
757
|
classes: {},
|
|
680
758
|
append_classes: {}
|
|
681
759
|
)
|
|
682
760
|
```
|
|
683
761
|
|
|
762
|
+
`summary_mode:` controls how a multiple select renders its collapsed trigger label. The default `:tokens` shows the first two selected display labels followed by `& +N`. Pass `:count` to render a single localized `"N selected"` summary instead (see [Count Summary](#count-summary)).
|
|
763
|
+
|
|
764
|
+
`tooltip:` / `tooltip_partial:` enable an optional hover tooltip on the trigger (see [Selection Tooltip](#selection-tooltip)).
|
|
765
|
+
|
|
684
766
|
`advanced_select_options_tag`:
|
|
685
767
|
|
|
686
768
|
```ruby
|
|
@@ -738,8 +820,12 @@ shared:
|
|
|
738
820
|
empty: "No options found"
|
|
739
821
|
error: "Options could not be loaded"
|
|
740
822
|
loading: "Loading..."
|
|
823
|
+
search_placeholder: "Search..."
|
|
824
|
+
selected_count: "%{count} selected"
|
|
741
825
|
```
|
|
742
826
|
|
|
827
|
+
`selected_count` is used by the [count summary](#count-summary) (`summary_mode: :count`).
|
|
828
|
+
|
|
743
829
|
Override these keys in the host app as needed.
|
|
744
830
|
|
|
745
831
|
## Styling
|
|
@@ -851,6 +937,7 @@ Common styling hooks:
|
|
|
851
937
|
|
|
852
938
|
- `.ui-advanced-select-trigger` controls the visible input button, border, radius, height, background, and focus outline.
|
|
853
939
|
- `.ui-advanced-select-dropdown` controls the popup container, border, radius, shadow, width, and `z-index`.
|
|
940
|
+
- `.ui-advanced-select-tooltip`, `.ui-advanced-select-tooltip-list`, and `.ui-advanced-select-tooltip-item` control the optional [selection tooltip](#selection-tooltip) container and its built-in list. The tooltip is positioned with CSS (`position: absolute` below the trigger); adjust `top`, `min-width`, or `z-index` here if your layout needs it.
|
|
854
941
|
- `.ui-advanced-select-options` controls the scroll container and default `max-height`.
|
|
855
942
|
- `.ui-advanced-select-option` controls option row spacing, hover state, and font sizing.
|
|
856
943
|
- `.ui-advanced-select-option[aria-selected="true"]` controls selected option colors.
|
|
@@ -101,6 +101,40 @@
|
|
|
101
101
|
z-index: 20;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
.ui-advanced-select-tooltip {
|
|
105
|
+
background: #ffffff;
|
|
106
|
+
border: 1px solid #e5e7eb;
|
|
107
|
+
border-radius: 0.75rem;
|
|
108
|
+
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
109
|
+
box-sizing: border-box;
|
|
110
|
+
color: #374151;
|
|
111
|
+
font-size: 0.875rem;
|
|
112
|
+
left: 0;
|
|
113
|
+
line-height: 1.25rem;
|
|
114
|
+
margin-top: 0.25rem;
|
|
115
|
+
min-width: 100%;
|
|
116
|
+
padding: 0.75rem 1rem;
|
|
117
|
+
position: absolute;
|
|
118
|
+
top: 100%;
|
|
119
|
+
width: max-content;
|
|
120
|
+
z-index: 30;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ui-advanced-select-tooltip-list {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: 0.25rem;
|
|
127
|
+
list-style: none;
|
|
128
|
+
margin: 0;
|
|
129
|
+
padding: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ui-advanced-select-tooltip-item {
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
text-overflow: ellipsis;
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
}
|
|
137
|
+
|
|
104
138
|
.ui-advanced-select-search {
|
|
105
139
|
border: 1px solid #e5e7eb;
|
|
106
140
|
border-radius: 0.5rem;
|
|
@@ -2,7 +2,7 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
2
2
|
import { Turbo } from "@hotwired/turbo-rails"
|
|
3
3
|
|
|
4
4
|
export default class extends Controller {
|
|
5
|
-
static targets = ["hiddenFields", "trigger", "summary", "dropdown", "search", "options", "caret", "clear"]
|
|
5
|
+
static targets = ["hiddenFields", "trigger", "summary", "dropdown", "search", "options", "caret", "clear", "tooltip"]
|
|
6
6
|
static values = {
|
|
7
7
|
addMode: Boolean,
|
|
8
8
|
autoSelectSingle: { type: Boolean, default: true },
|
|
@@ -19,6 +19,8 @@ export default class extends Controller {
|
|
|
19
19
|
placeholder: String,
|
|
20
20
|
searchable: Boolean,
|
|
21
21
|
selected: Array,
|
|
22
|
+
selectedCountText: String,
|
|
23
|
+
summaryMode: { type: String, default: "tokens" },
|
|
22
24
|
targetId: String,
|
|
23
25
|
url: String
|
|
24
26
|
}
|
|
@@ -30,6 +32,7 @@ export default class extends Controller {
|
|
|
30
32
|
this.placeholderClass = this.element.dataset.advancedSelectPlaceholderClass || "ui-advanced-select-placeholder"
|
|
31
33
|
this.valueClass = this.element.dataset.advancedSelectValueClass || "ui-advanced-select-value"
|
|
32
34
|
this.tokenClass = this.element.dataset.advancedSelectTokenClass || "ui-advanced-select-token"
|
|
35
|
+
this.tooltipItemClass = this.element.dataset.advancedSelectTooltipItemClass || "ui-advanced-select-tooltip-item"
|
|
33
36
|
this.loadingClass = this.element.dataset.advancedSelectLoadingClass || "ui-advanced-select-loading"
|
|
34
37
|
this.errorClass = this.element.dataset.advancedSelectErrorClass || "ui-advanced-select-error"
|
|
35
38
|
this.emptyClass = this.element.dataset.advancedSelectEmptyClass || "ui-advanced-select-empty"
|
|
@@ -48,6 +51,7 @@ export default class extends Controller {
|
|
|
48
51
|
|
|
49
52
|
disconnect() {
|
|
50
53
|
window.clearTimeout(this.timer)
|
|
54
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
51
55
|
document.removeEventListener("click", this.close)
|
|
52
56
|
|
|
53
57
|
if (this.dependentChangeListener) {
|
|
@@ -66,6 +70,7 @@ export default class extends Controller {
|
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
open() {
|
|
73
|
+
this.hideTooltip(true)
|
|
69
74
|
this.dropdownTarget.classList.remove("hidden")
|
|
70
75
|
this.triggerTarget.setAttribute("aria-expanded", "true")
|
|
71
76
|
document.addEventListener("click", this.close)
|
|
@@ -302,6 +307,7 @@ export default class extends Controller {
|
|
|
302
307
|
renderSelection() {
|
|
303
308
|
this.hiddenFieldsTarget.replaceChildren(...this.hiddenFieldElements)
|
|
304
309
|
this.summaryTarget.replaceChildren(...this.selectionElements)
|
|
310
|
+
this.renderTooltip()
|
|
305
311
|
this.renderOptionsState()
|
|
306
312
|
this.caretTarget.classList.toggle("hidden", this.selectedValue.length > 0)
|
|
307
313
|
this.clearTarget.classList.toggle("hidden", this.selectedValue.length === 0)
|
|
@@ -471,6 +477,10 @@ export default class extends Controller {
|
|
|
471
477
|
return [this.textElement("span", this.valueClass, this.displayLabel(this.selectedValue[0]))]
|
|
472
478
|
}
|
|
473
479
|
|
|
480
|
+
if (this.summaryModeValue === "count") {
|
|
481
|
+
return [this.textElement("span", this.valueClass, this.selectedCountLabel(this.selectedValue.length))]
|
|
482
|
+
}
|
|
483
|
+
|
|
474
484
|
const tokens = this.selectedValue.slice(0, 2).map((option) => this.textElement("span", this.tokenClass, this.displayLabel(option)))
|
|
475
485
|
|
|
476
486
|
if (this.selectedValue.length > 2) {
|
|
@@ -480,10 +490,60 @@ export default class extends Controller {
|
|
|
480
490
|
return tokens
|
|
481
491
|
}
|
|
482
492
|
|
|
493
|
+
selectedCountLabel(count) {
|
|
494
|
+
return (this.selectedCountTextValue || "%{count}").replace("%{count}", count)
|
|
495
|
+
}
|
|
496
|
+
|
|
483
497
|
displayLabel(option) {
|
|
484
498
|
return option.displayLabel || option.label
|
|
485
499
|
}
|
|
486
500
|
|
|
501
|
+
showTooltip() {
|
|
502
|
+
if (!this.hasTooltipTarget || this.expanded || this.selectedValue.length === 0) {
|
|
503
|
+
return
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
507
|
+
this.tooltipTarget.classList.remove("hidden")
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
hideTooltip(immediate = false) {
|
|
511
|
+
if (!this.hasTooltipTarget) {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
516
|
+
|
|
517
|
+
if (immediate === true) {
|
|
518
|
+
this.tooltipTarget.classList.add("hidden")
|
|
519
|
+
} else {
|
|
520
|
+
this.tooltipHideTimer = window.setTimeout(() => this.tooltipTarget.classList.add("hidden"), 150)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
keepTooltip() {
|
|
525
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
renderTooltip() {
|
|
529
|
+
if (!this.hasTooltipTarget || this.tooltipTarget.hasAttribute("data-advanced-select-tooltip-custom")) {
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const list = this.tooltipTarget.querySelector("[data-advanced-select-tooltip-list]")
|
|
534
|
+
if (!list) {
|
|
535
|
+
return
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
list.replaceChildren(
|
|
539
|
+
...this.selectedValue.map((option) => this.textElement("li", this.tooltipItemClass, this.displayLabel(option)))
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
if (this.selectedValue.length === 0) {
|
|
543
|
+
this.hideTooltip(true)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
487
547
|
textElement(tagName, className, text) {
|
|
488
548
|
const element = document.createElement(tagName)
|
|
489
549
|
element.className = className
|
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
data-advanced-select-include-hidden-value="<%= include_hidden %>"
|
|
16
16
|
data-advanced-select-auto-select-single-value="<%= auto_select_single %>"
|
|
17
17
|
data-advanced-select-eager-value="<%= eager %>"
|
|
18
|
+
data-advanced-select-summary-mode-value="<%= summary_mode %>"
|
|
19
|
+
data-advanced-select-selected-count-text-value="<%= t("shared.advanced_select.selected_count", count: "%{count}") %>"
|
|
18
20
|
data-advanced-select-placeholder-class="<%= advanced_select_class(class_map, :placeholder) %>"
|
|
19
21
|
data-advanced-select-value-class="<%= advanced_select_class(class_map, :value) %>"
|
|
20
22
|
data-advanced-select-token-class="<%= advanced_select_class(class_map, :token) %>"
|
|
23
|
+
data-advanced-select-tooltip-item-class="<%= advanced_select_class(class_map, :tooltip_item) %>"
|
|
21
24
|
data-advanced-select-empty-class="<%= advanced_select_class(class_map, :empty) %>"
|
|
22
25
|
data-advanced-select-loading-class="<%= advanced_select_class(class_map, :loading) %>"
|
|
23
26
|
data-advanced-select-error-class="<%= advanced_select_class(class_map, :error) %>"
|
|
@@ -38,6 +41,8 @@
|
|
|
38
41
|
<% end %>
|
|
39
42
|
</div>
|
|
40
43
|
|
|
44
|
+
<% tooltip_enabled = tooltip || tooltip_partial.present? %>
|
|
45
|
+
|
|
41
46
|
<button type="button"
|
|
42
47
|
id="<%= "#{id}_trigger" %>"
|
|
43
48
|
class="<%= advanced_select_class(class_map, :trigger) %>"
|
|
@@ -45,14 +50,33 @@
|
|
|
45
50
|
aria-expanded="false"
|
|
46
51
|
aria-controls="<%= "#{id}_dropdown" %>"
|
|
47
52
|
data-advanced-select-target="trigger"
|
|
48
|
-
data-action="advanced-select#toggle keydown->advanced-select#keydown">
|
|
53
|
+
data-action="advanced-select#toggle keydown->advanced-select#keydown<%= " mouseenter->advanced-select#showTooltip mouseleave->advanced-select#hideTooltip" if tooltip_enabled %>">
|
|
49
54
|
<span id="<%= "#{id}_summary" %>" class="<%= advanced_select_class(class_map, :summary) %>" data-advanced-select-target="summary">
|
|
50
|
-
<%= render partial: "advanced_select/summary", locals: { selected_options: selected_options, multiple: multiple, placeholder: placeholder, class_map: class_map } %>
|
|
55
|
+
<%= render partial: "advanced_select/summary", locals: { selected_options: selected_options, multiple: multiple, placeholder: placeholder, summary_mode: summary_mode, class_map: class_map } %>
|
|
51
56
|
</span>
|
|
52
57
|
<span id="<%= "#{id}_caret" %>" class="<%= [advanced_select_class(class_map, :caret), ("hidden" if selected_options.any?)].compact.join(" ") %>" data-advanced-select-target="caret">⌄</span>
|
|
53
58
|
<span id="<%= "#{id}_clear" %>" class="<%= [advanced_select_class(class_map, :clear), ("hidden" if selected_options.empty?)].compact.join(" ") %>" data-advanced-select-target="clear" data-action="click->advanced-select#clear">×</span>
|
|
54
59
|
</button>
|
|
55
60
|
|
|
61
|
+
<% if tooltip_enabled %>
|
|
62
|
+
<div id="<%= "#{id}_tooltip" %>"
|
|
63
|
+
class="<%= [advanced_select_class(class_map, :tooltip), "hidden"].join(" ") %>"
|
|
64
|
+
role="tooltip"
|
|
65
|
+
data-advanced-select-target="tooltip"
|
|
66
|
+
<%= "data-advanced-select-tooltip-custom" if tooltip_partial.present? %>
|
|
67
|
+
data-action="mouseenter->advanced-select#keepTooltip mouseleave->advanced-select#hideTooltip">
|
|
68
|
+
<% if tooltip_partial.present? %>
|
|
69
|
+
<%= render partial: tooltip_partial, locals: { selected_options: selected_options, options: options } %>
|
|
70
|
+
<% else %>
|
|
71
|
+
<ul class="<%= advanced_select_class(class_map, :tooltip_list) %>" data-advanced-select-tooltip-list>
|
|
72
|
+
<% selected_options.each do |option| %>
|
|
73
|
+
<li class="<%= advanced_select_class(class_map, :tooltip_item) %>"><%= option.fetch(:display_label) %></li>
|
|
74
|
+
<% end %>
|
|
75
|
+
</ul>
|
|
76
|
+
<% end %>
|
|
77
|
+
</div>
|
|
78
|
+
<% end %>
|
|
79
|
+
|
|
56
80
|
<div id="<%= "#{id}_dropdown" %>"
|
|
57
81
|
class="<%= [advanced_select_class(class_map, :dropdown), "hidden"].join(" ") %>"
|
|
58
82
|
data-advanced-select-target="dropdown">
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
<% summary_mode = local_assigns.fetch(:summary_mode, :tokens) %>
|
|
1
2
|
<% if selected_options.any? %>
|
|
2
3
|
<% if multiple %>
|
|
3
|
-
<%
|
|
4
|
-
<span class="<%= advanced_select_class(class_map, :
|
|
5
|
-
<%
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
<% if summary_mode.to_s == "count" %>
|
|
5
|
+
<span class="<%= advanced_select_class(class_map, :value) %>"><%= t("shared.advanced_select.selected_count", count: selected_options.size) %></span>
|
|
6
|
+
<% else %>
|
|
7
|
+
<% selected_options.first(2).each do |option| %>
|
|
8
|
+
<span class="<%= advanced_select_class(class_map, :token) %>"><%= option.fetch(:display_label) %></span>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% if selected_options.size > 2 %>
|
|
11
|
+
<span class="<%= advanced_select_class(class_map, :token) %>"><%= "& +#{selected_options.size - 2}" %></span>
|
|
12
|
+
<% end %>
|
|
8
13
|
<% end %>
|
|
9
14
|
<% else %>
|
|
10
15
|
<span class="<%= advanced_select_class(class_map, :value) %>"><%= selected_options.first.fetch(:display_label) %></span>
|
data/config/locales/en.yml
CHANGED
data/config/locales/tr.yml
CHANGED
|
@@ -9,6 +9,9 @@ module AdvancedSelect
|
|
|
9
9
|
token: "ui-advanced-select-token",
|
|
10
10
|
caret: "ui-advanced-select-caret",
|
|
11
11
|
clear: "ui-advanced-select-clear",
|
|
12
|
+
tooltip: "ui-advanced-select-tooltip",
|
|
13
|
+
tooltip_list: "ui-advanced-select-tooltip-list",
|
|
14
|
+
tooltip_item: "ui-advanced-select-tooltip-item",
|
|
12
15
|
dropdown: "ui-advanced-select-dropdown",
|
|
13
16
|
search: "ui-advanced-select-search",
|
|
14
17
|
options: "ui-advanced-select-options",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module AdvancedSelect
|
|
2
2
|
module Helper
|
|
3
|
-
def advanced_select_tag(name, id:, selected:, options:, placeholder:, options_url: nil, multiple: false, searchable: true, add_mode: false, dependent_fields: {}, include_hidden: true, auto_select_single: true, eager: true, option_content_partial: nil, classes: {}, append_classes: {})
|
|
3
|
+
def advanced_select_tag(name, id:, selected:, options:, placeholder:, options_url: nil, multiple: false, searchable: true, add_mode: false, dependent_fields: {}, include_hidden: true, auto_select_single: true, eager: true, summary_mode: :tokens, tooltip: false, tooltip_partial: nil, option_content_partial: nil, classes: {}, append_classes: {})
|
|
4
4
|
selected_options = advanced_select_selected_options(selected)
|
|
5
5
|
class_map = advanced_select_class_map(classes, append_classes)
|
|
6
6
|
|
|
@@ -18,6 +18,9 @@ module AdvancedSelect
|
|
|
18
18
|
include_hidden: include_hidden,
|
|
19
19
|
auto_select_single: auto_select_single,
|
|
20
20
|
eager: eager,
|
|
21
|
+
summary_mode: summary_mode,
|
|
22
|
+
tooltip: tooltip,
|
|
23
|
+
tooltip_partial: tooltip_partial,
|
|
21
24
|
target_id: "#{id}_options",
|
|
22
25
|
option_content_partial: option_content_partial,
|
|
23
26
|
class_map: class_map
|
|
@@ -101,6 +101,40 @@
|
|
|
101
101
|
z-index: 20;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
.ui-advanced-select-tooltip {
|
|
105
|
+
background: #ffffff;
|
|
106
|
+
border: 1px solid #e5e7eb;
|
|
107
|
+
border-radius: 0.75rem;
|
|
108
|
+
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
109
|
+
box-sizing: border-box;
|
|
110
|
+
color: #374151;
|
|
111
|
+
font-size: 0.875rem;
|
|
112
|
+
left: 0;
|
|
113
|
+
line-height: 1.25rem;
|
|
114
|
+
margin-top: 0.25rem;
|
|
115
|
+
min-width: 100%;
|
|
116
|
+
padding: 0.75rem 1rem;
|
|
117
|
+
position: absolute;
|
|
118
|
+
top: 100%;
|
|
119
|
+
width: max-content;
|
|
120
|
+
z-index: 30;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ui-advanced-select-tooltip-list {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: 0.25rem;
|
|
127
|
+
list-style: none;
|
|
128
|
+
margin: 0;
|
|
129
|
+
padding: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ui-advanced-select-tooltip-item {
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
text-overflow: ellipsis;
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
}
|
|
137
|
+
|
|
104
138
|
.ui-advanced-select-search {
|
|
105
139
|
border: 1px solid #e5e7eb;
|
|
106
140
|
border-radius: 0.5rem;
|
|
@@ -2,7 +2,7 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
2
2
|
import { Turbo } from "@hotwired/turbo-rails"
|
|
3
3
|
|
|
4
4
|
export default class extends Controller {
|
|
5
|
-
static targets = ["hiddenFields", "trigger", "summary", "dropdown", "search", "options", "caret", "clear"]
|
|
5
|
+
static targets = ["hiddenFields", "trigger", "summary", "dropdown", "search", "options", "caret", "clear", "tooltip"]
|
|
6
6
|
static values = {
|
|
7
7
|
addMode: Boolean,
|
|
8
8
|
autoSelectSingle: { type: Boolean, default: true },
|
|
@@ -19,6 +19,8 @@ export default class extends Controller {
|
|
|
19
19
|
placeholder: String,
|
|
20
20
|
searchable: Boolean,
|
|
21
21
|
selected: Array,
|
|
22
|
+
selectedCountText: String,
|
|
23
|
+
summaryMode: { type: String, default: "tokens" },
|
|
22
24
|
targetId: String,
|
|
23
25
|
url: String
|
|
24
26
|
}
|
|
@@ -30,6 +32,7 @@ export default class extends Controller {
|
|
|
30
32
|
this.placeholderClass = this.element.dataset.advancedSelectPlaceholderClass || "ui-advanced-select-placeholder"
|
|
31
33
|
this.valueClass = this.element.dataset.advancedSelectValueClass || "ui-advanced-select-value"
|
|
32
34
|
this.tokenClass = this.element.dataset.advancedSelectTokenClass || "ui-advanced-select-token"
|
|
35
|
+
this.tooltipItemClass = this.element.dataset.advancedSelectTooltipItemClass || "ui-advanced-select-tooltip-item"
|
|
33
36
|
this.loadingClass = this.element.dataset.advancedSelectLoadingClass || "ui-advanced-select-loading"
|
|
34
37
|
this.errorClass = this.element.dataset.advancedSelectErrorClass || "ui-advanced-select-error"
|
|
35
38
|
this.emptyClass = this.element.dataset.advancedSelectEmptyClass || "ui-advanced-select-empty"
|
|
@@ -48,6 +51,7 @@ export default class extends Controller {
|
|
|
48
51
|
|
|
49
52
|
disconnect() {
|
|
50
53
|
window.clearTimeout(this.timer)
|
|
54
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
51
55
|
document.removeEventListener("click", this.close)
|
|
52
56
|
|
|
53
57
|
if (this.dependentChangeListener) {
|
|
@@ -66,6 +70,7 @@ export default class extends Controller {
|
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
open() {
|
|
73
|
+
this.hideTooltip(true)
|
|
69
74
|
this.dropdownTarget.classList.remove("hidden")
|
|
70
75
|
this.triggerTarget.setAttribute("aria-expanded", "true")
|
|
71
76
|
document.addEventListener("click", this.close)
|
|
@@ -302,6 +307,7 @@ export default class extends Controller {
|
|
|
302
307
|
renderSelection() {
|
|
303
308
|
this.hiddenFieldsTarget.replaceChildren(...this.hiddenFieldElements)
|
|
304
309
|
this.summaryTarget.replaceChildren(...this.selectionElements)
|
|
310
|
+
this.renderTooltip()
|
|
305
311
|
this.renderOptionsState()
|
|
306
312
|
this.caretTarget.classList.toggle("hidden", this.selectedValue.length > 0)
|
|
307
313
|
this.clearTarget.classList.toggle("hidden", this.selectedValue.length === 0)
|
|
@@ -473,6 +479,10 @@ export default class extends Controller {
|
|
|
473
479
|
return [this.textElement("span", this.valueClass, this.displayLabel(this.selectedValue[0]))]
|
|
474
480
|
}
|
|
475
481
|
|
|
482
|
+
if (this.summaryModeValue === "count") {
|
|
483
|
+
return [this.textElement("span", this.valueClass, this.selectedCountLabel(this.selectedValue.length))]
|
|
484
|
+
}
|
|
485
|
+
|
|
476
486
|
const tokens = this.selectedValue.slice(0, 2).map((option) => this.textElement("span", this.tokenClass, this.displayLabel(option)))
|
|
477
487
|
|
|
478
488
|
if (this.selectedValue.length > 2) {
|
|
@@ -482,10 +492,60 @@ export default class extends Controller {
|
|
|
482
492
|
return tokens
|
|
483
493
|
}
|
|
484
494
|
|
|
495
|
+
selectedCountLabel(count) {
|
|
496
|
+
return (this.selectedCountTextValue || "%{count}").replace("%{count}", count)
|
|
497
|
+
}
|
|
498
|
+
|
|
485
499
|
displayLabel(option) {
|
|
486
500
|
return option.displayLabel || option.label
|
|
487
501
|
}
|
|
488
502
|
|
|
503
|
+
showTooltip() {
|
|
504
|
+
if (!this.hasTooltipTarget || this.expanded || this.selectedValue.length === 0) {
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
509
|
+
this.tooltipTarget.classList.remove("hidden")
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
hideTooltip(immediate = false) {
|
|
513
|
+
if (!this.hasTooltipTarget) {
|
|
514
|
+
return
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
518
|
+
|
|
519
|
+
if (immediate === true) {
|
|
520
|
+
this.tooltipTarget.classList.add("hidden")
|
|
521
|
+
} else {
|
|
522
|
+
this.tooltipHideTimer = window.setTimeout(() => this.tooltipTarget.classList.add("hidden"), 150)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
keepTooltip() {
|
|
527
|
+
window.clearTimeout(this.tooltipHideTimer)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
renderTooltip() {
|
|
531
|
+
if (!this.hasTooltipTarget || this.tooltipTarget.hasAttribute("data-advanced-select-tooltip-custom")) {
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const list = this.tooltipTarget.querySelector("[data-advanced-select-tooltip-list]")
|
|
536
|
+
if (!list) {
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
list.replaceChildren(
|
|
541
|
+
...this.selectedValue.map((option) => this.textElement("li", this.tooltipItemClass, this.displayLabel(option)))
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
if (this.selectedValue.length === 0) {
|
|
545
|
+
this.hideTooltip(true)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
489
549
|
textElement(tagName, className, text) {
|
|
490
550
|
const element = document.createElement(tagName)
|
|
491
551
|
element.className = className
|