avo 4.2.5 → 4.2.7

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +38 -38
  3. data/app/assets/builds/avo/application.css +19 -0
  4. data/app/assets/stylesheets/css/components/tooltip.css +21 -0
  5. data/app/components/avo/field_wrapper_component.html.erb +15 -10
  6. data/app/components/avo/field_wrapper_component.rb +11 -0
  7. data/app/components/avo/index/field_wrapper_component.html.erb +3 -3
  8. data/app/components/avo/index/field_wrapper_component.rb +2 -0
  9. data/app/components/avo/modal_component.html.erb +1 -1
  10. data/app/components/avo/u_i/panel_component.html.erb +1 -1
  11. data/app/controllers/avo/associations_controller.rb +7 -4
  12. data/app/controllers/avo/base_controller.rb +8 -3
  13. data/app/controllers/avo/search_controller.rb +4 -0
  14. data/app/views/avo/partials/_confirm_dialog.html.erb +2 -2
  15. data/app/views/avo/partials/_table_header.html.erb +3 -2
  16. data/app/views/avo/partials/_table_header_label.html.erb +10 -0
  17. data/lib/avo/base_action.rb +5 -2
  18. data/lib/avo/concerns/has_field_tooltips.rb +38 -0
  19. data/lib/avo/current.rb +19 -1
  20. data/lib/avo/fields/area_field.rb +4 -0
  21. data/lib/avo/fields/base_field.rb +25 -0
  22. data/lib/avo/fields/code_field.rb +4 -0
  23. data/lib/avo/fields/easy_mde_field.rb +4 -0
  24. data/lib/avo/fields/files_field.rb +4 -0
  25. data/lib/avo/fields/location_field.rb +10 -0
  26. data/lib/avo/fields/progress_bar_field.rb +4 -0
  27. data/lib/avo/resources/array_resource.rb +23 -15
  28. data/lib/avo/resources/base.rb +36 -2
  29. data/lib/avo/services/telemetry_service.rb +15 -1
  30. data/lib/avo/skills/avo-associations/SKILL.md +1 -1
  31. data/lib/avo/skills/avo-fields/SKILL.md +1 -0
  32. data/lib/avo/skills/docs-index.json +2 -2
  33. data/lib/avo/version.rb +1 -1
  34. data/lib/avo/view_types/view_type_manager.rb +6 -4
  35. metadata +3 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bba089d3b0fe4f9d84ca2284da37ced9876bb8380a883a29c7a542c4c7bd79c
4
- data.tar.gz: 46fe47577923cc7f1de8e6df76fefcfb4ab999360eb59a08d39d48d111687fa2
3
+ metadata.gz: 06a99742dcd8d96855fb10577085da6acf2fb2eabebc8ba66d02b653e404f962
4
+ data.tar.gz: 7d63ef33110cdf91bb3188ecf82abac3ecf9f56fd3c1edc53605737da96b2557
5
5
  SHA512:
6
- metadata.gz: 8501ec94f95a72ab5ed6eb2d4750791686440b3f74ee17630f553ea169ab26d9ca475163562aa0ab4a5735d6e3ada3082f2421c0c52e7a1db3d2d35d41590d4a
7
- data.tar.gz: de04871e9e0a0f6d058197970d3424b7ce10b7cb810edfa0aab06ea598fc132ef16c041bd28816b218ac5d3b61dbb095982ae14bf56c9d4661540b12524ce522
6
+ metadata.gz: 4dec169c3e7a227e2fd6baffd9a8766c91fe0fba55a04fb3bcf9cd1691c12479a5579450e98cf27a90c1cfab99970d9b5498a9b26e3a745c443878b4f1504006
7
+ data.tar.gz: 8903fb5e47c672f02254cf44e90fb3df88775e0d5b382131b7ca70c3567d5d6b35815b09e6a129b827111ef447d6cc6dc1dba99655b4bf628d59b9eb114dd69d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (4.2.5)
4
+ avo (4.2.7)
5
5
  actionview (>= 6.1)
6
6
  active_link_to
7
7
  activerecord (>= 6.1)
@@ -120,7 +120,7 @@ GEM
120
120
  prism
121
121
  addressable (2.9.0)
122
122
  public_suffix (>= 2.0.2, < 8.0)
123
- amazing_print (2.0.0)
123
+ amazing_print (3.0.0)
124
124
  annotate (2.6.5)
125
125
  activerecord (>= 2.3.0)
126
126
  rake (>= 0.8.7)
@@ -139,8 +139,8 @@ GEM
139
139
  avo-record_link_field (0.0.2)
140
140
  avo-rhino_field (4.1.0)
141
141
  aws-eventstream (1.4.0)
142
- aws-partitions (1.1283.0)
143
- aws-sdk-core (3.254.1)
142
+ aws-partitions (1.1289.0)
143
+ aws-sdk-core (3.257.0)
144
144
  aws-eventstream (~> 1, >= 1.3.0)
145
145
  aws-partitions (~> 1, >= 1.992.0)
146
146
  aws-sigv4 (~> 1.9)
@@ -148,11 +148,12 @@ GEM
148
148
  bigdecimal
149
149
  jmespath (~> 1, >= 1.6.1)
150
150
  logger
151
- aws-sdk-kms (1.130.0)
152
- aws-sdk-core (~> 3, >= 3.254.0)
151
+ rexml (~> 3.4, >= 3.4.2)
152
+ aws-sdk-kms (1.132.0)
153
+ aws-sdk-core (~> 3, >= 3.256.0)
153
154
  aws-sigv4 (~> 1.5)
154
- aws-sdk-s3 (1.229.0)
155
- aws-sdk-core (~> 3, >= 3.254.1)
155
+ aws-sdk-s3 (1.232.1)
156
+ aws-sdk-core (~> 3, >= 3.256.0)
156
157
  aws-sdk-kms (~> 1)
157
158
  aws-sigv4 (~> 1.5)
158
159
  aws-sigv4 (1.12.1)
@@ -179,9 +180,9 @@ GEM
179
180
  erubi (~> 1.4)
180
181
  parser (>= 2.4)
181
182
  smart_properties
182
- bigdecimal (4.1.2)
183
+ bigdecimal (4.1.3)
183
184
  bindex (0.8.1)
184
- bootsnap (1.25.0)
185
+ bootsnap (1.26.0)
185
186
  msgpack (~> 1.5)
