katalyst-tables 3.3.1 → 3.3.2

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/builds/katalyst/tables.esm.js +332 -6
  4. data/app/assets/builds/katalyst/tables.js +332 -6
  5. data/app/assets/builds/katalyst/tables.min.js +1 -1
  6. data/app/assets/builds/katalyst/tables.min.js.map +1 -1
  7. data/app/assets/stylesheets/katalyst/tables/_index.scss +1 -1
  8. data/app/assets/stylesheets/katalyst/tables/_query.scss +109 -0
  9. data/app/assets/stylesheets/katalyst/tables/typed-columns/_date.scss +1 -1
  10. data/app/assets/stylesheets/katalyst/tables/typed-columns/_datetime.scss +1 -1
  11. data/app/components/katalyst/table_component.rb +13 -2
  12. data/app/components/katalyst/tables/cells/currency_component.rb +21 -2
  13. data/app/components/katalyst/tables/cells/number_component.rb +31 -1
  14. data/app/components/katalyst/tables/query/input_component.html.erb +12 -0
  15. data/app/components/katalyst/tables/query/input_component.rb +46 -0
  16. data/app/components/katalyst/tables/query/modal_component.html.erb +33 -0
  17. data/app/components/katalyst/tables/query/modal_component.rb +89 -0
  18. data/app/components/katalyst/tables/query_component.html.erb +8 -0
  19. data/app/components/katalyst/tables/{filter_component.rb → query_component.rb} +37 -30
  20. data/app/controllers/concerns/katalyst/tables/backend.rb +14 -0
  21. data/app/helpers/katalyst/tables/frontend.rb +14 -8
  22. data/app/javascript/tables/application.js +8 -3
  23. data/app/javascript/tables/query_controller.js +108 -0
  24. data/app/javascript/tables/query_input_controller.js +228 -0
  25. data/app/models/concerns/katalyst/tables/collection/core.rb +2 -2
  26. data/app/models/concerns/katalyst/tables/collection/query/array_value_parser.rb +13 -26
  27. data/app/models/concerns/katalyst/tables/collection/query/parser.rb +16 -13
  28. data/app/models/concerns/katalyst/tables/collection/query/single_value_parser.rb +11 -3
  29. data/app/models/concerns/katalyst/tables/collection/query/value_parser.rb +10 -5
  30. data/app/models/concerns/katalyst/tables/collection/query.rb +42 -5
  31. data/app/models/concerns/katalyst/tables/collection/sorting.rb +11 -1
  32. data/app/models/katalyst/tables/collection/base.rb +10 -0
  33. data/app/models/katalyst/tables/collection/filter.rb +10 -0
  34. data/app/models/katalyst/tables/collection/type/boolean.rb +11 -1
  35. data/app/models/katalyst/tables/collection/type/date.rb +19 -22
  36. data/app/models/katalyst/tables/collection/type/enum.rb +11 -0
  37. data/app/models/katalyst/tables/collection/type/float.rb +3 -39
  38. data/app/models/katalyst/tables/collection/type/helpers/delegate.rb +2 -22
  39. data/app/models/katalyst/tables/collection/type/helpers/extensions.rb +14 -0
  40. data/app/models/katalyst/tables/collection/type/helpers/multiple.rb +30 -0
  41. data/app/models/katalyst/tables/collection/type/helpers/range.rb +59 -0
  42. data/app/models/katalyst/tables/collection/type/integer.rb +3 -39
  43. data/app/models/katalyst/tables/collection/type/value.rb +22 -2
  44. metadata +12 -8
  45. data/app/assets/stylesheets/katalyst/tables/_filter.scss +0 -43
  46. data/app/components/katalyst/tables/filter/modal_component.html.erb +0 -25
  47. data/app/components/katalyst/tables/filter/modal_component.rb +0 -112
  48. data/app/components/katalyst/tables/filter_component.html.erb +0 -18
  49. data/app/javascript/tables/filter/modal_controller.js +0 -13
@@ -7,49 +7,13 @@ module Katalyst
7
7
  class Float < Value
8
8
  include Helpers::Delegate
9
9
  include Helpers::Multiple
10
+ include Helpers::Range
11
+
12
+ define_range_patterns(/-?\d+(?:\.\d+)?/)
10
13
 
11
14
  def initialize(**)
12
15
  super(**, delegate: ActiveModel::Type::Float)
13
16
  end
