katalyst-tables 3.3.1 → 3.3.3

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 (62) 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 +13 -0
  15. data/app/components/katalyst/tables/query/input_component.rb +50 -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/config/locales/tables.en.yml +2 -0
  35. data/{app/models → lib}/katalyst/tables/collection/type/boolean.rb +11 -1
  36. data/lib/katalyst/tables/collection/type/date.rb +57 -0
  37. data/lib/katalyst/tables/collection/type/enum.rb +32 -0
  38. data/lib/katalyst/tables/collection/type/float.rb +21 -0
  39. data/lib/katalyst/tables/collection/type/helpers/delegate.rb +32 -0
  40. data/{app/models → lib}/katalyst/tables/collection/type/helpers/extensions.rb +14 -0
  41. data/lib/katalyst/tables/collection/type/helpers/multiple.rb +60 -0
  42. data/lib/katalyst/tables/collection/type/helpers/range.rb +59 -0
  43. data/lib/katalyst/tables/collection/type/integer.rb +21 -0
  44. data/{app/models → lib}/katalyst/tables/collection/type/value.rb +22 -2
  45. data/{app/models → lib}/katalyst/tables/collection/type.rb +16 -0
  46. data/lib/katalyst/tables/collection.rb +11 -0
  47. data/lib/katalyst/tables.rb +6 -0
  48. metadata +26 -21
  49. data/app/assets/stylesheets/katalyst/tables/_filter.scss +0 -43
  50. data/app/components/katalyst/tables/filter/modal_component.html.erb +0 -25
  51. data/app/components/katalyst/tables/filter/modal_component.rb +0 -112
  52. data/app/components/katalyst/tables/filter_component.html.erb +0 -18
  53. data/app/javascript/tables/filter/modal_controller.js +0 -13
  54. data/app/models/katalyst/tables/collection/type/date.rb +0 -60
  55. data/app/models/katalyst/tables/collection/type/enum.rb +0 -21
  56. data/app/models/katalyst/tables/collection/type/float.rb +0 -57
  57. data/app/models/katalyst/tables/collection/type/helpers/delegate.rb +0 -50
  58. data/app/models/katalyst/tables/collection/type/helpers/multiple.rb +0 -30
  59. data/app/models/katalyst/tables/collection/type/integer.rb +0 -57
  60. /data/{app/models → lib}/katalyst/tables/collection/type/query.rb +0 -0
  61. /data/{app/models → lib}/katalyst/tables/collection/type/search.rb +0 -0
  62. /data/{app/models → lib}/katalyst/tables/collection/type/string.rb +0 -0