186
187
  builder (3.3.0)
187
188
  bump (0.10.0)
@@ -218,15 +219,16 @@ GEM
218
219
  bigdecimal
219
220
  rexml
220
221
  crass (1.0.7)
221
- css_parser (3.0.0)
222
+ css_parser (3.2.0)
222
223
  addressable
224
+ base64
223
225
  ssrf_filter (~> 1.5)
224
226
  cssbundling-rails (1.4.3)
225
227
  railties (>= 6.0.0)
226
228
  csv (3.3.6)
227
- cuprite (0.17)
229
+ cuprite (0.18)
228
230
  capybara (~> 3.0)
229
- ferrum (~> 0.17.0)
231
+ ferrum (~> 0.18.0)
230
232
  danger (9.6.1)
231
233
  base64 (~> 0.2)
232
234
  claide (~> 1.0)
@@ -326,21 +328,20 @@ GEM
326
328
  railties (>= 6.1.0)
327
329
  faker (3.8.0)
328
330
  i18n (>= 1.8.11, < 2)
329
- faraday (2.14.3)
331
+ faraday (2.14.4)
330
332
  faraday-net_http (>= 2.0, < 3.5)
331
333
  json
332
334
  logger
333
- faraday-http-cache (2.7.0)
335
+ faraday-http-cache (2.8.0)
334
336
  faraday (>= 0.8)
335
337
  faraday-multipart (1.2.0)
336
338
  multipart-post (~> 2.0)
337
339
  faraday-net_http (3.4.4)
338
340
  net-http (~> 0.5)
339
- ferrum (0.17.2)
341
+ ferrum (0.18.0)
340
342
  addressable (~> 2.5)
341
343
  base64 (~> 0.2)
342
344
  concurrent-ruby (~> 1.1)
343
- webrick (~> 1.7)
344
345
  websocket-driver (~> 0.7)
345
346
  ffi (1.17.4)
346
347
  ffi (1.17.4-arm64-darwin)
@@ -362,7 +363,7 @@ GEM
362
363
  gem-release (2.2.4)
363
364
  get_process_mem (0.2.7)
364
365
  ffi (~> 1.0)
365
- git (5.2.0)
366
+ git (5.6.0)
366
367
  activesupport (>= 5.0)
367
368
  addressable (~> 2.8)
368
369
  process_executer (~> 4.1)
@@ -409,7 +410,7 @@ GEM
409
410
  inline_svg (1.10.0)
410
411
  activesupport (>= 3.0)
411
412
  nokogiri (>= 1.6)
412
- io-console (0.9.2)
413
+ io-console (0.9.4)
413
414
  irb (1.18.0)
414
415
  pp (>= 0.6.0)
415
416
  prism (>= 1.3.0)
@@ -431,8 +432,8 @@ GEM
431
432
  addressable (~> 2.8)
432
433
  childprocess (~> 5.0)
433
434
  logger (~> 1.6)
434
- lexxy (0.9.31)
435
- rails (>= 8.0.2)
435
+ lexxy (0.9.33)
436
+ railties (>= 8.0.2)
436
437
  lint_roller (1.1.0)
437
438
  listen (3.10.0)
438
439
  logger
@@ -485,17 +486,17 @@ GEM
485
486
  monetize (~> 1.9)
486
487
  money (~> 6.13)
487
488
  railties (>= 3.0)
488
- msgpack (1.8.4)
489
+ msgpack (1.8.5)
489
490
  multipart-post (2.4.1)
490
491
  nap (1.1.0)
491
492
  net-http (0.9.1)
492
493
  uri (>= 0.11.1)
493
- net-imap (0.6.6)
494
+ net-imap (0.6.7)
494
495
  date
495
496
  net-protocol
496
497
  net-pop (0.1.2)
497
498
  net-protocol
498
- net-protocol (0.3.0)
499
+ net-protocol (0.4.0)
499
500
  timeout
500
501
  net-smtp (0.5.1)
501
502
  net-protocol
@@ -513,12 +514,12 @@ GEM
513
514
  openssl (4.0.2)
514
515
  orm_adapter (0.5.0)
515
516
  ostruct (0.6.3)
516
- pagy (43.6.2)
517
+ pagy (43.6.3)
517
518
  json
518
519
  uri
519
520
  yaml
520
- parallel (2.1.0)
521
- parallel_tests (5.7.0)
521
+ parallel (2.2.0)
522
+ parallel_tests (5.8.0)
522
523
  parallel
523
524
  parser (3.3.12.0)
524
525
  ast (~> 2.4.1)
@@ -595,7 +596,7 @@ GEM
595
596
  zeitwerk (~> 2.6)
596
597
  rainbow (3.1.1)
597
598
  rake (13.4.2)
598
- ransack (4.4.1)
599
+ ransack (5.0.2)
599
600
  activerecord (>= 7.2)
600
601
  activesupport (>= 7.2)
601
602
  i18n
@@ -623,7 +624,7 @@ GEM
623
624
  parser (~> 3.3.0)
624
625
  rainbow (>= 2.0, < 4.0)
625
626
  rexml (~> 3.1)
626
- regexp_parser (2.12.0)
627
+ regexp_parser (2.13.0)
627
628
  reline (0.7.0)
628
629
  io-console (~> 0.5)
629
630
  responders (3.2.1)
@@ -707,19 +708,19 @@ GEM
707
708
  simplecov (>= 0.22.0)
708
709
  tty-which (~> 0.5.0)
709
710
  virtus (~> 2.0)
710
- rubyzip (3.6.0)
711
+ rubyzip (3.7.0)
711
712
  sawyer (0.9.3)
712
713
  addressable (>= 2.3.5)
713
714
  faraday (>= 0.17.3, < 3)
714
715
  securerandom (0.4.1)
715
- selenium-webdriver (4.48.0)
716
+ selenium-webdriver (4.49.0)
716
717
  base64 (~> 0.2)
717
718
  logger (~> 1.4)
718
719
  rexml (~> 3.2, >= 3.2.5)
719
720
  rubyzip (>= 1.2.2, < 4.0)
720
721
  websocket (~> 1.0)
721
- sexp_processor (4.17.5)
722
- simplecov (1.1.1)
722
+ sexp_processor (4.17.6)
723
+ simplecov (1.3.0)
723
724
  simplecov-cobertura (4.0.0)
724
725
  rexml