14
-
15
- def serialize(value)
16
- if value.is_a?(Range)
17
- if value.begin.nil?
18
- "<#{super(value.end)}"
19
- elsif value.end.nil?
20
- ">#{super(value.begin)}"
21
- else
22
- "#{super(value.begin)}..#{super(value.end)}"
23
- end
24
- else
25
- super
26
- end
27
- end
28
-
29
- private
30
-
31
- FLOAT = /(-?\d+(?:\.\d+)?)/
32
- SINGLE_VALUE = /\A#{FLOAT}\z/
33
- LOWER_BOUND = /\A>#{FLOAT}\z/
34
- UPPER_BOUND = /\A<#{FLOAT}\z/
35
- BOUNDED = /\A#{FLOAT}\.\.#{FLOAT}\z/
36
-
37
- def cast_value(value)
38
- case value
39
- when ::Range, ::Integer
40
- value
41
- when SINGLE_VALUE
42
- super($1)
43
- when LOWER_BOUND
44
- ((super($1))..)
45
- when UPPER_BOUND
46
- (..(super($1)))
47
- when BOUNDED
48
- ((super($1))..(super($2)))
49
- else
50
- super
51
- end
52
- end
53
17
  end
54
18
  end
55
19
  end
@@ -7,7 +7,7 @@ module Katalyst
7
7
  module Helpers
8
8
  # Lifts a delegating type from value to arrays of values
9
9
  module Delegate
10
- delegate :type, to: :@delegate
10
+ delegate :type, :deserialize, :serialize, to: :@delegate
11
11
 
12
12
  def initialize(delegate:, **arguments)
13
13
  super(**arguments)
@@ -17,30 +17,10 @@ module Katalyst
17
17
 
18
18
  using Extensions
19
19
 
20
- def deserialize(value)
21
- if multiple? && value.is_a?(::Array)
22
- value.map { |v| @delegate.deserialize(v) }
23
- else
24
- @delegate.deserialize(value)
25
- end
26
- end
27
-
28
- def serialize(value)
29
- if multiple? && value.is_a?(::Array)
30
- value.map { |v| @delegate.serialize(v) }
31
- else
32
- @delegate.serialize(value)
33
- end
34
- end
35
-
36
20
  private
37
21
 
38
22
  def cast_value(value)
39
- if multiple? && value.is_a?(::Array)
40
- value.map { |v| @delegate.cast(v) }
41
- else
42
- @delegate.cast(value)
43
- end
23
+ @delegate.cast(value)
44
24
  end
45
25
  end
46
26
  end
@@ -19,6 +19,20 @@ module Katalyst
19
19
  def filterable?
20
20
  false
21
21
  end
22
+
23
+ def examples_for(...)
24
+ []
25
+ end
26
+ end
27
+
28
+ refine(::ActiveModel::Attribute) do
29
+ def query_range=(range)
30
+ @query_range = range
31
+ end
32
+
33
+ def query_range
34
+ @query_range
35
+ end
22
36
  end
23
37
  end
24
38
  end
@@ -17,6 +17,36 @@ module Katalyst
17
17
  @multiple
18
18
  end
19
19
 
20
+ def cast(value)
21
+ return (multiple? ? [] : nil) if value.nil?
22
+
23
+ if multiple? && value.is_a?(::Array)
24
+ value.map { |v| super(v) }
25
+ elsif multiple?
26
+ [super]
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def deserialize(value)
33
+ if multiple? && value.is_a?(::Array)
34
+ value.map { |v| super(v) }.flatten
35
+ elsif multiple?
36
+ [super].flatten.compact
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def serialize(value)
43
+ if multiple? && value.is_a?(::Array)
44
+ value.map { |v| super(v) }.flatten
45
+ else
46
+ super
47
+ end
48
+ end
49
+
20
50
  using Extensions
21
51
 
