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 +4 -4
- data/app/assets/javascripts/controllers/tables/orderable/form_controller.js +11 -5
- data/app/assets/javascripts/controllers/tables/orderable/item_controller.js +94 -2
- data/app/assets/javascripts/controllers/tables/orderable/list_controller.js +211 -37
- data/app/components/concerns/katalyst/tables/has_table_content.rb +1 -1
- data/app/components/concerns/katalyst/tables/orderable.rb +51 -35
- data/app/components/concerns/katalyst/tables/turbo_replaceable.rb +24 -7
- data/app/components/katalyst/turbo/table_component.rb +4 -4
- data/lib/katalyst/tables/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8224ebb910f428c3f7750ae6e79c92f21440f5546a2baaede473bb286f217a82
|
4
|
+
data.tar.gz: 6be02c8a263dda22b7faae734cb20e8cec5879db532adb50f257bd502fa51265
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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(
|
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="${
|
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.
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
7
|
-
if (this.element !== event.target.parentElement) return;
|
6
|
+
//region State transitions
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
startDragging(dragState) {
|
9
|
+
this.dragState = dragState;
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
document.addEventListener("mousemove", this.mousemove);
|
12
|
+
document.addEventListener("mouseup", this.mouseup);
|
13
|
+
|
14
|
+
this.element.style.position = "relative";
|
14
15
|
}
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
stopDragging() {
|
18
|
+
const dragState = this.dragState;
|
19
|
+
delete this.dragState;
|
18
20
|
|
19
|
-
|
21
|
+
document.removeEventListener("mousemove", this.mousemove);
|
22
|
+
document.removeEventListener("mouseup", this.mouseup);
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
}
|
24
|
+
this.element.removeAttribute("style");
|
25
|
+
this.tablesOrderableItemOutlets.forEach((item) => item.reset());
|
24
26
|
|
25
|
-
|
26
|
-
event.preventDefault();
|
27
|
+
return dragState;
|
27
28
|
}
|
28
29
|
|
29
|
-
drop(
|
30
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
56
|
+
// save the changes
|
57
|
+
this.commitChanges();
|
36
58
|
}
|
37
59
|
|
38
|
-
|
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
|
44
|
-
if (item.
|
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
|
-
|
53
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
#
|
17
|
-
#
|
18
|
-
|
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
|
-
|
21
|
-
|
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
|
-
|
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
|
-
#
|
40
|
-
|
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
|
-
|
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,
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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(
|
102
|
-
"#{scope}[#{
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
18
|
-
end
|
19
|
+
@id = id
|
19
20
|
|
20
|
-
|
21
|
-
html_attributes[:id]
|
21
|
+
super(collection: collection, header: header, id: id, **options)
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
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.
|
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-
|
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
|