725
726
  simplecov (~> 1.0)
@@ -754,7 +755,7 @@ GEM
754
755
  spring (4.7.0)
755
756
  spring-commands-rspec (1.0.4)
756
757
  spring (>= 0.9.1)
757
- ssrf_filter (1.5.0)
758
+ ssrf_filter (1.6.0)
758
759
  standard (1.56.0)
759
760
  language_server-protocol (~> 3.17.0.2)
760
761
  lint_roller (~> 1.0)
@@ -789,9 +790,9 @@ GEM
789
790
  tzinfo (2.0.6)
790
791
  concurrent-ruby (~> 1.0)
791
792
  unaccent (0.4.0)
792
- unicode-display_width (3.2.0)
793
- unicode-emoji (~> 4.1)
794
- unicode-emoji (4.2.0)
793
+ unicode-display_width (3.3.0)
794
+ unicode-emoji (~> 4.3)
795
+ unicode-emoji (4.3.0)
795
796
  uri (1.1.1)
796
797
  useragent (0.16.11)
797
798
  view_component (4.15.0)
@@ -812,7 +813,6 @@ GEM
812
813
  addressable (>= 2.8.0)
813
814
  crack (>= 0.3.2)
814
815
  hashdiff (>= 0.4.0, < 2.0.0)
815
- webrick (1.9.2)
816
816
  websocket (1.2.11)
817
817
  websocket-driver (0.8.2)
818
818
  base64
@@ -4019,6 +4019,25 @@
4019
4019
  .tippy-box[data-theme~='preview'] .tippy-arrow {
4020
4020
  display: none;
4021
4021
  }
4022
+ .tooltip-anchor {
4023
+ max-width: 100%;
4024
+ min-width: 0px;
4025
+ }
4026
+ .tooltip-anchor--inline {
4027
+ display: inline-block;
4028
+ }
4029
+ .tooltip-anchor--block {
4030
+ display: block;
4031
+ width: 100%;
4032
+ }
4033
+ .label-tooltip__icon {
4034
+ margin-inline-start: var(--spacing);
4035
+ display: inline-block;
4036
+ width: 1em;
4037
+ height: 1em;
4038
+ vertical-align: -0.125em;
4039
+ color: var(--color-content-secondary);
4040
+ }
4022
4041
  tags.tagify {
4023
4042
  --tags-border-color: var(--color-tertiary);
4024
4043
  --tags-hover-border-color: var(--color-tertiary);
@@ -36,3 +36,24 @@
36
36
  @apply hidden;
37
37
  }
38
38
 
39
+ /* Anchors for the field `tooltip:` option. Inline hugs the value so the bubble
40
+ sits over a badge or a date rather than over the row; block keeps the width
41
+ of values that fill the row (editors, maps, progress bars). */
42
+ .tooltip-anchor {
43
+ @apply min-w-0 max-w-full;
44
+ }
45
+
46
+ .tooltip-anchor--inline {
47
+ @apply inline-block;
48
+ }
49
+
50
+ .tooltip-anchor--block {
51
+ @apply block w-full;
52
+ }
53
+
54
+ /* The cue that marks a label as hoverable, for the `label_tooltip:` option.
55
+ Sized off the label's own text so it fits the Show label and the smaller
56
+ Index header alike. */
57
+ .label-tooltip__icon {
58
+ @apply inline-block size-[1em] ms-1 align-[-0.125em] text-content-secondary;
59
+ }
@@ -7,17 +7,20 @@
7
7
  class: label_classes,
8
8
  style: label_style,
9
9
  data: label_data do %>
10
- <span class="self-center">
10
+ <%= content_tag :span, class: label_text_classes, **tooltip_attributes(label_tooltip) do %>
11
11
  <% if @form.present? %>
12
12
  <%= @form.label label_for, label %>
13
13
  <% else %>
14
14
  <%= @field.name %>
15
15
  <% end %>
16
16
  <% if on_edit? && @field.is_required? %> <span class="text-danger-content ms-1">*</span> <% end %>
17
+ <% if label_tooltip.present? %>
18
+ <%= helpers.svg "tabler/outline/info-circle", class: "label-tooltip__icon" %>
19
+ <% end %>
17
20
  <% if label_help.present? %>
18
21
  <div class="field-wrapper__label-help"><%== label_help %></div>
19
22
  <% end %>
20
- </span>
23
+ <% end %>
21
24
  <% end %>
22
25
  <%= content_tag :div,
23
26
  class: content_classes,
@@ -26,23 +29,25 @@
26
29
  <%= tag.div class: "field-wrapper__content-wrapper" do %>
27
30
  <% if on_show? %>
28
31
  <div class="flex flex-row items-center group gap-x-1 group/clipboard w-full">
29
- <% if render_dash? %>
30
- —
31
- <% elsif collapsable? %>
32
- <%= render Avo::Fields::Common::CollapsableContentComponent.new do %>
32
+ <%= value_tooltip_anchor(block: block_tooltip_anchor?) do %>
33
+ <% if render_dash? %>
34
+ —
35
+ <% elsif collapsable? %>
36
+ <%= render Avo::Fields::Common::CollapsableContentComponent.new do %>
37
+ <%= content %>
38
+ <% end %>
39
+ <% else %>
33
40
  <%= content %>
34
41
  <% end %>
35
- <% else %>
36
- <%= content %>
37
42
  <% end %>
38
43
  <% if @field.copyable %>
39
44
  <%= render Avo::ClipboardComponent.new(value: @field.value) %>
40
45
  <% end %>
41
46
  </div>
42
47
  <% elsif on_edit? %>
43
- <div class="field-wrapper__input">
48
+ <%= content_tag :div, class: "field-wrapper__input", **tooltip_attributes(tooltip) do %>
44
49
  <%= content %>
45
- </div>
50
+ <% end %>
46
51
  <% if @field.record_errors.include?(@field.id) %>
47
52
  <div class="field-wrapper__error"><%= @field.record_errors.full_messages_for(@field.id).to_sentence %></div>
48
53
  <% end %>
@@ -2,6 +2,7 @@
2
2
 
3
3
  class Avo::FieldWrapperComponent < Avo::BaseComponent
4
4
  include Avo::Concerns::HasResourceStimulusControllers
5
+ include Avo::Concerns::HasFieldTooltips
5
6
 
6
7
  prop :collapsable, default: false
