katalyst-tables 2.2.1 → 2.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 187059d630f338d5bcdd0ecc98eb3850fa7d4d01e6d37f0918143873c7a50033
4
- data.tar.gz: ac469214c3cdcf72b97ec5e5ab973356307d4d218d13face3ed48a82f9c09035
3
+ metadata.gz: 8224ebb910f428c3f7750ae6e79c92f21440f5546a2baaede473bb286f217a82
4
+ data.tar.gz: 6be02c8a263dda22b7faae734cb20e8cec5879db532adb50f257bd502fa51265
5
5
  SHA512:
6
- metadata.gz: 0a134c643f8780ff95287de876492b356adaa0e822b4fd72afe68980135859813cc5e9a1ac0c391bb227b27bc597a32c79266c1c88afcd005cc3186c3c30e27c
7
- data.tar.gz: 5eb871b2122d8edd2e95724f52d8a073ff4109e8281aadb1b7e8df231dd1f00d1096de80af370f8a3a6b42bfa8b9025254e29e2e16fb18ccc488c56222bee61e
6
+ metadata.gz: 61052d361a93ab2675274f9ed3b3f59e0acd08519261cf016c4db55006097d72f78114ba489e5eed9d066a82aa6bbaa9dd00675d378e24c996a5ccba775bce75
7
+ data.tar.gz: e411c014472cd5d34338ad0a02d869d69352fd4c6abe70c68ead05d75b3d2f0f52bf11c263031cd6606d27d09c7565e76770eee0b9149b58ef5e91ec383d86bf
@@ -1,20 +1,26 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
3
  export default class OrderableFormController extends Controller {
4
- add(name, value) {
4
+ add(item) {
5
+ const { id_name, id_value, index_name } = item.paramsValue;
5
6
  this.element.insertAdjacentHTML(
6
7
  "beforeend",
7
- `<input type="hidden" name="${name}" value="${value}" data-generated>`,
8
+ `<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
9
+ <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
8
10
  );
9
11
  }
10
12
 
11
13
  submit() {
14
+ if (this.inputs.length === 0) return;
15
+
12
16
  this.element.requestSubmit();
13
17
  }
14
18
 
15
19
  clear() {
16
- this.element
17
- .querySelectorAll("input[data-generated]")
18
- .forEach((input) => input.remove());
20
+ this.inputs.forEach((input) => input.remove());
21
+ }
22
+
23
+ get inputs() {
24
+ return this.element.querySelectorAll("input[data-generated]");
19
25
  }
20
26
  }
@@ -2,7 +2,99 @@ import { Controller } from "@hotwired/stimulus";
2
2
 
3
3
  export default class OrderableRowController extends Controller {
4
4
  static values = {
5
- name: String,
6
- value: Number,
5
+ params: Object,
7
6
  };
7
+
8
+ paramsValueChanged(params) {
9
+ this.id = params.id_value;
10
+ this.index = params.index_value;
11
+ }
12
+
13
+ dragUpdate(offset) {
14
+ this.dragOffset = offset;
15
+ this.row.style.position = "relative";
16
+ this.row.style.top = offset + "px";
17
+ this.row.style.zIndex = 1;
18
+ this.row.toggleAttribute("dragging", true);
19
+ }
20
+
21
+ /**
22
+ * Called on items that are not the dragged item during drag. Updates the
23
+ * visual position of the item relative to the dragged item.
24
+ *
25
+ * @param index {number} intended index of the item during drag
26
+ */
27
+ updateVisually(index) {
28
+ this.row.style.position = "relative";
29
+ this.row.style.top = `${
30
+ this.row.offsetHeight * (index - this.dragIndex)
31
+ }px`;
32
+ }
33
+
34
+ /**
35
+ * Set the index value of the item. This is called on all items after a drop
36
+ * event. If the index is different to the params index then this item has
37
+ * changed.
38
+ *
39
+ * @param index {number} the new index value
40
+ */
41
+ updateIndex(index) {
42
+ this.index = index;
43
+ }
44
+
45
+ /**
46
+ * Restore any visual changes made during drag and remove the drag state.
47
+ */
48
+ reset() {
49
+ delete this.dragOffset;
50
+ this.row.removeAttribute("style");
51
+ this.row.removeAttribute("dragging");
52
+ }
53
+
54
+ /**
55
+ * @returns {boolean} true when the item has a change to its index value
56
+ */
57
+ get hasChanges() {
58
+ return this.paramsValue.index_value !== this.index;
59
+ }
60
+
61
+ /**
62
+ * Calculate the relative index of the item during drag. This is used to
63
+ * sort items during drag as it takes into account any uncommitted changes
64
+ * to index caused by the drag offset.
65
+ *
66
+ * @returns {number} index for the purposes of drag and drop ordering
67
+ */
68
+ get dragIndex() {
69
+ if (this.dragOffset && this.dragOffset !== 0) {
70
+ return this.index + Math.round(this.dragOffset / this.row.offsetHeight);
71
+ } else {
72
+ return this.index;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Index value for use in comparisons during drag. This is used to determine
78
+ * whether the dragged item is above or below another item. If this item is
79
+ * being dragged then we offset the index by 0.5 to ensure that it jumps up
80
+ * or down when it reaches the midpoint of the item above or below it.
81
+ *
82
+ * @returns {number}
83
+ */
84
+ get comparisonIndex() {
85
+ if (this.dragOffset) {
86
+ return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);
87
+ } else {
88
+ return this.index;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * The containing row element.
94
+ *
95
+ * @returns {HTMLElement}
96
+ */
97
+ get row() {
98
+ return this.element.parentElement;
99
+ }
8
100
  }
@@ -3,71 +3,245 @@ import { Controller } from "@hotwired/stimulus";
3
3
  export default class OrderableListController extends Controller {
4
4
  static outlets = ["tables--orderable--item", "tables--orderable--form"];
5
5
 
6
- dragstart(event) {
7
- if (this.element !== event.target.parentElement) return;
6
+ //region State transitions
8
7
 
9
- const target = event.target;
10
- event.dataTransfer.effectAllowed = "move";
8
+ startDragging(dragState) {
9
+ this.dragState = dragState;
11
10
 
12
- // update element style after drag has begun
13
- setTimeout(() => (target.dataset.dragging = ""));
11
+ document.addEventListener("mousemove", this.mousemove);
12
+ document.addEventListener("mouseup", this.mouseup);
13
+
14
+ this.element.style.position = "relative";
14
15
  }
15
16
 
16
- dragover(event) {
17
- if (!this.dragItem) return;
17
+ stopDragging() {
18
+ const dragState = this.dragState;
19
+ delete this.dragState;
18
20
 
19
- swap(this.dropTarget(event.target), this.dragItem);
21
+ document.removeEventListener("mousemove", this.mousemove);
22
+ document.removeEventListener("mouseup", this.mouseup);
20
23
 
21
- event.preventDefault();
22
- return true;
23
- }
24
+ this.element.removeAttribute("style");
25
+ this.tablesOrderableItemOutlets.forEach((item) => item.reset());
24
26
 
25
- dragenter(event) {
26
- event.preventDefault();
27
+ return dragState;
27
28
  }
28
29
 
29
- drop(event) {
30
- if (!this.dragItem) return;
30
+ drop() {
31
+ // note: early returns guard against turbo updates that prevent us finding
32
+ // the right item to drop on. In this situation it's better to discard the
33
+ // drop than to drop in the wrong place.
34
+
35
+ const dragItem = this.dragItem;
36
+
37
+ if (!dragItem) return;
38
+
39
+ const newIndex = dragItem.dragIndex;
40
+ const targetItem = this.tablesOrderableItemOutlets[newIndex];
41
+
42
+ if (!targetItem) return;
31
43
 
32
- event.preventDefault();
33
- delete this.dragItem.dataset.dragging;
44
+ // swap the dragged item into the correct position for its current offset
45
+ if (newIndex < dragItem.index) {
46
+ targetItem.row.insertAdjacentElement("beforebegin", dragItem.row);
47
+ } else if (newIndex > dragItem.index) {
48
+ targetItem.row.insertAdjacentElement("afterend", dragItem.row);
49
+ }
50
+
51
+ // reindex all items based on their new positions
52
+ this.tablesOrderableItemOutlets.forEach((item, index) =>
53
+ item.updateIndex(index),
54
+ );
34
55
 
35
- this.update();
56
+ // save the changes
57
+ this.commitChanges();
36
58
  }
37
59
 
38
- update() {
60
+ commitChanges() {
39
61
  // clear any existing inputs to prevent duplicates
40
62
  this.tablesOrderableFormOutlet.clear();
41
63
 
42
64
  // insert any items that have changed position
43
- this.tablesOrderableItemOutlets.forEach((item, index) => {
44
- if (item.valueValue !== index) {
45
- this.tablesOrderableFormOutlet.add(item.nameValue, index);
46
- }
65
+ this.tablesOrderableItemOutlets.forEach((item) => {
66
+ if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);
47
67
  });
48
68
 
49
69
  this.tablesOrderableFormOutlet.submit();
50
70
  }
51
71
 
52
- get dragItem() {
53
- return this.element.querySelector("[data-dragging]");
72
+ //endregion
73
+
74
+ //region Events
75
+
76
+ mousedown(event) {
77
+ if (this.isDragging) return;
78
+
79
+ const target = this.#targetItem(event.target);
80
+
81
+ if (!target) return;
82
+
83
+ event.preventDefault(); // prevent built-in drag
84
+
85
+ this.startDragging(new DragState(this.element, event, target.id));
86
+
87
+ this.#updateDragItem(event);
54
88
  }
55
89
 
56
- dropTarget($e) {
57
- while ($e && $e.parentElement !== this.element) {
58
- $e = $e.parentElement;
90
+ mousemove = (event) => {
91
+ if (!this.isDragging) return;
92
+
93
+ event.preventDefault(); // prevent build-in drag
94
+
95
+ const dragItem = this.#updateDragItem(event);
96
+
97
+ // Visually updates the position of all items in the list relative to the
98
+ // dragged item. No actual changes to orderings at this stage.
99
+ this.#currentItems.forEach((item, index) => {
100
+ if (item === dragItem) return;
101
+ item.updateVisually(index);
102
+ });
103
+ };
104
+
105
+ mouseup = (event) => {
106
+ if (!this.isDragging) return;
107
+
108
+ this.drop();
109
+ this.stopDragging();
110
+ this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);
111
+ };
112
+
113
+ tablesOrderableFormOutletConnected(form, element) {
114
+ if (form.dragState) {
115
+ // restore the previous controller's state
116
+ this.startDragging(form.dragState);
59
117
  }
60
- return $e;
61
118
  }
119
+
120
+ tablesOrderableFormOutletDisconnected(form, element) {
121
+ if (this.isDragging) {
122
+ // cache drag state in the form
123
+ form.dragState = this.stopDragging();
124
+ }
125
+ }
126
+
127
+ //endregion
128
+
129
+ //region Helpers
130
+
131
+ get isDragging() {
132
+ return !!this.dragState;
133
+ }
134
+
135
+ get dragItem() {
136
+ if (!this.isDragging) return null;
137
+
138
+ return this.tablesOrderableItemOutlets.find(
139
+ (item) => item.id === this.dragState.targetId,
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Returns the current items in the list, sorted by their current index.
145
+ * Current uses the drag index if the item is being dragged, if set.
146
+ *
147
+ * @returns {Array[OrderableRowController]}
148
+ */
149
+ get #currentItems() {
150
+ return this.tablesOrderableItemOutlets.toSorted(
151
+ (a, b) => a.comparisonIndex - b.comparisonIndex,
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Returns the item outlet that was clicked on, if any.
157
+ *
158
+ * @param event {HTMLElement} the clicked ordinal cell
159
+ * @returns {OrderableRowController}
160
+ */
161
+ #targetItem(element) {
162
+ return this.tablesOrderableItemOutlets.find(
163
+ (item) => item.element === element,
164
+ );
165
+ }
166
+
167
+ #updateDragItem(event) {
168
+ const offset = this.dragState.itemOffset(
169
+ this.element,
170
+ this.dragItem.row,
171
+ event,
172
+ );
173
+ const item = this.dragItem;
174
+ item.dragUpdate(offset);
175
+ return item;
176
+ }
177
+
178
+ //endregion
62
179
  }
63
180
 
64
- function swap(target, item) {
65
- if (target && target !== item) {
66
- const positionComparison = target.compareDocumentPosition(item);
67
- if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {
68
- target.insertAdjacentElement("beforebegin", item);
69
- } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {
70
- target.insertAdjacentElement("afterend", item);
181
+ /**
182
+ * During drag we want to be able to translate a document-relative coordinate
183
+ * into a coordinate relative to the list element. This state object calculates
184
+ * and stores internal state so that we can translate absolute page coordinates
185
+ * from mouse events into relative offsets for the list items within the list
186
+ * element.
187
+ *
188
+ * We also keep track of the drag target so that if the controller is attached
189
+ * to a new element during the drag we can continue after the turbo update.
190
+ */
191
+ class DragState {
192
+ /**
193
+ * @param list {HTMLElement} the list controller's element (tbody)
194
+ * @param event {MouseEvent} the initial event
195
+ * @param id {String} the id of the element being dragged
196
+ */
197
+ constructor(list, event, id) {
198
+ // calculate the offset top of the tbody element relative to offsetParent
199
+ const offsetParent = list.offsetParent;
200
+ let offsetTop = event.offsetY;
201
+ let current = event.target;
202
+ while (current && current !== offsetParent) {
203
+ offsetTop += current.offsetTop;
204
+ current = current.offsetParent;
71
205
  }
206
+
207
+ // page offset is the offset of the tbody element relative to the page
208
+ this.pageOffset = event.pageY - offsetTop + list.offsetTop;
209
+
210
+ // cursor offset is the offset of the cursor relative to the drag item
211
+ this.cursorOffset = event.offsetY;
212
+
213
+ // initial offset is the offset position of the drag item at drag start
214
+ this.initialOffset = event.target.offsetTop - list.offsetTop;
215
+
216
+ // id of the item being dragged
217
+ this.targetId = id;
218
+ }
219
+
220
+ /**
221
+ * Calculates the offset of the drag item relative to its initial position.
222
+ *
223
+ * @param list {HTMLElement} the list controller's element (tbody)
224
+ * @param row {HTMLElement} the row being dragged
225
+ * @param event {MouseEvent} the current event
226
+ * @returns {number} relative offset for the item being dragged
227
+ */
228
+ itemOffset(list, row, event) {
229
+ // cursor position relative to the list
230
+ const cursorPosition = event.pageY - this.pageOffset;
231
+
232
+ // item position relative to the list
233
+ let itemPosition = cursorPosition - this.cursorOffset;
234
+
235
+ // ensure itemPosition is within the bounds of the list (tbody)
236
+ itemPosition = Math.max(itemPosition, 0);
237
+ itemPosition = Math.min(itemPosition, list.offsetHeight - row.offsetHeight);
238
+
239
+ // Item has position: relative, so we want to calculate the amount to move
240
+ // the item relative to it's DOM position to represent how much it has been
241
+ // dragged by.
242
+
243
+ // Convert itemPosition from offset relative to list to offset relative to
244
+ // its position within the DOM (if it hadn't moved).
245
+ return itemPosition - this.initialOffset;
72
246
  }
73
247
  }
@@ -26,7 +26,7 @@ module Katalyst
26
26
  def row_partial(row, record = nil)
27
27
  partial = @partial || model_name&.param_key&.to_s
28
28
  as = @as || model_name&.param_key&.to_sym
29
- render(partial: partial, variants: [:row], locals: { as => record, row: row })
29
+ render(partial: partial, variants: [:row], formats: [:html], locals: { as => record, row: row })
30
30
  end
31
31
  end
32
32
  end
@@ -13,32 +13,30 @@ module Katalyst
13
13
 
14
14
  using HasHtmlAttributes
15
15
 
16
- # Enhance a given table component class with orderable support.
17
- # Supports extension via `included` and `extended` hooks.
18
- def self.make_orderable(table_class)
16
+ # Support for inclusion in a table component class
17
+ # Adds an `orderable` slot and component configuration
18
+ included do
19
19
  # Add `orderable` slot to table component
20
- table_class.config_component :orderable, default: "FormComponent"
21
- table_class.renders_one(:orderable, lambda do |**attrs|
20
+ config_component :orderable, default: "Katalyst::Tables::Orderable::FormComponent"
21
+ renders_one(:orderable, lambda do |**attrs|
22
22
  orderable_component.new(table: self, **attrs)
23
23
  end)
24
24
  end
25
25
 
26
- # Support for inclusion in a table component class
27
- included do
28
- Orderable.make_orderable(self)
29
- end
30
-
31
26
  # Support for extending a table component instance
27
+ # Adds methods to the table component instance
32
28
  def self.extended(table)
33
- Orderable.make_orderable(table.class)
29
+ table.extend(TableMethods)
30
+
31
+ # ensure row components support orderable column calls
32
+ table.send(:add_orderable_columns)
34
33
  end
35
34
 
36
35
  def initialize(**attributes)
37
36
  super
38
37
 
39
- # Add `orderable` columns to row components
40
- header_row_component.include(HeaderRow)
41
- body_row_component.include(BodyRow)
38
+ # ensure row components support orderable column calls
39
+ add_orderable_columns
42
40
  end
43
41
 
44
42
  def tbody_attributes
@@ -47,16 +45,38 @@ module Katalyst
47
45
  super.merge_html(
48
46
  { data: { controller: LIST_CONTROLLER,
49
47
  action: <<~ACTIONS.squish,
50
- dragstart->#{LIST_CONTROLLER}#dragstart
51
- dragenter->#{LIST_CONTROLLER}#dragenter
52
- dragover->#{LIST_CONTROLLER}#dragover
53
- drop->#{LIST_CONTROLLER}#drop
48
+ mousedown->#{LIST_CONTROLLER}#mousedown
54
49
  ACTIONS
55
50
  "#{LIST_CONTROLLER}-#{FORM_CONTROLLER}-outlet" => "##{orderable.id}",
56
51
  "#{LIST_CONTROLLER}-#{ITEM_CONTROLLER}-outlet" => "td.ordinal" } },
57
52
  )
58
53
  end
59
54
 
55
+ private
56
+
57
+ # Add `orderable` columns to row components
58
+ def add_orderable_columns
59
+ header_row_component.include(HeaderRow)
60
+ body_row_component.include(BodyRow)
61
+ end
62
+
63
+ # Methods required to emulate a slot when extending an existing table.
64
+ module TableMethods
65
+ def with_orderable(**attrs)
66
+ @orderable = FormComponent.new(table: self, **attrs)
67
+
68
+ self
69
+ end
70
+
71
+ def orderable?
72
+ @orderable.present?
73
+ end
74
+
75
+ def orderable
76
+ @orderable
77
+ end
78
+ end
79
+
60
80
  module HeaderRow # :nodoc:
61
81
  def ordinal(attribute = :ordinal, **)
62
82
  cell(attribute, class: "ordinal", label: "")
@@ -64,23 +84,19 @@ module Katalyst
64
84
  end
65
85
 
66
86
  module BodyRow # :nodoc:
67
- def ordinal(attribute = :ordinal, id: :id)
68
- name = @table.orderable.record_scope(@record, id, attribute)
69
- value = @record.public_send(attribute)
70
- cell(attribute, class: "ordinal", data: {
71
- controller: ITEM_CONTROLLER,
72
- "#{ITEM_CONTROLLER}-name-value" => name,
73
- "#{ITEM_CONTROLLER}-value-value" => value,
87
+ def ordinal(attribute = :ordinal, primary_key: :id)
88
+ id = @record.public_send(primary_key)
89
+ params = {
90
+ id_name: @table.orderable.record_scope(id, primary_key),
91
+ id_value: id,
92
+ index_name: @table.orderable.record_scope(id, attribute),
93
+ index_value: @record.public_send(attribute),
94
+ }
95
+ cell(attribute, class: "ordinal", draggable: true, data: {
96
+ controller: ITEM_CONTROLLER,
97
+ "#{ITEM_CONTROLLER}-params-value": params.to_json,
74
98
  }) { t("katalyst.tables.orderable.value") }
75
99
  end
76
-
77
- def html_attributes
78
- super.merge_html(
79
- {
80
- draggable: "true",
81
- },
82
- )
83
- end
84
100
  end
85
101
 
86
102
  class FormComponent < ViewComponent::Base # :nodoc:
@@ -98,8 +114,8 @@ module Katalyst
98
114
  @scope = scope
99
115
  end
100
116
 
101
- def record_scope(record, id, attribute)
102
- "#{scope}[#{record.public_send(id)}][#{attribute}]"
117
+ def record_scope(id, attribute)
118
+ "#{scope}[#{id}][#{attribute}]"
103
119
  end
104
120
 
105
121
  def call
@@ -13,10 +13,16 @@ module Katalyst
13
13
 
14
14
  include ::Turbo::StreamsHelper
15
15
 
16
+ # Is turbo rendering enabled for this component?
16
17
  def turbo?
17
18
  @turbo
18
19
  end
19
20
 
21
+ # Are we rendering a turbo stream response?
22
+ def turbo_stream_response?
23
+ response.media_type.eql?("text/vnd.turbo-stream.html")
24
+ end
25
+
20
26
  def initialize(turbo: true, **options)
21
27
  super(**options)
22
28
 
@@ -44,16 +50,27 @@ module Katalyst
44
50
  super
45
51
 
46
52
  redefinition_lock.synchronize do
47
- component_class.alias_method(:vc_render_template_for, :render_template_for)
48
- component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
49
- def render_template_for(variant = nil)
50
- if turbo? && response.media_type.eql?("text/vnd.turbo-stream.html")
51
- turbo_stream.replace(id, vc_render_template_for(variant))
53
+ # Capture the instance method added by the default compiler and
54
+ # wrap it in a turbo stream replacement. Take care to ensure that
55
+ # subclasses of this component don't break delegation, as each
56
+ # subclass of ViewComponent::Base defines its own version of this
57
+ # method.
58
+ vc_render_template = component_class.instance_method(:render_template_for)
59
+ component_class.define_method(:render_template_for) do |variant = nil|
60
+ # VC discards the output from this method and uses the buffer
61
+ # if both are set. Capture and wrap the output.
62
+ content = capture { vc_render_template.bind_call(self, variant) }
63
+ # In turbo mode, replace the inner-most element using a turbo
64
+ # stream. Note that we only want one turbo stream per component
65
+ # from this mechanism, as subclasses may want to concat their
66
+ # own additional streams.
67
+ if turbo? && turbo_stream_response? && !@streamed
68
+ @streamed = true
69
+ concat(turbo_stream.replace(id, content))
52
70
  else
53
- vc_render_template_for(variant)
71
+ concat(content)
54
72
  end
55
73
  end
56
- RUBY
57
74
  end
58
75
  end
59
76
  end
@@ -7,6 +7,8 @@ module Katalyst
7
7
  class TableComponent < ::Katalyst::TableComponent
8
8
  include Tables::TurboReplaceable
9
9
 
10
+ attr_reader :id
11
+
10
12
  def initialize(collection:, id:, header: true, **options)
11
13
  header = if header.is_a?(Hash)
12
14
  default_header_options.merge(header)
@@ -14,11 +16,9 @@ module Katalyst
14
16
  default_header_options
15
17
  end
16
18
 
17
- super(collection: collection, header: header, id: id, **options)
18
- end
19
+ @id = id
19
20
 
20
- def id
21
- html_attributes[:id]
21
+ super(collection: collection, header: header, id: id, **options)
22
22
  end
23
23
 
24
24
  private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Katalyst
4
4
  module Tables
5
- VERSION = "2.2.1"
5
+ VERSION = "2.2.3"
6
6
  end
7
7
  end
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: 2.2.1
4
+ version: 2.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-20 00:00:00.000000000 Z
11
+ date: 2023-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: html-attributes-utils