katalyst-tables 3.3.1 → 3.3.2

Sign up to get free protection for your applications and to get access to all the features.
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
- }