22
52
  def default_value
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Collection
6
+ module Type
7
+ module Helpers
8
+ # Adds support for ranges
9
+ module Range
10
+ extend ActiveSupport::Concern
11
+
12
+ class_methods do
13
+ # @param single_value [Regex] pattern for accepting a single value
14
+ def define_range_patterns(single_value)
15
+ const_set(:SINGLE_VALUE, /\A(?<value>#{single_value})\z/)
16
+ const_set(:LOWER_BOUND, /\A(?<lower>#{single_value})\.\.\z/)
17
+ const_set(:UPPER_BOUND, /\A\.\.(?<upper>#{single_value})\z/)
18
+ const_set(:BOUNDED, /\A(?<lower>#{single_value})\.\.(?<upper>#{single_value})\z/)
19
+ end
20
+ end
21
+
22
+ def serialize(value)
23
+ if value.is_a?(::Range)
24
+ if value.begin.nil?
25
+ "..#{serialize(value.end)}"
26
+ elsif value.end.nil?
27
+ "#{serialize(value.begin)}.."
28
+ else
29
+ "#{serialize(value.begin)}..#{serialize(value.end)}"
30
+ end
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def cast(value)
37
+ case value
38
+ when nil
39
+ nil
40
+ when ::Range
41
+ value
42
+ when self.class.const_get(:SINGLE_VALUE)
43
+ super($~[:value])
44
+ when self.class.const_get(:LOWER_BOUND)
45
+ ((super($~[:lower]))..)
46
+ when self.class.const_get(:UPPER_BOUND)
47
+ (..(super($~[:upper])))
48
+ when self.class.const_get(:BOUNDED)
49
+ ((super($~[:lower]))..(super($~[:upper])))
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -7,49 +7,13 @@ module Katalyst
7
7
  class Integer < Value
8
8
  include Helpers::Delegate
9
9
  include Helpers::Multiple
10
+ include Helpers::Range
11
+
12
+ define_range_patterns(/-?\d+/)
10
13
 
11
14
  def initialize(**)
12
15
  super(**, delegate: ActiveModel::Type::Integer)
13
16
  end
14
-
15
- def serialize(value)
16
- if value.is_a?(Range)
17
- if value.begin.nil?
18
- "<#{super(value.end)}"
19
- elsif value.end.nil?
20
- ">#{super(value.begin)}"
21
- else
22
- "#{super(value.begin)}..#{super(value.end)}"
23
- end
24
- else
25
- super
26
- end
27
- end
28
-
29
- private
30
-
31
- INTEGER = /(-?\d+)/
32
- SINGLE_VALUE = /\A#{INTEGER}\z/
33
- LOWER_BOUND = /\A>#{INTEGER}\z/
34
- UPPER_BOUND = /\A<#{INTEGER}\z/
35
- BOUNDED = /\A#{INTEGER}\.\.#{INTEGER}\z/
36
-
37
- def cast_value(value)
38
- case value
39
- when ::Range, ::Integer
40
- value
41
- when SINGLE_VALUE
42
- super($1)
43
- when LOWER_BOUND
44
- ((super($1))..)
45
- when UPPER_BOUND
46
- (..(super($1)))
47
- when BOUNDED
48
- ((super($1))..(super($2)))
49
- else
50
- super
51
- end
52
- end
53
17
  end
54
18
  end
55
19
  end
@@ -21,7 +21,13 @@ module Katalyst
21
21
  end
22
22
 
23
23
  def filter?(attribute, value)
24
- filterable? && (value.present? || attribute.came_from_user?)
24
+ return false unless filterable?
25
+
26
+ if attribute.came_from_user?
27
+ attribute.value_before_type_cast.present?
28
+ else
29
+ value.present?
30
+ end
25
31
  end
26
32
 
27
33
  def filter(scope, attribute)
@@ -30,11 +36,25 @@ module Katalyst
30
36
  return scope unless filter?(attribute, value)
31
37
 
32
38
  scope, model, column = model_and_column_for(scope, attribute)
33
- condition = filter_condition(model, column, value)
39
+ condition = filter_condition(model, column, value)
34
40
 
35
41
  scope.merge(condition)
36
42
  end
37
43
 
44
+ def examples_for(scope, attribute)
45
+ scope, model, column = model_and_column_for(scope, attribute)
46
+
47
+ return unless model.attribute_types.has_key?(column)
48
+
49
+ filter(scope, attribute)
50
+ .group(column)
51
+ .distinct
52
+ .limit(10)
53
+ .reorder(column => :asc)
54
+ .pluck(column)
55
+ .map { |v| serialize(v) }
56
+ end
57
+
38
58
  private
39
59
 
40
60
  def filter_value(attribute)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: katalyst-tables
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.1
4
+ version: 3.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-21 00:00:00.000000000 Z
11
+ date: 2024-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: katalyst-html-attributes
@@ -54,9 +54,9 @@ files:
54
54
  - app/assets/builds/katalyst/tables.min.js
55
55
  - app/assets/builds/katalyst/tables.min.js.map
56
56
  - app/assets/config/katalyst-tables.js
57
- - app/assets/stylesheets/katalyst/tables/_filter.scss
58
57
  - app/assets/stylesheets/katalyst/tables/_index.scss
59
58
  - app/assets/stylesheets/katalyst/tables/_ordinal.scss
59
+ - app/assets/stylesheets/katalyst/tables/_query.scss
60
60
  - app/assets/stylesheets/katalyst/tables/_select.scss
61
61
  - app/assets/stylesheets/katalyst/tables/_summary.scss
62
62
  - app/assets/stylesheets/katalyst/tables/_table.scss
@@ -92,15 +92,17 @@ files:
92
92
  - app/components/katalyst/tables/data.rb
93
93
  - app/components/katalyst/tables/empty_caption_component.html.erb
94
94
  - app/components/katalyst/tables/empty_caption_component.rb
95
- - app/components/katalyst/tables/filter/modal_component.html.erb
96
- - app/components/katalyst/tables/filter/modal_component.rb
97
- - app/components/katalyst/tables/filter_component.html.erb
98
- - app/components/katalyst/tables/filter_component.rb
99
95
  - app/components/katalyst/tables/header_row_component.html.erb
100
96
  - app/components/katalyst/tables/header_row_component.rb
101
97
  - app/components/katalyst/tables/label.rb
102
98
  - app/components/katalyst/tables/orderable/form_component.rb
103
99
  - app/components/katalyst/tables/pagy_nav_component.rb
100
+ - app/components/katalyst/tables/query/input_component.html.erb
101
+ - app/components/katalyst/tables/query/input_component.rb
102
+ - app/components/katalyst/tables/query/modal_component.html.erb
103
+ - app/components/katalyst/tables/query/modal_component.rb
104
+ - app/components/katalyst/tables/query_component.html.erb
105
+ - app/components/katalyst/tables/query_component.rb
104
106
  - app/components/katalyst/tables/selectable/form_component.html.erb
105
107
  - app/components/katalyst/tables/selectable/form_component.rb
106
108
  - app/components/katalyst/tables/summary/body_component.html.erb
@@ -112,10 +114,11 @@ files:
112
114
  - app/controllers/concerns/katalyst/tables/backend.rb
113
115
  - app/helpers/katalyst/tables/frontend.rb
114
116
  - app/javascript/tables/application.js
115
- - app/javascript/tables/filter/modal_controller.js
116
117
  - app/javascript/tables/orderable/form_controller.js
117
118
  - app/javascript/tables/orderable/item_controller.js
118
119
  - app/javascript/tables/orderable/list_controller.js
120
+ - app/javascript/tables/query_controller.js
121
+ - app/javascript/tables/query_input_controller.js
119
122
  - app/javascript/tables/selection/form_controller.js
120
123
  - app/javascript/tables/selection/item_controller.js
121
124
  - app/models/concerns/katalyst/tables/collection/core.rb
@@ -140,6 +143,7 @@ files:
140
143
  - app/models/katalyst/tables/collection/type/helpers/delegate.rb
141
144
  - app/models/katalyst/tables/collection/type/helpers/extensions.rb
142
145
  - app/models/katalyst/tables/collection/type/helpers/multiple.rb
146
+ - app/models/katalyst/tables/collection/type/helpers/range.rb
143
147
  - app/models/katalyst/tables/collection/type/integer.rb
144
148
  - app/models/katalyst/tables/collection/type/query.rb
145
149
  - app/models/katalyst/tables/collection/type/search.rb
@@ -1,43 +0,0 @@
1
- [data-controller="tables--filter--modal"] {
2
- position: relative;
3
- }
4
-
5
- .filter-keys-modal {
6
- position: absolute;
7
- top: 100%;
8
- left: 0;
9
- right: 0;
10
- border: 1px solid rgba(0, 0, 0, 0.16);
11
- box-shadow:
12
- 0 3px 6px rgba(0, 0, 0, 0.16),
13
- 0 3px 6px rgba(0, 0, 0, 0.23);
14
- padding-inline: 1rem;
15
- padding-block: 0.5rem 0;
16
- margin-top: 0.5rem;
17
- background: white;
18
- border-radius: 4px;
19
- z-index: 1;
20
- opacity: 0;
21
- transition: opacity 0.125s;
22
- pointer-events: none;
23
-
24
- &[data-open] {
25
- opacity: 1;
26
- pointer-events: unset;
27
- }
28
-
29
- table {
30
- table-layout: fixed;
31
- }
32
-
33
- th.label,
34
- th.key {
35
- width: 15%;
36
- }
37
-
38
- .footer {
39
- display: flex;
40
- justify-content: flex-end;
41
- padding-block: 1rem;
42
- }
43
- }
@@ -1,25 +0,0 @@
1
- <%= tag.div(**html_attributes) do %>
2
- <table>
3
- <thead>
4
- <tr>
5
- <th class="label"></th>
6
- <th class="key">Key</th>
7
- <th class="values">Values</th>
8
- </tr>
9
- </thead>
10
- <tbody>
11
- <% attributes.each do |key, attribute| %>
12
- <tr>
13
- <th class="label"><%= collection.model.human_attribute_name(key) %></th>
14
- <td class="key"><%= key %></td>
15
- <td class="values"><%= values_for(key, attribute) %></td>
16
- </tr>
17
- <% end %>
18
- </tbody>
19
- </table>
20
- <% if footer? %>
21
- <div class="footer">
22
- <%= footer %>
23
- </div>
24
- <% end %>
25
- <% end %>
@@ -1,112 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Katalyst
4
- module Tables
5
- module Filter
6
- class ModalComponent < ViewComponent::Base
7
- include Katalyst::HtmlAttributes
8
- include Katalyst::Tables::Frontend
9
-
10
- DEFAULT_ATTRIBUTES = %w[page sort search query].freeze
11
-
12
- renders_one :footer
13
-
14
- attr_reader :collection, :url
15
-
16
- def initialize(collection:, **)
17
- super(**)
18
-
19
- @collection = collection
20
- end
21
-
22
- private
23
-
24
- def default_html_attributes
25
- {
26
- class: "filter-keys-modal",
27
- data: {
28
- tables__filter__modal_target: "modal",
29
- },
30
- }
31
- end
32
-
33
- using Collection::Type::Helpers::Extensions
34
-
35
- def attributes
36
- collection.class.attribute_types
37
- .select { |_, a| a.filterable? && a.type != :search }
38
- end
39
-
40
- def values_for(key, attribute)
41
- values_method = "#{key.parameterize.underscore}_values"
42
- if collection.respond_to?(values_method)
43
- return scope_values(attribute, values_method)
44
- end
45
-
46
- case attribute.type
47
- when :boolean
48
- render_options(true, false)
49
- when :date
50
- date_values
51
- when :integer
52
- integer_values(attribute)
53
- when :float
54
- float_values(attribute)
55
- when :enum
56
- enum_values(key)
57
- when :string
58
- string_values(attribute)
59
- end
60
- end
61
-
62
- def scope_values(attribute, values_method)
63
- values = collection.public_send(values_method)
64
- attribute.multiple? ? render_array(*values) : render_options(*values)
65
- end
66
-
67
- def date_values
68
- render_options("YYYY-MM-DD", ">YYYY-MM-DD", "<YYYY-MM-DD", "YYYY-MM-DD..YYYY-MM-DD")
69
- end
70
-
71
- def string_values(attribute)
72
- options = render_options("example", '"an example"')
73
- safe_join([options, attribute.exact? ? "(exact match)" : "(fuzzy match)"], " ")
74
- end
75
-
76
- def enum_values(key)
77
- enums = collection.model.defined_enums
78
-
79
- render_array(*enums[key].keys) if enums.has_key?(key)
80
- end
81
-
82
- def float_values(attribute)
83
- if attribute.multiple?
84
- render_array("0.5", "1", "...")
85
- else
86
- render_options("0.5", ">0.5", "<0.5", "-0.5..0.5")
87
- end
88
- end
89
-
90
- def integer_values(attribute)
91
- if attribute.multiple?
92
- render_array("0", "1", "...")
93
- else
94
- render_options("10", ">10", "<10", "0..10")
95
- end
96
- end
97
-
98
- def render_option(value)
99
- tag.code(value.to_s)
100
- end
101
-
102
- def render_options(*values)
103
- safe_join(values.map { |value| render_option(value) }, ", ")
104
- end
105
-
106
- def render_array(*values)
107
- safe_join(["[", render_options(*values), "]"])
108
- end
109
- end
110
- end
111
- end
112
- end
@@ -1,18 +0,0 @@
1
- <%= tag.div(**html_attributes) do %>
2
- <% if content? %>
3
- <%= content %>
4
- <% else %>
5
- <%= form do |form| %>
6
- <%= form.text_field(
7
- :query,
8
- type: :search,
9
- autocomplete: "off",
10
- **input_attributes,
11
- ) %>
12
-
13
- <%= form.button("Apply", type: :submit, name: nil) %>
14
- <% end %>
15
- <% end %>
16
-
17
- <%= modal %>
18
- <% end %>
@@ -1,13 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus";
2
-
3
- export default class FilterModalController extends Controller {
4
- static targets = ["modal"];
5
-
6
- close(e) {
7
- delete this.modalTarget.dataset.open;
8
- }
9
-
10
- open(e) {
11
- this.modalTarget.dataset.open = "true";
12
- }
13
- }