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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9be8d05d537d5e36a565546c0f821c68a6a62cd5e3978cbbe23d3ef7451c1f2a
4
- data.tar.gz: c6c4c0667ef096fd55e57024621eb9a21d2338ecbef2c2305e2bf2edab0fad4d
3
+ metadata.gz: 43e9d7178d6a064d18c4243d1fad3ec15fbb935834eb2d07da8cd89d33c955a1
4
+ data.tar.gz: 389f74b51ecd1615a2d3b11c2794e10c57557ce6085595ebe06f3a296b88874a
5
5
  SHA512:
6
- metadata.gz: 492cee1cf22c71e9c6e328052bb3ff7c49d519816c06ebe8610f2c0d07d2cbcfd9ebe09383ac3a69e696497295a34015aa467f004d46e40194ae4cc7d7538473
7
- data.tar.gz: 65b3749af47dc024f475284c0998657190f5c68c27da5578d93dfd35669a4eb9c319d3b1b6df1a07fb16157eb0f8897ed4154ab7b54f5e49d8ad7dda3d1bec84
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 &amp; 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">&#8964;</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">&times;</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
- <% selected_options.first(2).each do |option| %>
4
- <span class="<%= advanced_select_class(class_map, :token) %>"><%= option.fetch(:display_label) %></span>
5
- <% end %>
6
- <% if selected_options.size > 2 %>
7
- <span class="<%= advanced_select_class(class_map, :token) %>"><%= "& +#{selected_options.size - 2}" %></span>
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>
@@ -6,3 +6,4 @@ en:
6
6
  error: "Options could not be loaded"
7
7
  loading: "Loading..."
8
8
  search_placeholder: "Search..."
9
+ selected_count: "%{count} selected"
@@ -6,3 +6,4 @@ tr:
6
6
  error: "Seçenekler yüklenemedi"
7
7
  loading: "Yükleniyor..."
8
8
  search_placeholder: "Ara..."
9
+ selected_count: "%{count} seçili"
@@ -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
@@ -1,3 +1,3 @@
1
1
  module AdvancedSelect
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  end
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: advanced_select
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mehmet Celik