@@ -0,0 +1,228 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class QueryInputController extends Controller {
4
+ static targets = ["input", "highlight"];
5
+ static values = { query: String };
6
+
7
+ connect() {
8
+ this.queryValue = this.inputTarget.value;
9
+ }
10
+
11
+ update() {
12
+ this.queryValue = this.inputTarget.value;
13
+ }
14
+
15
+ queryValueChanged(query) {
16
+ this.highlightTarget.innerHTML = "";
17
+
18
+ new Parser().parse(query).tokens.forEach((token) => {
19
+ this.highlightTarget.appendChild(token.render());
20
+ });
21
+ }
22
+ }
23
+
24
+ class Parser {
25
+ constructor() {
26
+ this.tokens = [];
27
+ this.values = null;
28
+ }
29
+
30
+ parse(input) {
31
+ const query = new StringScanner(input);
32
+
33
+ while (!query.isEos()) {
34
+ this.push(this.skipWhitespace(query));
35
+
36
+ const value = this.takeTagged(query) || this.takeUntagged(query);
37
+
38
+ if (!this.push(value)) break;
39
+ }
40
+
41
+ return this;
42
+ }
43
+
44
+ push(token) {
45
+ if (token) {
46
+ this.values ? this.values.push(token) : this.tokens.push(token);
47
+ }
48
+
49
+ return !!token;
50
+ }
51
+
52
+ skipWhitespace(query) {
53
+ if (!query.scan(/\s+/)) return;
54
+
55
+ return new Token(query.matched());
56
+ }
57
+
58
+ takeUntagged(query) {
59
+ if (!query.scan(/\S+/)) return;
60
+
61
+ return new Untagged(query.matched());
62
+ }
63
+
64
+ takeTagged(query) {
65
+ if (!query.scan(/(\w+(?:\.\w+)?)(:\s*)/)) return;
66
+
67
+ const key = query.valueAt(1);
68
+ const separator = query.valueAt(2);
69
+
70
+ const value =
71
+ this.takeArrayValue(query) || this.takeSingleValue(query) || new Token();
72
+
73
+ return new Tagged(key, separator, value);
74
+ }
75
+
76
+ takeArrayValue(query) {
77
+ if (!query.scan(/\[\s*/)) return;
78
+
79
+ const start = new Token(query.matched());
80
+ const values = (this.values = []);
81
+
82
+ while (!query.isEos()) {
83
+ if (!this.push(this.takeSingleValue(query))) break;
84
+ if (!this.push(this.takeDelimiter(query))) break;
85
+ }
86
+
87
+ query.scan(/\s*]/);
88
+ const end = new Token(query.matched());
89
+
90
+ this.values = null;
91
+
92
+ return new Array(start, values, end);
93
+ }
94
+
95
+ takeDelimiter(query) {
96
+ if (!query.scan(/\s*,\s*/)) return;
97
+
98
+ return new Token(query.matched());
99
+ }
100
+
101
+ takeSingleValue(query) {
102
+ return this.takeQuotedValue(query) || this.takeUnquotedValue(query);
103
+ }
104
+
105
+ takeQuotedValue(query) {
106
+ if (!query.scan(/"([^"]*)"/)) return;
107
+
108
+ return new Value(query.matched());
109
+ }
110
+
111
+ takeUnquotedValue(query) {
112
+ if (!query.scan(/[^ \],]*/)) return;
113
+
114
+ return new Value(query.matched());
115
+ }
116
+ }
117
+
118
+ class Token {
119
+ constructor(value = "") {
120
+ this.value = value;
121
+ }
122
+
123
+ render() {
124
+ return document.createTextNode(this.value);
125
+ }
126
+ }
127
+
128
+ class Value extends Token {
129
+ render() {
130
+ const span = document.createElement("span");
131
+ span.className = "value";
132
+ span.innerText = this.value;
133
+
134
+ return span;
135
+ }
136
+ }
137
+
138
+ class Tagged extends Token {
139
+ constructor(key, separator, value) {
140
+ super();
141
+
142
+ this.key = key;
143
+ this.separator = separator;
144
+ this.value = value;
145
+ }
146
+
147
+ render() {
148
+ const span = document.createElement("span");
149
+ span.className = "tag";
150
+
151
+ const key = document.createElement("span");
152
+ key.className = "key";
153
+ key.innerText = this.key;
154
+
155
+ span.appendChild(key);
156
+ span.appendChild(document.createTextNode(this.separator));
157
+ span.appendChild(this.value.render());
158
+
159
+ return span;
160
+ }
161
+ }
162
+
163
+ class Untagged extends Token {
164
+ render() {
165
+ const span = document.createElement("span");
166
+ span.className = "untagged";
167
+ span.innerText = this.value;
168
+ return span;
169
+ }
170
+ }
171
+
172
+ class Array extends Token {
173
+ constructor(start, values, end) {
174
+ super();
175
+
176
+ this.start = start;
177
+ this.values = values;
178
+ this.end = end;
179
+ }
180
+
181
+ render() {
182
+ const array = document.createElement("span");
183
+ array.className = "array-values";
184
+ array.appendChild(this.start.render());
185
+
186
+ this.values.forEach((value) => {
187
+ const span = document.createElement("span");
188
+ span.appendChild(value.render());
189
+ array.appendChild(span);
190
+ });
191
+
192
+ array.appendChild(this.end.render());
193
+
194
+ return array;
195
+ }
196
+ }
197
+
198
+ class StringScanner {
199
+ constructor(input) {
200
+ this.input = input;
201
+ this.position = 0;
202
+ this.last = null;
203
+ }
204
+
205
+ isEos() {
206
+ return this.position >= this.input.length;
207
+ }
208
+
209
+ scan(regex) {
210
+ const match = regex.exec(this.input.substring(this.position));
211
+ if (match?.index === 0) {
212
+ this.last = match;
213
+ this.position += match[0].length;
214
+ return true;
215
+ } else {
216
+ this.last = {};
217
+ return false;
218
+ }
219
+ }
220
+
221
+ matched() {
222
+ return this.last && this.last[0];
223
+ }
224
+
225
+ valueAt(index) {
226
+ return this.last && this.last[index];
227
+ }
228
+ }
@@ -46,7 +46,7 @@ module Katalyst
46
46
  end
47
47
 
48
48
  included do
49
- attr_accessor :items
49
+ attr_accessor :items, :unscoped_items
50
50
 