7
8
  prop :dash_if_blank, default: true
@@ -90,6 +91,16 @@ class Avo::FieldWrapperComponent < Avo::BaseComponent
90
91
  Avo::ExecutionContext.new(target: @label_help || @field.label_help, record: record, resource: @resource, view: @view).handle
91
92
  end
92
93
 
94
+ def label_text_classes
95
+ class_names("self-center", "label-tooltip": label_tooltip.present?)
96
+ end
97
+
98
+ # Collapsable content fills the row like a block-shaped field does, so its
99
+ # anchor keeps that width too.
100
+ def block_tooltip_anchor?
101
+ collapsable? || @field.tooltip_anchor == :block
102
+ end
103
+
93
104
  def record
94
105
  @resource.present? ? @resource.record : nil
95
106
  end
@@ -14,14 +14,14 @@
14
14
  },
15
15
  style: @field.get_html(:style, view: @view, element: :wrapper) do %>
16
16
  <% if render_dash? %>
17
- —
17
+ <%= value_tooltip_anchor { "—" } %>
18
18
  <% else %>
19
19
  <% if @center_content %>
20
20
  <div class="flex items-center justify-center">
21
- <%= content %>
21
+ <%= value_tooltip_anchor(block: @field.tooltip_anchor == :block) { content } %>
22
22
  </div>
23
23
  <% else %>
24
- <%= content %>
24
+ <%= value_tooltip_anchor(block: @field.tooltip_anchor == :block) { content } %>
25
25
  <% end %>
26
26
  <% if @field.copyable %>
27
27
  <%= render Avo::ClipboardComponent.new(value: @field.value) %>
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Index::FieldWrapperComponent < Avo::BaseComponent
4
+ include Avo::Concerns::HasFieldTooltips
5
+
4
6
  # Vertical padding for each density step. `nil` density falls back to
5
7
  # `Avo.configuration.density`.