51
51
  delegate :each, :count, :empty?, to: :items, allow_nil: true
52
52
  end
@@ -73,7 +73,7 @@ module Katalyst
73
73
  end
74
74
 
75
75
  def apply(items)
76
- @items = items
76
+ @unscoped_items = @items = items
77
77
  reducers.build do |_|
78
78
  filter
79
79
  self
@@ -5,49 +5,36 @@ module Katalyst
5
5
  module Collection
6
6
  module Query
7
7
  class ArrayValueParser < ValueParser
8
+ def initialize(...)
9
+ super
10
+
11
+ @value = []
12
+ end
13
+
8
14
  # @param query [StringScanner]
9
15
  def parse(query)
10
16
  @query = query
11
17
 
12
- skip_whitespace
13
-
14
- if query.scan(/#{'\['}/)
15
- take_values
16
- else
17
- take_value
18
- end
19
- end
18
+ query.scan(/#{'\['}\s*/)
20
19
 
21
- def take_values
22
20
  until query.eos?
23
- skip_whitespace
24
21
  break unless take_quoted_value || take_unquoted_value
25
-
26
- skip_whitespace
27
22
  break unless take_delimiter
28
23
  end
29
24
 
30
- skip_whitespace
31
- take_end_of_list
32
- end
25
+ query.scan(/\s*#{'\]'}?/)
33
26
 
34
- def take_value
35
- take_quoted_value || take_unquoted_value
36
- end
27
+ @end = query.charpos
37
28
 
38
- def take_delimiter
39
- query.scan(/#{','}/)
29
+ self
40
30
  end
41
31
 
42
- def take_end_of_list
43
- query.scan(/#{']'}/)
32
+ def take_delimiter
33
+ query.scan(/\s*#{','}\s*/)
44
34
  end
45
35
 
46
36
  def value=(value)
47
- return if @attribute.type_cast(value).nil? # undefined attribute
48
-
49
- current = @collection.attributes[@attribute.name]
50
- @collection.assign_attributes(@attribute.name => current + [value])
37
+ @value << value
51
38
  end
52
39
  end
53
40
  end
@@ -7,11 +7,12 @@ module Katalyst
7
7
  class Parser # :nodoc:
8
8
  # query [StringScanner]
9
9
  attr_accessor :query
10
- attr_reader :collection, :untagged
10
+ attr_reader :collection, :untagged, :tagged
11
11
 
12
12
  def initialize(collection)
13
13
  @collection = collection
14
- @untagged = []
14
+ @tagged = {}
15
+ @untagged = []
15
16
  end
16
17
 
17
18
  # @param query [String]
@@ -25,10 +26,6 @@ module Katalyst
25
26
  break unless take_tagged || take_untagged
26
27
  end
27
28
 
28
- if untagged.any? && (search = collection.class.search_attribute)
29
- collection.assign_attributes(search => untagged.join(" "))
30
- end
31
-
32
29
  self
33
30
  end
34
31
 
@@ -39,12 +36,14 @@ module Katalyst
39
36
  end
40
37
 
41
38
  def take_tagged
39
+ start = query.charpos
40
+
42
41
  return unless query.scan(/(\w+(\.\w+)?):/)
43
42
 
44
43
  key, = query.values_at(1)
45
44
  skip_whitespace
46
45
 
47
- parser_for(key).parse(query)
46
+ tagged[key] = value_parser(start).parse(query)
48
47
  end
49
48
 
50
49
  def take_untagged
@@ -57,14 +56,18 @@ module Katalyst
57
56
 
58
57
  using Type::Helpers::Extensions
59
58
 
60
- def parser_for(key)
61
- attribute = collection.class._default_attributes[key]
62
-
63
- if attribute.type.multiple? || attribute.value.is_a?(::Array)
64
- ArrayValueParser.new(collection:, attribute:)
59
+ def value_parser(start)
60
+ if query.check(/#{'\['}\s*/)
61
+ ArrayValueParser.new(start:)
65
62
  else
66
- SingleValueParser.new(collection:, attribute:)
63
+ SingleValueParser.new(start:)
67
64
  end
65
+
66
+ # if attribute.type.multiple? || attribute.value.is_a?(::Array)
67
+ # ArrayValueParser.new(attribute:, pos:)
68
+ # else
69
+ # SingleValueParser.new(attribute:, pos:)
70
+ # end
68
71
  end
69
72
  end
70
73
  end
@@ -5,17 +5,25 @@ module Katalyst
5
5
  module Collection
6
6
  module Query
7
7
  class SingleValueParser < ValueParser
8
+ def initialize(...)
9
+ super
10
+
11
+ @value = nil
12
+ end
13
+
8
14
  # @param query [StringScanner]
9
15
  def parse(query)
10
16
  @query = query
11
17
 
12
18
  take_quoted_value || take_unquoted_value
19
+
20
+ @end = query.charpos
21
+
22
+ self
13
23
  end
14
24
 
15
25
  def value=(value)
16
- return if @attribute.type_cast(value).nil? # undefined attribute
17
-
18
- @collection.assign_attributes(@attribute.name => value)
26
+ @value = value
19
27
  end
20
28
  end
21
29
  end
@@ -5,11 +5,14 @@ module Katalyst
5
5
  module Collection
6
6
  module Query
7
7
  class ValueParser
8
- attr_accessor :query
8
+ attr_accessor :query, :value
9
9
 
10
- def initialize(collection:, attribute:)
11
- @collection = collection
12
- @attribute = attribute
10
+ def initialize(start:)
11
+ @start = start
12
+ end
13
+
14
+ def range
15
+ @start..@end
13
16
  end
14
17
 
15
18
  def take_quoted_value
@@ -19,7 +22,9 @@ module Katalyst
19
22
  end
20
23
 
21
24
  def take_unquoted_value
22
- return unless query.scan(/([^" \],]*)/)
25
+ # note, we allow unquoted values to begin with a " so that partial
26
+ # inputs can be accepted
27
+ return unless query.scan(/"?([^ \],]*)/)
23
28
 
24
29
  self.value, = query.values_at(1)
25
30
  end
@@ -13,6 +13,8 @@ module Katalyst
13
13
  _default_attributes.each_value do |attribute|
14
14
  return attribute.name if attribute.type.type == :search
15
15
  end
16
+
17
+ nil
16
18
  end
17
19
  end
18
20
 
@@ -20,14 +22,49 @@ module Katalyst
20
22
  attribute :q, :query, default: ""
21
23
  alias_attribute :query, :q
22
24
 
23
- # Note: this is defined inline so that we can overwrite query=
24
- def q=(value)
25
- query = super
25
+ attribute :p, :integer, filter: false
26
+ alias_attribute :position, :p
27
+ end
28
+
29
+ using Type::Helpers::Extensions
30
+
31
+ def examples_for(key)
32
+ key = key.to_s
33
+ values_method = "#{key.parameterize.underscore}_values"
34
+ if respond_to?(values_method)
35
+ public_send(values_method)
36
+ elsif @attributes.key?(key)
37
+ @attributes[key].type.examples_for(unscoped_items, @attributes[key])
38
+ end
39
+ end
40
+
41
+ def query_active?(attribute)
42
+ @attributes[attribute].query_range&.cover?(position)
43
+ end
44
+
45
+ private
26
46
 
27
- Parser.new(self).parse(query)
47
+ def _assign_attributes(new_attributes)
48
+ result = super
28
49
 
29
- query
50
+ if query_changed?
51
+ parser = Parser.new(self).parse(query)
52
+
53
+ parser.tagged.each do |k, p|
54
+ if @attributes.key?(k)
55
+ _assign_attribute(k, p.value)
56
+ @attributes[k].query_range = p.range
57
+ else
58
+ errors.add(k, :unknown)
59
+ end
60
+ end
61
+
62
+ if parser.untagged.any? && (search = self.class.search_attribute)
63
+ _assign_attribute(search, parser.untagged.join(" "))
64
+ end
30
65
  end
66
+
67
+ result
31
68
  end
32
69
  end
33
70
  end
@@ -35,6 +35,16 @@ module Katalyst
35
35
  { column:, direction: }
36
36
  end
37
37
  end
38
+
39
+ refine Symbol do
40
+ def to_param
41
+ to_s.to_param
42
+ end
43
+
44
+ def to_h
45
+ to_s.to_h
46
+ end
47
+ end
38
48
  end
39
49
 
40
50
  using SortParams
@@ -116,7 +126,7 @@ module Katalyst
116
126
 
117
127
  if collection.items.respond_to?(:"order_by_#{column}")
118
128
  collection.items = collection.items.reorder(nil).public_send(:"order_by_#{column}", direction.to_sym)
119
- elsif collection.items.model.has_attribute?(column)
129
+ elsif collection.model.has_attribute?(column)
120
130
  collection.items = collection.items.reorder(column => direction)
121
131
  end
122
132
 
@@ -48,6 +48,16 @@ module Katalyst
48
48
  self
49
49
  end
50
50
 
51
+ def apply(items)
52
+ if items.is_a?(Class) && items < ActiveRecord::Base
53
+ super(items.all)
54
+ elsif items.is_a?(ActiveRecord::Relation)
55
+ super
56
+ else
57
+ raise ArgumentError, "Collection requires an ActiveRecord scope, given #{items.class}"
58
+ end
59
+ end
60
+
51
61
  def inspect
52
62
  "#<#{self.class.name} @attributes=#{attributes.inspect} @model_name=\"#{model_name}\" @count=#{items&.count}>"
53
63
  end
@@ -69,6 +69,16 @@ module Katalyst
69
69
  end
70
70
  end
71
71
 
72
+ def apply(items)
73
+ if items.is_a?(Class) && items < ActiveRecord::Base
74
+ super(items.all)
75
+ elsif items.is_a?(ActiveRecord::Relation)
76
+ super
77
+ else
78
+ raise ArgumentError, "Collection requires an ActiveRecord scope, given #{items.class}"
79
+ end
80
+ end
81
+
72
82
  def inspect
73
83
  "#<#{self.class.name} @param_key=#{param_key.inspect} " +
74
84
  "@attributes=#{attributes.inspect} @model=\"#{model}\" @count=#{items&.count}>"
@@ -1,6 +1,8 @@
1
1
  en:
2
2
  katalyst:
3
3
  tables:
4
+ query:
5
+ placeholder: "Filter %{name}"
4
6
  orderable:
5
7
  value:
6
8
  "⠿"
@@ -13,7 +13,17 @@ module Katalyst
13
13
  end
14
14
 
15
15
  def filter?(attribute, value)
16
- (!value.nil? && !value.eql?([])) || attribute.came_from_user?
16
+ return false unless filterable?
17
+
18
+ if attribute.came_from_user?
19
+ attribute.value_before_type_cast.present? || value === false
20
+ else
21
+ !value.nil? && !value.eql?([])
22
+ end
23
+ end
24
+
25
+ def examples_for(...)
26
+ [true, false]
17
27
  end
18
28
  end
19
29
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Collection
6
+ module Type
7
+ class Date < Value
8
+ include Helpers::Range
9
+
10
+ define_range_patterns /\d{4}-\d\d-\d\d/
11
+
12
+ def type
13
+ :date
14
+ end
15
+
16
+ def serialize(value)
17
+ if value.is_a?(::Date)
18
+ value.to_fs(:db)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def examples_for(...)
25
+ [
26
+ ::Date.current,
27
+ ::Date.yesterday,
28
+ ::Date.current.beginning_of_week..,
29
+ ::Date.current.beginning_of_month..,
30
+ ::Date.current.beginning_of_year..,
31
+ ].map { |d| serialize(d) }
32
+ end
33
+
34
+ private
35
+
36
+ ISO_DATE = /\A(?<year>\d{4})-(?<month>\d\d)-(?<day>\d\d)\z/
37
+
38
+ def cast_value(value)
39
+ return value unless value.is_a?(::String)
40
+
41
+ if /\A(?<year>\d{4})-(?<month>\d\d)-(?<day>\d\d)\z/ =~ value
42
+ new_date(year.to_i, month.to_i, day.to_i)
43
+ end
44
+ end
45
+
46
+ def new_date(year, mon, mday)
47
+ return nil if year.nil? || (year.zero? && mon.zero? && mday.zero?)
48
+
49
+ ::Date.new(year, mon, mday)
50
+ rescue ArgumentError, TypeError
51
+ nil
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Collection
6
+ module Type
7
+ class Enum < Value
8
+ include Helpers::Multiple
9
+
10
+ def initialize(multiple: true, **)
11
+ super
12
+ end
13
+
14
+ def type
15
+ :enum
16
+ end
17
+
18
+ def examples_for(scope, attribute)
19
+ _, model, column = model_and_column_for(scope, attribute)
20
+ keys = model.defined_enums[column]&.keys
21
+
22
+ if attribute.value_before_type_cast.present?
23
+ keys.select { |key| key.include?(attribute.value_before_type_cast.last) }
24
+ else
25
+ keys
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Collection
6
+ module Type
7
+ class Float < Value
8
+ include Helpers::Delegate
9
+ include Helpers::Multiple
10
+ include Helpers::Range
11
+
12
+ define_range_patterns(/-?\d+(?:\.\d+)?/)
13
+
14
+ def initialize(**)
15
+ super(**, delegate: ActiveModel::Type::Float)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end