6
8
  DENSITY_PADDING = {
@@ -17,7 +17,7 @@
17
17
  aria-modal="true"
18
18
  class="modal__card-container"
19
19
  >
20
- <div class="modal__card" data-<%= ctrl %>-target="card">
20
+ <div class="modal__card" <%= tag.attributes(data: {"#{ctrl}-target": "card"}) %>>
21
21
  <div class="card">
22
22
  <div class="card__wrapper">
23
23
  <% if has_header? %>
@@ -36,7 +36,7 @@
36
36
  <% end %>
37
37
 
38
38
  <div class="panel__content">
39
- <div class="panel__body" <%= "data-content-focus tabindex=-1".html_safe if content_focusable? %>>
39
+ <div class="panel__body" <%= tag.attributes(content_focusable? ? {data: {"content-focus": ""}, tabindex: -1} : {}) %>>
40
40
  <% if card? %>
41
41
  <div class="panel__card"><%= card %></div>
42
42
  <% else %>
@@ -412,6 +412,9 @@ module Avo
412
412
  def checkbox_list_option(record)
413
413
  resource = @attachment_resource.new(record:, view: Avo::ViewInquirer.new(:new), params:)
414
414
  search_item = (@attachment_resource.fetch_search(:item, record:) || {}).with_indifferent_access
415
+ # Same split as the global search controller: `record_title` is the fallback, and a
416
+ # declared `self.search[:item]` supplies title, description and image itself. Those
417
+ # values are app-authored and outside what a title-attribute rule governs.
415
418
  title = search_item[:title] || resource.record_title
416
419
 
417
420
  {
@@ -446,9 +449,9 @@ module Avo
446
449
  page_key = "#{pagination_key}.page"
447
450
 
448
451
  @index_params[:page] = if Avo.configuration.session_persistence_enabled?
449
- session[page_key] = params[:page] || session[page_key] || 1
452
+ session[page_key] = positive_integer_or(params[:page] || session[page_key], 1)
450
453
  else
451
- params[:page] || 1
454
+ positive_integer_or(params[:page], 1)
452
455
  end
453
456
  end
454
457
 
@@ -457,9 +460,9 @@ module Avo
457
460
  per_page_key = "#{pagination_key}.per_page"
458
461
 
459
462
  @index_params[:per_page] = if Avo.configuration.session_persistence_enabled?
460
- session[per_page_key] = params[:per_page] || session[per_page_key] || Avo.configuration.via_per_page
463
+ session[per_page_key] = positive_integer_or(params[:per_page] || session[per_page_key], Avo.configuration.via_per_page)
461
464
  else
462
- params[:per_page] || Avo.configuration.via_per_page
465
+ positive_integer_or(params[:per_page], Avo.configuration.via_per_page)
463
466
  end
464
467
  end
465
468
  end
@@ -706,12 +706,17 @@ module Avo
706
706
  end
707
707
 
708
708
  def set_pagination_params
709
- @index_params[:page] = params[:page] || 1
709
+ @index_params[:page] = positive_integer_or(params[:page], 1)
710
+ @index_params[:per_page] = positive_integer_or(params[:per_page].presence || cookies[:per_page], Avo.configuration.per_page)
710
711
 
711
712
  # If the request includes the 'per_page' parameter, save its value to the cookies
712
- cookies[:per_page] = params[:per_page] if params[:per_page].present?
713
+ cookies[:per_page] = @index_params[:per_page] if params[:per_page].present?
714
+ end
713
715
 
714
- @index_params[:per_page] = cookies[:per_page] || Avo.configuration.per_page
716
+ # Pagy raises on anything that is not a positive integer, so fall back when the value is not one.
717
+ def positive_integer_or(value, fallback)
718
+ value = value.to_s.to_i
719
+ value.positive? ? value : fallback
715
720
  end
716
721
 
717
722
  # If we don't get a query object predefined from a child controller like associations, just spin one up.
@@ -160,6 +160,10 @@ module Avo
160
160
  end
161
161
 
162
162
  def fetch_result_information(record, resource, item)
163
+ # `record_title` is the fallback here, not the source: a resource declaring
164
+ # `self.search[:item]` supplies the title itself, and that block is app-authored.
165
+ # Anything that governs which attributes a title may expose therefore reaches this
166
+ # surface through `record_title` only — the item half is the app's own.
163
167
  title = item&.dig(:title) || resource.record_title
164
168
  highlighted_title = highlight(title&.to_s, CGI.escapeHTML(params[:q] || ""))
165
169
 
@@ -2,14 +2,14 @@
2
2
  <form method="dialog">
3
3
  <div class="card relative">
4
4
  <button value="cancel" class="absolute top-3 inset-e-2.5 text-content-secondary bg-transparent hover:bg-tertiary hover:text-content rounded-lg text-sm size-8 ms-auto inline-flex justify-center items-center" data-modal-hide="popup-modal" tabindex="3">
5
- <%= svg "x-mark", class: "size-6" %>
5
+ <%= svg "x", class: "size-6" %>
6
6
  <span class="sr-only"><%= t "avo.close_modal" %></span>
7
7
  </button>
8
8
  <div class="card__wrapper">
9
9
  <div class="card__body text-center items-center justify-between min-h-56">
10
10
  <div class="flex flex-col items-center justify-center p-4 md:p-5">
11
11
  <svg class="mt-4 mx-auto mb-4 text-content-secondary size-12" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
12
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
12
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
13
13
  </svg>
14
14
  <p class="text-md font-normal text-content">
15
15
  <%= t "avo.are_you_sure" %>
@@ -53,6 +53,7 @@
53
53
  sort_direction = first_option
54
54
  end
55
55
  text_classes = "text-content-secondary tracking-tight leading-tight text-xs font-semibold"
56
+ label_tooltip = field.label_tooltip
56
57
  table_header_class = field.table_header_class
57
58
  end_aligned = table_header_class.to_s.split.include?("text-end") %>
58
59
 
@@ -86,7 +87,7 @@
86
87
  "flex-row-reverse": end_aligned
87
88
  ),
88
89
  'data-turbo-frame': params[:turbo_frame] do %>
89
- <%= field.table_header_label %>
90
+ <%= render partial: "avo/partials/table_header_label", locals: {field: field, label_tooltip: label_tooltip} %>
90
91
 
91
92
  <%= render partial: 'avo/partials/sortable_component', locals: {
92
93
  field: field,
@@ -95,7 +96,7 @@
95
96
  <% end %>
96
97
  <% else %>
97
98
  <%= content_tag :div, class: text_classes, data: {sortable: false} do %>
98
- <%= field.table_header_label %>
99
+ <%= render partial: "avo/partials/table_header_label", locals: {field: field, label_tooltip: label_tooltip} %>
99
100
  <% end %>
100
101
  <% end %>
101
102
 
@@ -0,0 +1,10 @@
1
+ <%# The label alone unless the field declares `label_tooltip:`; then the text and
2
+ its cue share one anchor, so hovering either shows the bubble. %>
3
+ <% if label_tooltip.present? %>
4
+ <%= content_tag :span, class: "label-tooltip", title: label_tooltip, data: {tippy: :tooltip} do %>
5
+ <%= field.table_header_label %>
6
+ <%= svg "tabler/outline/info-circle", class: "label-tooltip__icon" %>
7
+ <% end %>
8
+ <% else %>
9
+ <%= field.table_header_label %>
10
+ <% end %>
@@ -186,8 +186,11 @@ module Avo
186
186
 
187
187
  # For some fields, like belongs_to, the id and database_id differ (user vs user_id).
188
188
  # That's why we need to fetch the database_id for when we process the action.
189
- action_fields_by_database_id = action_fields.map do |id, value|
190
- [value.database_id.to_sym, value]
189
+ action_fields_by_database_id = action_fields.values.flat_map do |field|
190
+ # A polymorphic belongs_to submits both `<name>_type` and `<name>_id`, but its database_id only covers the type.
191
+ database_ids = field.try(:is_polymorphic?) ? field.to_permitted_param : [field.database_id]
192
+
193
+ database_ids.map { |database_id| [database_id.to_sym, field] }
191
194
  end.to_h
192
195
 
193
196
  args[:fields].to_unsafe_h.map do |name, value|
@@ -0,0 +1,38 @@
1
+ module Avo
2
+ module Concerns
3
+ # Hover tooltips on a field's label and value, declared with the field's
4
+ # `tooltip:` and `label_tooltip:` options. tippy takes the text from
5
+ # `title` and selects on `data-tippy`, so both attributes travel together.
6
+ module HasFieldTooltips
7
+ extend ActiveSupport::Concern
8
+
9
+ def tooltip
10
+ @resolved_tooltip ||= @field.tooltip
11
+ end
12
+
13
+ def label_tooltip
14
+ @resolved_label_tooltip ||= @field.label_tooltip
15
+ end
16
+
17
+ def tooltip_attributes(text)
18
+ return {} if text.blank?
19
+
20
+ {title: text, data: {tippy: :tooltip}}
21
+ end
22
+
23
+ # Wraps the rendered value in the tooltip anchor. Inline hugs the value so
24
+ # the bubble sits over a badge or a date rather than mid-row; block keeps
25
+ # the width of values that fill the row (editors, maps, bars). Rendered
26
+ # only when there is a tooltip: an empty wrapper would still change how
27
+ # the value sizes.
28
+ def value_tooltip_anchor(block: false, &content_block)
29
+ return capture(&content_block) if tooltip.blank?
30
+
31
+ content_tag :div,
32
+ class: class_names("tooltip-anchor", block ? "tooltip-anchor--block" : "tooltip-anchor--inline"),
33
+ **tooltip_attributes(tooltip),
34
+ &content_block
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/avo/current.rb CHANGED
@@ -27,13 +27,31 @@ class Avo::Current < ActiveSupport::CurrentAttributes
27
27
 
28
28
  attribute :appearance_settings
29
29
 
30
+ # Which Avo surface this request arrived through: `:ui` for the admin screens, `:api`,
31
+ # `:ai`, `:mcp`. It names the client, not the screen — `view` already means index/show/
32
+ # edit/new, which is a screen within one surface. Consumers that resolve an answer per
33
+ # user can differ per surface without a second declaration.
34
+ attribute :interface
35
+
36
+ # Set by the callers that map over every resource with no user in scope — this gem's
37
+ # telemetry, and avo-licensing's debug service. Consumers that restrict what a user may
38
+ # reach read this to opt out explicitly, so the exemption can never be inferred from a
39
+ # nil user (which is a legitimate state on the API surface).
40
+ attribute :enumerating_all_resources
41
+
30
42
  # Rails 7.1 CurrentAttributes#attribute is only `def attribute(*names)` — no `default:` keyword.
31
43
  # `attribute :x, default: {}` is passed as a second positional `{ default: {} }`, so `names.map(&:to_sym)` raises.
32
- resets { self.appearance_settings = {} }
44
+ resets do
45
+ self.appearance_settings = {}
46
+ self.interface = :ui
47
+ self.enumerating_all_resources = false
48
+ end
33
49
 
34
50
  def initialize
35
51
  super
36
52
  self.appearance_settings = {}
53
+ self.interface = :ui
54
+ self.enumerating_all_resources = false
37
55
  end
38
56
 
39
57
  # Protect from error #<RuntimeError: Missing rack.input> when request is ActionDispatch::Request.empty
@@ -41,6 +41,10 @@ module Avo
41
41
  def geometry
42
42
  @geometry.to_s.classify
43
43
  end
44
+
45
+ def tooltip_anchor
46
+ :block
47
+ end
44
48
  end
45
49
  end
46
50
  end
@@ -20,6 +20,9 @@ module Avo
20
20
  include Avo::Fields::Concerns::DomId
21
21
 
22
22
  include ActionView::Helpers::UrlHelper
23
+ # Rails 8.2 extracted link_to, button_to and current_page? into NavigationHelper
24
+ # (rails/rails#58735). Without it, link_to on a resource is a NoMethodError there.
25
+ include ActionView::Helpers::NavigationHelper if defined?(ActionView::Helpers::NavigationHelper)
23
26
 
24
27
  delegate :app, to: ::Avo::Current
25
28
  delegate :view_context, to: :app
@@ -92,6 +95,8 @@ module Avo
92
95
  @autocomplete = args[:autocomplete]
93
96
  @help = args[:help]
94
97
  @label_help = args[:label_help]
98
+ @tooltip = args[:tooltip]
99
+ @label_tooltip = args[:label_tooltip]
95
100
  @default = args[:default]
96
101
  @visible = args[:visible]
97
102
  @html = args[:html]
@@ -227,6 +232,26 @@ module Avo
227
232
  translated_option(:help)
228
233
  end
229
234
 
235
+ # Hover text for the value: anchored to the value itself on Index and
236
+ # Show, to the input on forms. Resolved here rather than in the wrapper
237
+ # because the Index header renders the label outside any wrapper.
238
+ def tooltip
239
+ execute_context(@tooltip.nil? ? translated_option(:tooltip) : @tooltip)
240
+ end
241
+
242
+ # Hover text for the label, on every view and on the Index header.
243
+ def label_tooltip
244
+ execute_context(@label_tooltip.nil? ? translated_option(:label_tooltip) : @label_tooltip)
245
+ end
246
+
247
+ # How the value tooltip anchors to the rendered value. `:inline` hugs the
248
+ # value so the bubble sits over a badge or a date rather than mid-row;
249
+ # fields whose value fills the row (editors, maps, progress bars) answer
250
+ # `:block` so the anchor keeps their width instead of collapsing it.
251
+ def tooltip_anchor
252
+ :inline
253
+ end
254
+
230
255
  def placeholder
231
256
  target = if !@placeholder.nil?
232
257
  @placeholder
@@ -29,6 +29,10 @@ module Avo
29
29
  @line_wrapping = args[:line_wrapping].present? ? args[:line_wrapping] : true
30
30
  @always_show = args[:always_show] || false
31
31
  end
32
+
33
+ def tooltip_anchor
34
+ :block
35
+ end
32
36
  end
33
37
  end
34
38
  end
@@ -20,6 +20,10 @@ module Avo
20
20
  height: @height
21
21
  }
22
22
  end
23
+
24
+ def tooltip_anchor
25
+ :block
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -33,6 +33,10 @@ module Avo
33
33
 
34
34
  record
35
35
  end
36
+
37
+ def tooltip_anchor
38
+ :block
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -3,6 +3,12 @@
3
3
  module Avo
4
4
  module Fields
5
5
  class LocationField < BaseField
6
+ # The two columns a location is stored across, when it is stored across two. Read
7
+ # by anything that has to know which columns back a field — `to_permitted_param`
8
+ # cannot answer that here, since it yields the field id and a hash rather than the
9
+ # column names.
10
+ attr_reader :stored_as
11
+
6
12
  def initialize(id, **args, &block)
7
13
  hide_on :index
8
14
  super
@@ -113,6 +119,10 @@ module Avo
113
119
  record.send(:"#{database_id}=", value[index])
114
120
  end
115
121
  end
122
+
123
+ def tooltip_anchor
124
+ :block
125
+ end
116
126
  end
117
127
  end
118
128
  end
@@ -14,6 +14,10 @@ module Avo
14
14
  @display_value = args[:display_value] || false
15
15
  @value_suffix = args[:value_suffix] || nil
16
16
  end
17
+
18
+ def tooltip_anchor
19
+ :block
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -14,17 +14,23 @@ module Avo
14
14
  }
15
15
 
16
16
  class << self
17
+ # Each array resource keeps its own model class. It starts as a placeholder class and
18
+ # fetch_records replaces it with the class of the records it builds or finds.
17
19
  def model_class
18
- @@model_class ||= Avo.const_set(
20
+ return @model_class if @model_class
21
+
22
+ resource_class = self
23
+
24
+ Avo.send(:remove_const, class_name) if Avo.const_defined?(class_name, false)
25
+
26
+ @model_class = Avo.const_set(
19
27
  class_name,
20
28
  Class.new do
21
29
  include ActiveModel::Model
22
30
 
23
- class << self
24
- def primary_key = nil
25
-
26
- def all = "Avo::Resources::#{class_name}".constantize.new.fetch_records
27
- end
31
+ define_singleton_method(:primary_key) { nil }
32
+ define_singleton_method(:class_name) { resource_class.class_name }
33
+ define_singleton_method(:all) { resource_class.new.fetch_records }
28
34
  end
29
35
  )
30
36
  end
@@ -68,21 +74,22 @@ module Avo
68
74
  @fetched_records ||= if array_of_records.empty?
69
75
  array_of_records
70
76
  elsif is_array_of_active_records?(array_of_records)
71
- @@model_class = array_of_records.first.class
72
- @@model_class.where(id: array_of_records.map(&:id))
77
+ self.class.model_class = array_of_records.first.class
78
+ model_class.where(id: array_of_records.map(&:id))
73
79
  elsif is_active_record_relation?(array_of_records)
74
- @@model_class = array_of_records.try(:model)
80
+ self.class.model_class = array_of_records.try(:model)
75
81
  array_of_records
76
82
  elsif is_array_of_store_model?(array_of_records)
77
- @@model_class = array_of_records.first.class
83
+ self.class.model_class = array_of_records.first.class
78
84
  return(array_of_records)
79
85
  else
80
86
  # Dynamically create a class with accessors for all unique keys from the records
81
87
  keys = array_of_records.flat_map(&:keys).uniq
88
+ resource_class = self.class
82
89
 
83
90
  Avo.send(:remove_const, class_name) if Avo.const_defined?(class_name, false)
84
91
 
85
- Avo.const_set(
92
+ custom_class = Avo.const_set(
86
93
  class_name,
87
94
  Class.new do
88
95
  include ActiveModel::Model
@@ -94,13 +101,14 @@ module Avo
94
101
  id
95
102
  end
96
103
 
97
- class << self
98
- def class_name = class_name
99
- end
104
+ define_singleton_method(:primary_key) { nil }
105
+ define_singleton_method(:class_name) { resource_class.class_name }
106
+ define_singleton_method(:all) { resource_class.new.fetch_records }
100
107
  end
101
108
  )
102
109
 
103
- custom_class = "Avo::#{class_name}".constantize
110
+ # Keep model_class in sync so the records are instances of it
111
+ resource_class.model_class = custom_class
104
112
 
105
113
  # Map the records to instances of the dynamically created class
106
114
  array_of_records.map do |item|
@@ -4,6 +4,9 @@ module Avo
4
4
  extend ActiveSupport::DescendantsTracker
5
5
 
6
6
  include ActionView::Helpers::UrlHelper
7
+ # Rails 8.2 extracted link_to, button_to and current_page? into NavigationHelper
8
+ # (rails/rails#58735). Without it, link_to on a resource is a NoMethodError there.
9
+ include ActionView::Helpers::NavigationHelper if defined?(ActionView::Helpers::NavigationHelper)
7
10
  include Avo::Concerns::HasFieldDiscovery
8
11
  include Avo::Concerns::HasItems
9
12
  include Avo::Concerns::CanReplaceItems
@@ -513,21 +516,52 @@ module Avo
513
516
  fetch_record_title.to_s
514
517
  end
515
518
 
519
+ # Whether this resource may expose `attribute` inside a record title. Core always
520
+ # answers true; a plugin that restricts what a user may reach overrides this and
521
+ # answers from its own rules. That is what keeps a restricted attribute out of
522
+ # global search results, breadcrumbs, record links and the association picker
523
+ # without teaching each of those surfaces separately — they all render a title.
524
+ #
525
+ # Deliberately a question about one attribute rather than a field lookup: a bare
526
+ # `Resource.new(record:)` is built once per option in a belongs_to lookup list, and
527
+ # its items holder is empty until `detect_fields` runs, so resolving a title through
528
+ # the field pipeline would both cost a detection per option and answer nothing on
529
+ # the resources that never detect.
530
+ def title_attribute_reachable?(attribute)
531
+ true
532
+ end
533
+
516
534
  def fetch_record_title
517
535
  return name if @record.nil?
518
536
 
519
537
  # Get the title from the record if title is not set, try to get the name, title or label, or fallback to the to_param
520
- return @record.try(:name) || @record.try(:title) || @record.try(:label) || @record.to_param if title.nil?
538
+ return reachable_title_fallback || @record.to_param if title.nil?
521
539
 
522
540
  # If the title is a symbol, get the value from the record else execute the block/string
523
541
  case title
524
542
  when Symbol
525
- @record.send title
543
+ title_attribute_reachable?(title) ? @record.send(title) : @record.to_param
526
544
  when Proc
545
+ # App-authored code. The app's own code is outside this boundary, so the
546
+ # reachability question is not asked of it.
527
547
  Avo::ExecutionContext.new(target: title, resource: self, record: @record).handle
528
548
  end
529
549
  end
530
550
 
551
+ # Preserves the original `||` chain exactly — an unreachable candidate is skipped
552
+ # the same way a blank one is.
553
+ def reachable_title_fallback
554
+ reachable_title_attribute(:name) ||
555
+ reachable_title_attribute(:title) ||
556
+ reachable_title_attribute(:label)
557
+ end
558
+
559
+ def reachable_title_attribute(attribute)
560
+ return unless title_attribute_reachable?(attribute)
561
+
562
+ @record.try(attribute)
563
+ end
564
+
531
565
  def record_icon
532
566
  fetch_record_icon.to_s
533
567
  end
@@ -24,7 +24,10 @@ class Avo::Services::TelemetryService
24
24
  def avo_metadata
25
25
  resources = Avo.resource_manager.all
26
26
  dashboards = Avo::Current.app.dashboard_manager.all
27
- field_definitions = resources.map(&:get_field_definitions)
27
+ # Counting fields across every resource with no user in scope. Declared explicitly so a
28
+ # consumer that restricts fields per user opts out here rather than inferring it from a
29
+ # nil user — nil is a legitimate user on the API surface, where restriction must still apply.
30
+ field_definitions = enumerating_all_resources { resources.map(&:get_field_definitions) }
28
31
  fields_count = field_definitions.map(&:count).sum
29
32
  fields_per_resource = sprintf("%0.01f", fields_count / (resources.count + 0.0))
30
33
 
@@ -69,6 +72,17 @@ class Avo::Services::TelemetryService
69
72
  "error: #{error.message}"
70
73
  end
71
74
 
75
+ # Marks a block as enumerating every resource without a user, restoring whatever the
76
+ # surrounding request had set rather than assuming it was unset.
77
+ def enumerating_all_resources
78
+ previous = Avo::Current.enumerating_all_resources
79
+ Avo::Current.enumerating_all_resources = true
80
+
81
+ yield
82
+ ensure
83
+ Avo::Current.enumerating_all_resources = previous
84
+ end
85
+
72
86
  def other_metadata(type = :actions)
73
87
  resources = Avo.resource_manager.all
74
88
 
@@ -160,7 +160,7 @@ Turning a long `belongs_to` / `has_many` picker into a type-to-search field is t
160
160
  ## Gotchas
161
161
 
162
162
  - **Set `inverse_of` on the model association.** Avo relies on it to resolve the reciprocal; missing it causes wrong/empty attach lists and save bugs. Set it on both sides.
163
- - **The Rails association must exist first.** Adding `field :x, as: :has_many` does nothing if the model has no `has_many :x` — the association frame raises `Avo::MissingAssociationError`, naming the model, the field, and the declaration to add (`has_many :orders`).
163
+ - **The Rails association must exist first.** Adding `field :x, as: :has_many` does nothing if the model has no `has_many :x` — the association panel raises `Avo::MissingAssociationError`, naming the model, the field, and the declaration to add (`has_many :orders`). The cause is either side: the model genuinely lacks the association, or the field name doesn't match the one the model declares (`field :comments` against a `has_many :post_comments`). The **`array`** field is the one exception — it renders a panel deliberately not backed by an association, so it never raises this.
164
164
  - **`has_*` fields are hidden on Edit by default.** Add `show_on: :edit` to surface them on the form. For editing the related record *in* the form (not just displaying it), use `nested` — which needs the `avo-nested` gem.
165
165
  - **Attach/detach/create/destroy buttons come from the *target* resource's Pundit policy**, and the method names are **plural, matching the association name**: `attach_users?`, `detach_users?`, `create_users?`, `destroy_users?`, `view_users?`, `show_users?` — *not* the singular `detach_user?`. This is the #1 "why isn't my button showing" cause. Cross-link `avo-authorization`.
166
166
  - **`can_create: true` is still vetoed by the policy.** If the target resource's `create?` returns `false`, no create link appears regardless of `can_create`.
@@ -146,6 +146,7 @@ Every field accepts these common options (full list + types at the field-options
146
146
  - **`disabled:`** vs **`readonly:`** — both render the input disabled on forms. `disabled:` **also ignores the value on save** (safe against DOM tampering). `readonly:` is UI-only — a user can re-enable it and submit; don't rely on it for protection.
147
147
  - **`default:`** — pre-fills the New form (and action modals): `default: -> { Time.current }`.
148
148
  - **`help:`** / **`label_help:`** — help text (HTML allowed) below the input (forms only) or below the label (every view).
149
+ - **`tooltip:`** / **`label_tooltip:`** — hover bubble (HTML allowed) on the value (badge/text on Index and Show, the input on forms) or on the label (every view, plus the Index header, with an info icon as the cue). String or block; the block gets `record`, `resource`, `view`, `field`, and returning `nil` renders no tooltip. `record` is `nil` for `label_tooltip` on the Index header. Prefer over `help:`/`label_help:` when the context matters only on demand: `tooltip: -> { "Revoked at #{record.revoked_at}" if record.revoked_at? }`.
149
150
  - **`placeholder:`** — placeholder for empty text-like inputs.
150
151
  - **`sortable:`** — makes the Index column sortable. `true` for real columns; a `-> { query.order(...) }` lambda (receives `query` and `direction`) for computed fields.
151
152
  - **Number `format:`** — formats `:number` fields on Index and Show with `:delimited`, `:percentage`, or `:human`; forms keep the raw number. A formatted number is end-aligned in tables, while a bare number stays start-aligned for identifier/year-shaped values. Rails i18n controls delimiters and separators. For money use the `:money` field, and for anything else `format_display_using:`.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "repo": "avo-hq/docs.avohq.io",
3
3
  "docs_path": "docs/4.0",
4
- "commit": "aba11fb247009e10d25a705d4e437449f4a80c5c",
5
- "date": "2026-09-17",
4
+ "commit": "7f2d52546ecf8bd84044cd011d58f5ffcdeae016",
5
+ "date": "2026-09-22",
6
6
  "note": "The docs commit these skills were last indexed from. Run ruby lib/avo/skills/bin/docs_drift.rb to see what changed since, and --update after a refresh."
7
7
  }
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "4.2.5" unless const_defined?(:VERSION)
2
+ VERSION = "4.2.7" unless const_defined?(:VERSION)
3
3
  end
@@ -40,24 +40,26 @@ module Avo
40
40
 
41
41
  private
42
42
 
43
+ # Names, not constants: plugins register view types from Avo.boot, and loading a component there loads
44
+ # Action View while the app initializes, which Rails 8.2 flags (rails/rails#57224).
43
45
  def register_defaults
44
46
  register :table,
45
- component: Avo::ViewTypes::TableComponent,
47
+ component: "Avo::ViewTypes::TableComponent",
46
48
  icon: "tabler/outline/layout-list",
47
49
  active_icon: "tabler/filled/layout-list"
48
50
 
49
51
  register :grid,
50
- component: Avo::ViewTypes::GridComponent,
52
+ component: "Avo::ViewTypes::GridComponent",
51
53
  icon: "tabler/outline/layout-grid",
52
54
  active_icon: "tabler/filled/layout-grid"
53
55
 
54
56
  register :map,
55
- component: Avo::ViewTypes::MapComponent,
57
+ component: "Avo::ViewTypes::MapComponent",
56
58
  icon: "tabler/outline/compass",
57
59
  active_icon: "tabler/filled/compass"
58
60
 
59
61
  register :list,
60
- component: Avo::ViewTypes::TableComponent,
62
+ component: "Avo::ViewTypes::TableComponent",
61
63
  icon: "tabler/outline/layout-list",
62
64
  active_icon: "tabler/filled/layout-list"
63
65
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avo
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.5
4
+ version: 4.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin
@@ -915,6 +915,7 @@ files:
915
915
  - app/views/avo/partials/_switcher_dropdown.html.erb
916
916
  - app/views/avo/partials/_switcher_inline.html.erb
917
917
  - app/views/avo/partials/_table_header.html.erb
918
+ - app/views/avo/partials/_table_header_label.html.erb
918
919
  - app/views/avo/partials/_view_toggle_button.html.erb
919
920
  - app/views/avo/partials/distribution_chart.html.erb
920
921
  - app/views/avo/partials/distribution_chart_full.html.erb
@@ -969,6 +970,7 @@ files:
969
970
  - lib/avo/concerns/has_description.rb
970
971
  - lib/avo/concerns/has_discreet_information.rb
971
972
  - lib/avo/concerns/has_field_discovery.rb
973
+ - lib/avo/concerns/has_field_tooltips.rb
972
974
  - lib/avo/concerns/has_helpers.rb
973
975
  - lib/avo/concerns/has_item_type.rb
974
976
  - lib/avo/concerns/has_items.rb