katalyst-tables 2.2.11 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +10 -0
- data/app/assets/builds/katalyst/tables.esm.js +496 -0
- data/app/assets/builds/katalyst/tables.js +496 -0
- data/app/assets/builds/katalyst/tables.min.js +2 -0
- data/app/assets/builds/katalyst/tables.min.js.map +1 -0
- data/app/assets/config/katalyst-tables.js +1 -1
- data/app/components/concerns/katalyst/tables/orderable.rb +1 -1
- data/app/javascript/tables/application.js +25 -0
- data/app/{assets/javascripts/controllers → javascript}/tables/orderable/form_controller.js +1 -1
- data/app/{assets/javascripts/controllers → javascript}/tables/orderable/list_controller.js +6 -6
- data/config/importmap.rb +1 -4
- data/lib/katalyst/tables/backend.rb +1 -1
- data/lib/katalyst/tables/engine.rb +4 -2
- metadata +13 -8
- /data/app/{assets/javascripts/controllers → javascript}/tables/orderable/item_controller.js +0 -0
- /data/app/{assets/javascripts/controllers/tables → javascript/tables/turbo}/turbo_collection_controller.js +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f02c8db5c8d61ac1a1e678ac9e4c020826c2289dc25a584b82a7786e859d9023
|
4
|
+
data.tar.gz: 9d32b27a803509807facf9981f8b53eb8e696f5daa6e816427e32bc5d7c1bd03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1c10b35c516f19094d4447db4fdb597847d650dce403a7f1062e04a3d35b17a2fb706e6e10f253b14300999e3570057bbc287d414c163c5e4440b5ac389349c
|
7
|
+
data.tar.gz: c5ff9bbe6c0f0e159e8254eb97f92805528fc7a5d2f4b53f616d0a1a13211d512d421f10843568026b6b6788d8c5753a96444fd7d5ec7785efb508b20df03d56
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -14,6 +14,16 @@ And then execute:
|
|
14
14
|
|
15
15
|
$ bundle install
|
16
16
|
|
17
|
+
Add the Gem's javascript and CSS to your build pipeline. This assumes that
|
18
|
+
you're using `rails-dartsass` and `importmaps` to manage your assets.
|
19
|
+
|
20
|
+
```javascript
|
21
|
+
// app/javascript/controllers/application.js
|
22
|
+
import { application } from "controllers/application";
|
23
|
+
import tables from "@katalyst/tables";
|
24
|
+
application.load(tables);
|
25
|
+
```
|
26
|
+
|
17
27
|
## Usage
|
18
28
|
|
19
29
|
This gem provides entry points for backend and frontend concerns:
|
@@ -0,0 +1,496 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
2
|
+
import { Turbo } from '@hotwired/turbo-rails';
|
3
|
+
|
4
|
+
class TurboCollectionController extends Controller {
|
5
|
+
static values = {
|
6
|
+
query: String,
|
7
|
+
sort: String,
|
8
|
+
};
|
9
|
+
|
10
|
+
queryValueChanged(query) {
|
11
|
+
Turbo.navigator.history.replace(this.#url(query));
|
12
|
+
}
|
13
|
+
|
14
|
+
sortValueChanged(sort) {
|
15
|
+
document.querySelectorAll(this.#sortSelector).forEach((input) => {
|
16
|
+
if (input) input.value = sort;
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
get #sortSelector() {
|
21
|
+
return "input[name='sort']";
|
22
|
+
}
|
23
|
+
|
24
|
+
#url(query) {
|
25
|
+
const frame = this.element.closest("turbo-frame");
|
26
|
+
let url;
|
27
|
+
|
28
|
+
if (frame) {
|
29
|
+
url = new URL(frame.baseURI);
|
30
|
+
} else {
|
31
|
+
url = new URL(window.location.href);
|
32
|
+
}
|
33
|
+
|
34
|
+
url.search = query;
|
35
|
+
|
36
|
+
return url;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
class OrderableRowController extends Controller {
|
41
|
+
static values = {
|
42
|
+
params: Object,
|
43
|
+
};
|
44
|
+
|
45
|
+
connect() {
|
46
|
+
// index from server may be inconsistent with the visual ordering,
|
47
|
+
// especially if this is a new node. Use positional indexes instead,
|
48
|
+
// as these are the values we will send on save.
|
49
|
+
this.index = domIndex(this.row);
|
50
|
+
}
|
51
|
+
|
52
|
+
paramsValueChanged(params) {
|
53
|
+
this.id = params.id_value;
|
54
|
+
}
|
55
|
+
|
56
|
+
dragUpdate(offset) {
|
57
|
+
this.dragOffset = offset;
|
58
|
+
this.row.style.position = "relative";
|
59
|
+
this.row.style.top = offset + "px";
|
60
|
+
this.row.style.zIndex = "1";
|
61
|
+
this.row.toggleAttribute("dragging", true);
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Called on items that are not the dragged item during drag. Updates the
|
66
|
+
* visual position of the item relative to the dragged item.
|
67
|
+
*
|
68
|
+
* @param index {number} intended index of the item during drag
|
69
|
+
*/
|
70
|
+
updateVisually(index) {
|
71
|
+
this.row.style.position = "relative";
|
72
|
+
this.row.style.top = `${
|
73
|
+
this.row.offsetHeight * (index - this.dragIndex)
|
74
|
+
}px`;
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Set the index value of the item. This is called on all items after a drop
|
79
|
+
* event. If the index is different to the params index then this item has
|
80
|
+
* changed.
|
81
|
+
*
|
82
|
+
* @param index {number} the new index value
|
83
|
+
*/
|
84
|
+
updateIndex(index) {
|
85
|
+
this.index = index;
|
86
|
+
}
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Restore any visual changes made during drag and remove the drag state.
|
90
|
+
*/
|
91
|
+
reset() {
|
92
|
+
delete this.dragOffset;
|
93
|
+
this.row.removeAttribute("style");
|
94
|
+
this.row.removeAttribute("dragging");
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* @returns {boolean} true when the item has a change to its index value
|
99
|
+
*/
|
100
|
+
get hasChanges() {
|
101
|
+
return this.paramsValue.index_value !== this.index;
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Calculate the relative index of the item during drag. This is used to
|
106
|
+
* sort items during drag as it takes into account any uncommitted changes
|
107
|
+
* to index caused by the drag offset.
|
108
|
+
*
|
109
|
+
* @returns {number} index for the purposes of drag and drop ordering
|
110
|
+
*/
|
111
|
+
get dragIndex() {
|
112
|
+
if (this.dragOffset && this.dragOffset !== 0) {
|
113
|
+
return this.index + Math.round(this.dragOffset / this.row.offsetHeight);
|
114
|
+
} else {
|
115
|
+
return this.index;
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Index value for use in comparisons during drag. This is used to determine
|
121
|
+
* whether the dragged item is above or below another item. If this item is
|
122
|
+
* being dragged then we offset the index by 0.5 to ensure that it jumps up
|
123
|
+
* or down when it reaches the midpoint of the item above or below it.
|
124
|
+
*
|
125
|
+
* @returns {number}
|
126
|
+
*/
|
127
|
+
get comparisonIndex() {
|
128
|
+
if (this.dragOffset) {
|
129
|
+
return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);
|
130
|
+
} else {
|
131
|
+
return this.index;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* The containing row element.
|
137
|
+
*
|
138
|
+
* @returns {HTMLElement}
|
139
|
+
*/
|
140
|
+
get row() {
|
141
|
+
return this.element.parentElement;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
function domIndex(element) {
|
146
|
+
return Array.from(element.parentElement.children).indexOf(element);
|
147
|
+
}
|
148
|
+
|
149
|
+
class OrderableListController extends Controller {
|
150
|
+
static outlets = ["tables--orderable--item", "tables--orderable--form"];
|
151
|
+
|
152
|
+
//region State transitions
|
153
|
+
|
154
|
+
startDragging(dragState) {
|
155
|
+
this.dragState = dragState;
|
156
|
+
|
157
|
+
document.addEventListener("mousemove", this.mousemove);
|
158
|
+
document.addEventListener("mouseup", this.mouseup);
|
159
|
+
window.addEventListener("scroll", this.scroll, true);
|
160
|
+
|
161
|
+
this.element.style.position = "relative";
|
162
|
+
}
|
163
|
+
|
164
|
+
stopDragging() {
|
165
|
+
const dragState = this.dragState;
|
166
|
+
delete this.dragState;
|
167
|
+
|
168
|
+
document.removeEventListener("mousemove", this.mousemove);
|
169
|
+
document.removeEventListener("mouseup", this.mouseup);
|
170
|
+
window.removeEventListener("scroll", this.scroll, true);
|
171
|
+
|
172
|
+
this.element.removeAttribute("style");
|
173
|
+
this.tablesOrderableItemOutlets.forEach((item) => item.reset());
|
174
|
+
|
175
|
+
return dragState;
|
176
|
+
}
|
177
|
+
|
178
|
+
drop() {
|
179
|
+
// note: early returns guard against turbo updates that prevent us finding
|
180
|
+
// the right item to drop on. In this situation it's better to discard the
|
181
|
+
// drop than to drop in the wrong place.
|
182
|
+
|
183
|
+
const dragItem = this.dragItem;
|
184
|
+
|
185
|
+
if (!dragItem) return;
|
186
|
+
|
187
|
+
const newIndex = dragItem.dragIndex;
|
188
|
+
const targetItem = this.tablesOrderableItemOutlets[newIndex];
|
189
|
+
|
190
|
+
if (!targetItem) return;
|
191
|
+
|
192
|
+
// swap the dragged item into the correct position for its current offset
|
193
|
+
if (newIndex < dragItem.index) {
|
194
|
+
targetItem.row.insertAdjacentElement("beforebegin", dragItem.row);
|
195
|
+
} else if (newIndex > dragItem.index) {
|
196
|
+
targetItem.row.insertAdjacentElement("afterend", dragItem.row);
|
197
|
+
}
|
198
|
+
|
199
|
+
// reindex all items based on their new positions
|
200
|
+
this.tablesOrderableItemOutlets.forEach((item, index) =>
|
201
|
+
item.updateIndex(index)
|
202
|
+
);
|
203
|
+
|
204
|
+
// save the changes
|
205
|
+
this.commitChanges();
|
206
|
+
}
|
207
|
+
|
208
|
+
commitChanges() {
|
209
|
+
// clear any existing inputs to prevent duplicates
|
210
|
+
this.tablesOrderableFormOutlet.clear();
|
211
|
+
|
212
|
+
// insert any items that have changed position
|
213
|
+
this.tablesOrderableItemOutlets.forEach((item) => {
|
214
|
+
if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);
|
215
|
+
});
|
216
|
+
|
217
|
+
this.tablesOrderableFormOutlet.submit();
|
218
|
+
}
|
219
|
+
|
220
|
+
//endregion
|
221
|
+
|
222
|
+
//region Events
|
223
|
+
|
224
|
+
mousedown(event) {
|
225
|
+
if (this.isDragging) return;
|
226
|
+
|
227
|
+
const target = this.#targetItem(event.target);
|
228
|
+
|
229
|
+
if (!target) return;
|
230
|
+
|
231
|
+
event.preventDefault(); // prevent built-in drag
|
232
|
+
|
233
|
+
this.startDragging(new DragState(this.element, event, target.id));
|
234
|
+
|
235
|
+
this.dragState.updateCursor(this.element, target.row, event, this.animate);
|
236
|
+
}
|
237
|
+
|
238
|
+
mousemove = (event) => {
|
239
|
+
if (!this.isDragging) return;
|
240
|
+
|
241
|
+
event.preventDefault(); // prevent build-in drag
|
242
|
+
|
243
|
+
if (this.ticking) return;
|
244
|
+
|
245
|
+
this.ticking = true;
|
246
|
+
|
247
|
+
window.requestAnimationFrame(() => {
|
248
|
+
this.ticking = false;
|
249
|
+
this.dragState.updateCursor(
|
250
|
+
this.element,
|
251
|
+
this.dragItem.row,
|
252
|
+
event,
|
253
|
+
this.animate
|
254
|
+
);
|
255
|
+
});
|
256
|
+
};
|
257
|
+
|
258
|
+
scroll = (event) => {
|
259
|
+
if (!this.isDragging || this.ticking) return;
|
260
|
+
|
261
|
+
this.ticking = true;
|
262
|
+
|
263
|
+
window.requestAnimationFrame(() => {
|
264
|
+
this.ticking = false;
|
265
|
+
this.dragState.updateScroll(
|
266
|
+
this.element,
|
267
|
+
this.dragItem.row,
|
268
|
+
this.animate
|
269
|
+
);
|
270
|
+
});
|
271
|
+
};
|
272
|
+
|
273
|
+
mouseup = (event) => {
|
274
|
+
if (!this.isDragging) return;
|
275
|
+
|
276
|
+
this.drop();
|
277
|
+
this.stopDragging();
|
278
|
+
this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);
|
279
|
+
};
|
280
|
+
|
281
|
+
tablesOrderableFormOutletConnected(form, element) {
|
282
|
+
if (form.dragState) {
|
283
|
+
// restore the previous controller's state
|
284
|
+
this.startDragging(form.dragState);
|
285
|
+
}
|
286
|
+
}
|
287
|
+
|
288
|
+
tablesOrderableFormOutletDisconnected(form, element) {
|
289
|
+
if (this.isDragging) {
|
290
|
+
// cache drag state in the form
|
291
|
+
form.dragState = this.stopDragging();
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
//endregion
|
296
|
+
|
297
|
+
//region Helpers
|
298
|
+
|
299
|
+
/**
|
300
|
+
* Updates the position of the drag item with a relative offset. Updates
|
301
|
+
* other items relative to the new position of the drag item, as required.
|
302
|
+
*
|
303
|
+
* @callback {OrderableListController~animate}
|
304
|
+
* @param {number} offset
|
305
|
+
*/
|
306
|
+
animate = (offset) => {
|
307
|
+
const dragItem = this.dragItem;
|
308
|
+
|
309
|
+
// Visually update the dragItem so it follows the cursor
|
310
|
+
dragItem.dragUpdate(offset);
|
311
|
+
|
312
|
+
// Visually updates the position of all items in the list relative to the
|
313
|
+
// dragged item. No actual changes to orderings at this stage.
|
314
|
+
this.#currentItems.forEach((item, index) => {
|
315
|
+
if (item === dragItem) return;
|
316
|
+
item.updateVisually(index);
|
317
|
+
});
|
318
|
+
};
|
319
|
+
|
320
|
+
get isDragging() {
|
321
|
+
return !!this.dragState;
|
322
|
+
}
|
323
|
+
|
324
|
+
get dragItem() {
|
325
|
+
if (!this.isDragging) return null;
|
326
|
+
|
327
|
+
return this.tablesOrderableItemOutlets.find(
|
328
|
+
(item) => item.id === this.dragState.targetId
|
329
|
+
);
|
330
|
+
}
|
331
|
+
|
332
|
+
/**
|
333
|
+
* Returns the current items in the list, sorted by their current index.
|
334
|
+
* Current uses the drag index if the item is being dragged, if set.
|
335
|
+
*
|
336
|
+
* @returns {Array[OrderableRowController]}
|
337
|
+
*/
|
338
|
+
get #currentItems() {
|
339
|
+
return this.tablesOrderableItemOutlets.toSorted(
|
340
|
+
(a, b) => a.comparisonIndex - b.comparisonIndex
|
341
|
+
);
|
342
|
+
}
|
343
|
+
|
344
|
+
/**
|
345
|
+
* Returns the item outlet that was clicked on, if any.
|
346
|
+
*
|
347
|
+
* @param element {HTMLElement} the clicked ordinal cell
|
348
|
+
* @returns {OrderableRowController}
|
349
|
+
*/
|
350
|
+
#targetItem(element) {
|
351
|
+
return this.tablesOrderableItemOutlets.find(
|
352
|
+
(item) => item.element === element
|
353
|
+
);
|
354
|
+
}
|
355
|
+
|
356
|
+
//endregion
|
357
|
+
}
|
358
|
+
|
359
|
+
/**
|
360
|
+
* During drag we want to be able to translate a document-relative coordinate
|
361
|
+
* into a coordinate relative to the list element. This state object calculates
|
362
|
+
* and stores internal state so that we can translate absolute page coordinates
|
363
|
+
* from mouse events into relative offsets for the list items within the list
|
364
|
+
* element.
|
365
|
+
*
|
366
|
+
* We also keep track of the drag target so that if the controller is attached
|
367
|
+
* to a new element during the drag we can continue after the turbo update.
|
368
|
+
*/
|
369
|
+
class DragState {
|
370
|
+
/**
|
371
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
372
|
+
* @param event {MouseEvent} the initial event
|
373
|
+
* @param id {String} the id of the element being dragged
|
374
|
+
*/
|
375
|
+
constructor(list, event, id) {
|
376
|
+
// cursor offset is the offset of the cursor relative to the drag item
|
377
|
+
this.cursorOffset = event.offsetY;
|
378
|
+
|
379
|
+
// initial offset is the offset position of the drag item at drag start
|
380
|
+
this.initialPosition = event.target.offsetTop - list.offsetTop;
|
381
|
+
|
382
|
+
// id of the item being dragged
|
383
|
+
this.targetId = id;
|
384
|
+
}
|
385
|
+
|
386
|
+
/**
|
387
|
+
* Calculates the offset of the drag item relative to its initial position.
|
388
|
+
*
|
389
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
390
|
+
* @param row {HTMLElement} the row being dragged
|
391
|
+
* @param event {MouseEvent} the current event
|
392
|
+
* @param callback {OrderableListController~animate} updates the drag item with a relative offset
|
393
|
+
*/
|
394
|
+
updateCursor(list, row, event, callback) {
|
395
|
+
// Calculate and store the list offset relative to the viewport
|
396
|
+
// This value is cached so we can calculate the outcome of any scroll events
|
397
|
+
this.listOffset = list.getBoundingClientRect().top;
|
398
|
+
|
399
|
+
// Calculate the position of the cursor relative to the list.
|
400
|
+
// Accounts for scroll offsets by using the item's bounding client rect.
|
401
|
+
const cursorPosition = event.clientY - this.listOffset;
|
402
|
+
|
403
|
+
// intended item position relative to the list, from cursor position
|
404
|
+
let itemPosition = cursorPosition - this.cursorOffset;
|
405
|
+
|
406
|
+
this.#updateItemPosition(list, row, itemPosition, callback);
|
407
|
+
}
|
408
|
+
|
409
|
+
/**
|
410
|
+
* Animates the item's position as the list scrolls. Requires a previous call
|
411
|
+
* to set the scroll offset.
|
412
|
+
*
|
413
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
414
|
+
* @param row {HTMLElement} the row being dragged
|
415
|
+
* @param callback {OrderableListController~animate} updates the drag item with a relative offset
|
416
|
+
*/
|
417
|
+
updateScroll(list, row, callback) {
|
418
|
+
const previousScrollOffset = this.listOffset;
|
419
|
+
|
420
|
+
// Calculate and store the list offset relative to the viewport
|
421
|
+
// This value is cached so we can calculate the outcome of any scroll events
|
422
|
+
this.listOffset = list.getBoundingClientRect().top;
|
423
|
+
|
424
|
+
// Calculate the change in scroll offset since the last update
|
425
|
+
const scrollDelta = previousScrollOffset - this.listOffset;
|
426
|
+
|
427
|
+
// intended item position relative to the list, from cursor position
|
428
|
+
const position = this.position + scrollDelta;
|
429
|
+
|
430
|
+
this.#updateItemPosition(list, row, position, callback);
|
431
|
+
}
|
432
|
+
|
433
|
+
#updateItemPosition(list, row, position, callback) {
|
434
|
+
// ensure itemPosition is within the bounds of the list (tbody)
|
435
|
+
position = Math.max(position, 0);
|
436
|
+
position = Math.min(position, list.offsetHeight - row.offsetHeight);
|
437
|
+
|
438
|
+
// cache the item's position relative to the list for use in scroll events
|
439
|
+
this.position = position;
|
440
|
+
|
441
|
+
// Item has position: relative, so we want to calculate the amount to move
|
442
|
+
// the item relative to it's DOM position to represent how much it has been
|
443
|
+
// dragged by.
|
444
|
+
const offset = position - this.initialPosition;
|
445
|
+
|
446
|
+
// Convert itemPosition from offset relative to list to offset relative to
|
447
|
+
// its position within the DOM (if it hadn't moved).
|
448
|
+
callback(offset);
|
449
|
+
}
|
450
|
+
}
|
451
|
+
|
452
|
+
class OrderableFormController extends Controller {
|
453
|
+
add(item) {
|
454
|
+
const { id_name, id_value, index_name } = item.paramsValue;
|
455
|
+
this.element.insertAdjacentHTML(
|
456
|
+
"beforeend",
|
457
|
+
`<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
|
458
|
+
<input type="hidden" name="${index_name}" value="${item.index}" data-generated>`
|
459
|
+
);
|
460
|
+
}
|
461
|
+
|
462
|
+
submit() {
|
463
|
+
if (this.inputs.length === 0) return;
|
464
|
+
|
465
|
+
this.element.requestSubmit();
|
466
|
+
}
|
467
|
+
|
468
|
+
clear() {
|
469
|
+
this.inputs.forEach((input) => input.remove());
|
470
|
+
}
|
471
|
+
|
472
|
+
get inputs() {
|
473
|
+
return this.element.querySelectorAll("input[data-generated]");
|
474
|
+
}
|
475
|
+
}
|
476
|
+
|
477
|
+
const Definitions = [
|
478
|
+
{
|
479
|
+
identifier: "tables--turbo--turbo-collection",
|
480
|
+
controllerConstructor: TurboCollectionController,
|
481
|
+
},
|
482
|
+
{
|
483
|
+
identifier: "tables--orderable--item",
|
484
|
+
controllerConstructor: OrderableRowController,
|
485
|
+
},
|
486
|
+
{
|
487
|
+
identifier: "tables--orderable--list",
|
488
|
+
controllerConstructor: OrderableListController,
|
489
|
+
},
|
490
|
+
{
|
491
|
+
identifier: "tables--orderable--form",
|
492
|
+
controllerConstructor: OrderableFormController,
|
493
|
+
},
|
494
|
+
];
|
495
|
+
|
496
|
+
export { Definitions as default };
|
@@ -0,0 +1,496 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
2
|
+
import { Turbo } from '@hotwired/turbo-rails';
|
3
|
+
|
4
|
+
class TurboCollectionController extends Controller {
|
5
|
+
static values = {
|
6
|
+
query: String,
|
7
|
+
sort: String,
|
8
|
+
};
|
9
|
+
|
10
|
+
queryValueChanged(query) {
|
11
|
+
Turbo.navigator.history.replace(this.#url(query));
|
12
|
+
}
|
13
|
+
|
14
|
+
sortValueChanged(sort) {
|
15
|
+
document.querySelectorAll(this.#sortSelector).forEach((input) => {
|
16
|
+
if (input) input.value = sort;
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
get #sortSelector() {
|
21
|
+
return "input[name='sort']";
|
22
|
+
}
|
23
|
+
|
24
|
+
#url(query) {
|
25
|
+
const frame = this.element.closest("turbo-frame");
|
26
|
+
let url;
|
27
|
+
|
28
|
+
if (frame) {
|
29
|
+
url = new URL(frame.baseURI);
|
30
|
+
} else {
|
31
|
+
url = new URL(window.location.href);
|
32
|
+
}
|
33
|
+
|
34
|
+
url.search = query;
|
35
|
+
|
36
|
+
return url;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
class OrderableRowController extends Controller {
|
41
|
+
static values = {
|
42
|
+
params: Object,
|
43
|
+
};
|
44
|
+
|
45
|
+
connect() {
|
46
|
+
// index from server may be inconsistent with the visual ordering,
|
47
|
+
// especially if this is a new node. Use positional indexes instead,
|
48
|
+
// as these are the values we will send on save.
|
49
|
+
this.index = domIndex(this.row);
|
50
|
+
}
|
51
|
+
|
52
|
+
paramsValueChanged(params) {
|
53
|
+
this.id = params.id_value;
|
54
|
+
}
|
55
|
+
|
56
|
+
dragUpdate(offset) {
|
57
|
+
this.dragOffset = offset;
|
58
|
+
this.row.style.position = "relative";
|
59
|
+
this.row.style.top = offset + "px";
|
60
|
+
this.row.style.zIndex = "1";
|
61
|
+
this.row.toggleAttribute("dragging", true);
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Called on items that are not the dragged item during drag. Updates the
|
66
|
+
* visual position of the item relative to the dragged item.
|
67
|
+
*
|
68
|
+
* @param index {number} intended index of the item during drag
|
69
|
+
*/
|
70
|
+
updateVisually(index) {
|
71
|
+
this.row.style.position = "relative";
|
72
|
+
this.row.style.top = `${
|
73
|
+
this.row.offsetHeight * (index - this.dragIndex)
|
74
|
+
}px`;
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Set the index value of the item. This is called on all items after a drop
|
79
|
+
* event. If the index is different to the params index then this item has
|
80
|
+
* changed.
|
81
|
+
*
|
82
|
+
* @param index {number} the new index value
|
83
|
+
*/
|
84
|
+
updateIndex(index) {
|
85
|
+
this.index = index;
|
86
|
+
}
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Restore any visual changes made during drag and remove the drag state.
|
90
|
+
*/
|
91
|
+
reset() {
|
92
|
+
delete this.dragOffset;
|
93
|
+
this.row.removeAttribute("style");
|
94
|
+
this.row.removeAttribute("dragging");
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* @returns {boolean} true when the item has a change to its index value
|
99
|
+
*/
|
100
|
+
get hasChanges() {
|
101
|
+
return this.paramsValue.index_value !== this.index;
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Calculate the relative index of the item during drag. This is used to
|
106
|
+
* sort items during drag as it takes into account any uncommitted changes
|
107
|
+
* to index caused by the drag offset.
|
108
|
+
*
|
109
|
+
* @returns {number} index for the purposes of drag and drop ordering
|
110
|
+
*/
|
111
|
+
get dragIndex() {
|
112
|
+
if (this.dragOffset && this.dragOffset !== 0) {
|
113
|
+
return this.index + Math.round(this.dragOffset / this.row.offsetHeight);
|
114
|
+
} else {
|
115
|
+
return this.index;
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Index value for use in comparisons during drag. This is used to determine
|
121
|
+
* whether the dragged item is above or below another item. If this item is
|
122
|
+
* being dragged then we offset the index by 0.5 to ensure that it jumps up
|
123
|
+
* or down when it reaches the midpoint of the item above or below it.
|
124
|
+
*
|
125
|
+
* @returns {number}
|
126
|
+
*/
|
127
|
+
get comparisonIndex() {
|
128
|
+
if (this.dragOffset) {
|
129
|
+
return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);
|
130
|
+
} else {
|
131
|
+
return this.index;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* The containing row element.
|
137
|
+
*
|
138
|
+
* @returns {HTMLElement}
|
139
|
+
*/
|
140
|
+
get row() {
|
141
|
+
return this.element.parentElement;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
function domIndex(element) {
|
146
|
+
return Array.from(element.parentElement.children).indexOf(element);
|
147
|
+
}
|
148
|
+
|
149
|
+
class OrderableListController extends Controller {
|
150
|
+
static outlets = ["tables--orderable--item", "tables--orderable--form"];
|
151
|
+
|
152
|
+
//region State transitions
|
153
|
+
|
154
|
+
startDragging(dragState) {
|
155
|
+
this.dragState = dragState;
|
156
|
+
|
157
|
+
document.addEventListener("mousemove", this.mousemove);
|
158
|
+
document.addEventListener("mouseup", this.mouseup);
|
159
|
+
window.addEventListener("scroll", this.scroll, true);
|
160
|
+
|
161
|
+
this.element.style.position = "relative";
|
162
|
+
}
|
163
|
+
|
164
|
+
stopDragging() {
|
165
|
+
const dragState = this.dragState;
|
166
|
+
delete this.dragState;
|
167
|
+
|
168
|
+
document.removeEventListener("mousemove", this.mousemove);
|
169
|
+
document.removeEventListener("mouseup", this.mouseup);
|
170
|
+
window.removeEventListener("scroll", this.scroll, true);
|
171
|
+
|
172
|
+
this.element.removeAttribute("style");
|
173
|
+
this.tablesOrderableItemOutlets.forEach((item) => item.reset());
|
174
|
+
|
175
|
+
return dragState;
|
176
|
+
}
|
177
|
+
|
178
|
+
drop() {
|
179
|
+
// note: early returns guard against turbo updates that prevent us finding
|
180
|
+
// the right item to drop on. In this situation it's better to discard the
|
181
|
+
// drop than to drop in the wrong place.
|
182
|
+
|
183
|
+
const dragItem = this.dragItem;
|
184
|
+
|
185
|
+
if (!dragItem) return;
|
186
|
+
|
187
|
+
const newIndex = dragItem.dragIndex;
|
188
|
+
const targetItem = this.tablesOrderableItemOutlets[newIndex];
|
189
|
+
|
190
|
+
if (!targetItem) return;
|
191
|
+
|
192
|
+
// swap the dragged item into the correct position for its current offset
|
193
|
+
if (newIndex < dragItem.index) {
|
194
|
+
targetItem.row.insertAdjacentElement("beforebegin", dragItem.row);
|
195
|
+
} else if (newIndex > dragItem.index) {
|
196
|
+
targetItem.row.insertAdjacentElement("afterend", dragItem.row);
|
197
|
+
}
|
198
|
+
|
199
|
+
// reindex all items based on their new positions
|
200
|
+
this.tablesOrderableItemOutlets.forEach((item, index) =>
|
201
|
+
item.updateIndex(index)
|
202
|
+
);
|
203
|
+
|
204
|
+
// save the changes
|
205
|
+
this.commitChanges();
|
206
|
+
}
|
207
|
+
|
208
|
+
commitChanges() {
|
209
|
+
// clear any existing inputs to prevent duplicates
|
210
|
+
this.tablesOrderableFormOutlet.clear();
|
211
|
+
|
212
|
+
// insert any items that have changed position
|
213
|
+
this.tablesOrderableItemOutlets.forEach((item) => {
|
214
|
+
if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);
|
215
|
+
});
|
216
|
+
|
217
|
+
this.tablesOrderableFormOutlet.submit();
|
218
|
+
}
|
219
|
+
|
220
|
+
//endregion
|
221
|
+
|
222
|
+
//region Events
|
223
|
+
|
224
|
+
mousedown(event) {
|
225
|
+
if (this.isDragging) return;
|
226
|
+
|
227
|
+
const target = this.#targetItem(event.target);
|
228
|
+
|
229
|
+
if (!target) return;
|
230
|
+
|
231
|
+
event.preventDefault(); // prevent built-in drag
|
232
|
+
|
233
|
+
this.startDragging(new DragState(this.element, event, target.id));
|
234
|
+
|
235
|
+
this.dragState.updateCursor(this.element, target.row, event, this.animate);
|
236
|
+
}
|
237
|
+
|
238
|
+
mousemove = (event) => {
|
239
|
+
if (!this.isDragging) return;
|
240
|
+
|
241
|
+
event.preventDefault(); // prevent build-in drag
|
242
|
+
|
243
|
+
if (this.ticking) return;
|
244
|
+
|
245
|
+
this.ticking = true;
|
246
|
+
|
247
|
+
window.requestAnimationFrame(() => {
|
248
|
+
this.ticking = false;
|
249
|
+
this.dragState.updateCursor(
|
250
|
+
this.element,
|
251
|
+
this.dragItem.row,
|
252
|
+
event,
|
253
|
+
this.animate
|
254
|
+
);
|
255
|
+
});
|
256
|
+
};
|
257
|
+
|
258
|
+
scroll = (event) => {
|
259
|
+
if (!this.isDragging || this.ticking) return;
|
260
|
+
|
261
|
+
this.ticking = true;
|
262
|
+
|
263
|
+
window.requestAnimationFrame(() => {
|
264
|
+
this.ticking = false;
|
265
|
+
this.dragState.updateScroll(
|
266
|
+
this.element,
|
267
|
+
this.dragItem.row,
|
268
|
+
this.animate
|
269
|
+
);
|
270
|
+
});
|
271
|
+
};
|
272
|
+
|
273
|
+
mouseup = (event) => {
|
274
|
+
if (!this.isDragging) return;
|
275
|
+
|
276
|
+
this.drop();
|
277
|
+
this.stopDragging();
|
278
|
+
this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);
|
279
|
+
};
|
280
|
+
|
281
|
+
tablesOrderableFormOutletConnected(form, element) {
|
282
|
+
if (form.dragState) {
|
283
|
+
// restore the previous controller's state
|
284
|
+
this.startDragging(form.dragState);
|
285
|
+
}
|
286
|
+
}
|
287
|
+
|
288
|
+
tablesOrderableFormOutletDisconnected(form, element) {
|
289
|
+
if (this.isDragging) {
|
290
|
+
// cache drag state in the form
|
291
|
+
form.dragState = this.stopDragging();
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
//endregion
|
296
|
+
|
297
|
+
//region Helpers
|
298
|
+
|
299
|
+
/**
|
300
|
+
* Updates the position of the drag item with a relative offset. Updates
|
301
|
+
* other items relative to the new position of the drag item, as required.
|
302
|
+
*
|
303
|
+
* @callback {OrderableListController~animate}
|
304
|
+
* @param {number} offset
|
305
|
+
*/
|
306
|
+
animate = (offset) => {
|
307
|
+
const dragItem = this.dragItem;
|
308
|
+
|
309
|
+
// Visually update the dragItem so it follows the cursor
|
310
|
+
dragItem.dragUpdate(offset);
|
311
|
+
|
312
|
+
// Visually updates the position of all items in the list relative to the
|
313
|
+
// dragged item. No actual changes to orderings at this stage.
|
314
|
+
this.#currentItems.forEach((item, index) => {
|
315
|
+
if (item === dragItem) return;
|
316
|
+
item.updateVisually(index);
|
317
|
+
});
|
318
|
+
};
|
319
|
+
|
320
|
+
get isDragging() {
|
321
|
+
return !!this.dragState;
|
322
|
+
}
|
323
|
+
|
324
|
+
get dragItem() {
|
325
|
+
if (!this.isDragging) return null;
|
326
|
+
|
327
|
+
return this.tablesOrderableItemOutlets.find(
|
328
|
+
(item) => item.id === this.dragState.targetId
|
329
|
+
);
|
330
|
+
}
|
331
|
+
|
332
|
+
/**
|
333
|
+
* Returns the current items in the list, sorted by their current index.
|
334
|
+
* Current uses the drag index if the item is being dragged, if set.
|
335
|
+
*
|
336
|
+
* @returns {Array[OrderableRowController]}
|
337
|
+
*/
|
338
|
+
get #currentItems() {
|
339
|
+
return this.tablesOrderableItemOutlets.toSorted(
|
340
|
+
(a, b) => a.comparisonIndex - b.comparisonIndex
|
341
|
+
);
|
342
|
+
}
|
343
|
+
|
344
|
+
/**
|
345
|
+
* Returns the item outlet that was clicked on, if any.
|
346
|
+
*
|
347
|
+
* @param element {HTMLElement} the clicked ordinal cell
|
348
|
+
* @returns {OrderableRowController}
|
349
|
+
*/
|
350
|
+
#targetItem(element) {
|
351
|
+
return this.tablesOrderableItemOutlets.find(
|
352
|
+
(item) => item.element === element
|
353
|
+
);
|
354
|
+
}
|
355
|
+
|
356
|
+
//endregion
|
357
|
+
}
|
358
|
+
|
359
|
+
/**
|
360
|
+
* During drag we want to be able to translate a document-relative coordinate
|
361
|
+
* into a coordinate relative to the list element. This state object calculates
|
362
|
+
* and stores internal state so that we can translate absolute page coordinates
|
363
|
+
* from mouse events into relative offsets for the list items within the list
|
364
|
+
* element.
|
365
|
+
*
|
366
|
+
* We also keep track of the drag target so that if the controller is attached
|
367
|
+
* to a new element during the drag we can continue after the turbo update.
|
368
|
+
*/
|
369
|
+
class DragState {
|
370
|
+
/**
|
371
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
372
|
+
* @param event {MouseEvent} the initial event
|
373
|
+
* @param id {String} the id of the element being dragged
|
374
|
+
*/
|
375
|
+
constructor(list, event, id) {
|
376
|
+
// cursor offset is the offset of the cursor relative to the drag item
|
377
|
+
this.cursorOffset = event.offsetY;
|
378
|
+
|
379
|
+
// initial offset is the offset position of the drag item at drag start
|
380
|
+
this.initialPosition = event.target.offsetTop - list.offsetTop;
|
381
|
+
|
382
|
+
// id of the item being dragged
|
383
|
+
this.targetId = id;
|
384
|
+
}
|
385
|
+
|
386
|
+
/**
|
387
|
+
* Calculates the offset of the drag item relative to its initial position.
|
388
|
+
*
|
389
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
390
|
+
* @param row {HTMLElement} the row being dragged
|
391
|
+
* @param event {MouseEvent} the current event
|
392
|
+
* @param callback {OrderableListController~animate} updates the drag item with a relative offset
|
393
|
+
*/
|
394
|
+
updateCursor(list, row, event, callback) {
|
395
|
+
// Calculate and store the list offset relative to the viewport
|
396
|
+
// This value is cached so we can calculate the outcome of any scroll events
|
397
|
+
this.listOffset = list.getBoundingClientRect().top;
|
398
|
+
|
399
|
+
// Calculate the position of the cursor relative to the list.
|
400
|
+
// Accounts for scroll offsets by using the item's bounding client rect.
|
401
|
+
const cursorPosition = event.clientY - this.listOffset;
|
402
|
+
|
403
|
+
// intended item position relative to the list, from cursor position
|
404
|
+
let itemPosition = cursorPosition - this.cursorOffset;
|
405
|
+
|
406
|
+
this.#updateItemPosition(list, row, itemPosition, callback);
|
407
|
+
}
|
408
|
+
|
409
|
+
/**
|
410
|
+
* Animates the item's position as the list scrolls. Requires a previous call
|
411
|
+
* to set the scroll offset.
|
412
|
+
*
|
413
|
+
* @param list {HTMLElement} the list controller's element (tbody)
|
414
|
+
* @param row {HTMLElement} the row being dragged
|
415
|
+
* @param callback {OrderableListController~animate} updates the drag item with a relative offset
|
416
|
+
*/
|
417
|
+
updateScroll(list, row, callback) {
|
418
|
+
const previousScrollOffset = this.listOffset;
|
419
|
+
|
420
|
+
// Calculate and store the list offset relative to the viewport
|
421
|
+
// This value is cached so we can calculate the outcome of any scroll events
|
422
|
+
this.listOffset = list.getBoundingClientRect().top;
|
423
|
+
|
424
|
+
// Calculate the change in scroll offset since the last update
|
425
|
+
const scrollDelta = previousScrollOffset - this.listOffset;
|
426
|
+
|
427
|
+
// intended item position relative to the list, from cursor position
|
428
|
+
const position = this.position + scrollDelta;
|
429
|
+
|
430
|
+
this.#updateItemPosition(list, row, position, callback);
|
431
|
+
}
|
432
|
+
|
433
|
+
#updateItemPosition(list, row, position, callback) {
|
434
|
+
// ensure itemPosition is within the bounds of the list (tbody)
|
435
|
+
position = Math.max(position, 0);
|
436
|
+
position = Math.min(position, list.offsetHeight - row.offsetHeight);
|
437
|
+
|
438
|
+
// cache the item's position relative to the list for use in scroll events
|
439
|
+
this.position = position;
|
440
|
+
|
441
|
+
// Item has position: relative, so we want to calculate the amount to move
|
442
|
+
// the item relative to it's DOM position to represent how much it has been
|
443
|
+
// dragged by.
|
444
|
+
const offset = position - this.initialPosition;
|
445
|
+
|
446
|
+
// Convert itemPosition from offset relative to list to offset relative to
|
447
|
+
// its position within the DOM (if it hadn't moved).
|
448
|
+
callback(offset);
|
449
|
+
}
|
450
|
+
}
|
451
|
+
|
452
|
+
class OrderableFormController extends Controller {
|
453
|
+
add(item) {
|
454
|
+
const { id_name, id_value, index_name } = item.paramsValue;
|
455
|
+
this.element.insertAdjacentHTML(
|
456
|
+
"beforeend",
|
457
|
+
`<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
|
458
|
+
<input type="hidden" name="${index_name}" value="${item.index}" data-generated>`
|
459
|
+
);
|
460
|
+
}
|
461
|
+
|
462
|
+
submit() {
|
463
|
+
if (this.inputs.length === 0) return;
|
464
|
+
|
465
|
+
this.element.requestSubmit();
|
466
|
+
}
|
467
|
+
|
468
|
+
clear() {
|
469
|
+
this.inputs.forEach((input) => input.remove());
|
470
|
+
}
|
471
|
+
|
472
|
+
get inputs() {
|
473
|
+
return this.element.querySelectorAll("input[data-generated]");
|
474
|
+
}
|
475
|
+
}
|
476
|
+
|
477
|
+
const Definitions = [
|
478
|
+
{
|
479
|
+
identifier: "tables--turbo--turbo-collection",
|
480
|
+
controllerConstructor: TurboCollectionController,
|
481
|
+
},
|
482
|
+
{
|
483
|
+
identifier: "tables--orderable--item",
|
484
|
+
controllerConstructor: OrderableRowController,
|
485
|
+
},
|
486
|
+
{
|
487
|
+
identifier: "tables--orderable--list",
|
488
|
+
controllerConstructor: OrderableListController,
|
489
|
+
},
|
490
|
+
{
|
491
|
+
identifier: "tables--orderable--form",
|
492
|
+
controllerConstructor: OrderableFormController,
|
493
|
+
},
|
494
|
+
];
|
495
|
+
|
496
|
+
export { Definitions as default };
|
@@ -0,0 +1,2 @@
|
|
1
|
+
import{Controller as t}from"@hotwired/stimulus";import{Turbo as e}from"@hotwired/turbo-rails";class s{constructor(t,e,s){this.cursorOffset=e.offsetY,this.initialPosition=e.target.offsetTop-t.offsetTop,this.targetId=s}updateCursor(t,e,s,r){this.listOffset=t.getBoundingClientRect().top;let i=s.clientY-this.listOffset-this.cursorOffset;this.#t(t,e,i,r)}updateScroll(t,e,s){const r=this.listOffset;this.listOffset=t.getBoundingClientRect().top;const i=r-this.listOffset,a=this.position+i;this.#t(t,e,a,s)}#t(t,e,s,r){s=Math.max(s,0),s=Math.min(s,t.offsetHeight-e.offsetHeight),this.position=s;r(s-this.initialPosition)}}const r=[{identifier:"tables--turbo--turbo-collection",controllerConstructor:class extends t{static values={query:String,sort:String};queryValueChanged(t){e.navigator.history.replace(this.#e(t))}sortValueChanged(t){document.querySelectorAll(this.#s).forEach((e=>{e&&(e.value=t)}))}get#s(){return"input[name='sort']"}#e(t){const e=this.element.closest("turbo-frame");let s;return s=e?new URL(e.baseURI):new URL(window.location.href),s.search=t,s}}},{identifier:"tables--orderable--item",controllerConstructor:class extends t{static values={params:Object};connect(){var t;this.index=(t=this.row,Array.from(t.parentElement.children).indexOf(t))}paramsValueChanged(t){this.id=t.id_value}dragUpdate(t){this.dragOffset=t,this.row.style.position="relative",this.row.style.top=t+"px",this.row.style.zIndex="1",this.row.toggleAttribute("dragging",!0)}updateVisually(t){this.row.style.position="relative",this.row.style.top=this.row.offsetHeight*(t-this.dragIndex)+"px"}updateIndex(t){this.index=t}reset(){delete this.dragOffset,this.row.removeAttribute("style"),this.row.removeAttribute("dragging")}get hasChanges(){return this.paramsValue.index_value!==this.index}get dragIndex(){return this.dragOffset&&0!==this.dragOffset?this.index+Math.round(this.dragOffset/this.row.offsetHeight):this.index}get comparisonIndex(){return this.dragOffset?this.dragIndex+(this.dragOffset>0?.5:-.5):this.index}get row(){return this.element.parentElement}}},{identifier:"tables--orderable--list",controllerConstructor:class extends t{static outlets=["tables--orderable--item","tables--orderable--form"];startDragging(t){this.dragState=t,document.addEventListener("mousemove",this.mousemove),document.addEventListener("mouseup",this.mouseup),window.addEventListener("scroll",this.scroll,!0),this.element.style.position="relative"}stopDragging(){const t=this.dragState;return delete this.dragState,document.removeEventListener("mousemove",this.mousemove),document.removeEventListener("mouseup",this.mouseup),window.removeEventListener("scroll",this.scroll,!0),this.element.removeAttribute("style"),this.tablesOrderableItemOutlets.forEach((t=>t.reset())),t}drop(){const t=this.dragItem;if(!t)return;const e=t.dragIndex,s=this.tablesOrderableItemOutlets[e];s&&(e<t.index?s.row.insertAdjacentElement("beforebegin",t.row):e>t.index&&s.row.insertAdjacentElement("afterend",t.row),this.tablesOrderableItemOutlets.forEach(((t,e)=>t.updateIndex(e))),this.commitChanges())}commitChanges(){this.tablesOrderableFormOutlet.clear(),this.tablesOrderableItemOutlets.forEach((t=>{t.hasChanges&&this.tablesOrderableFormOutlet.add(t)})),this.tablesOrderableFormOutlet.submit()}mousedown(t){if(this.isDragging)return;const e=this.#r(t.target);e&&(t.preventDefault(),this.startDragging(new s(this.element,t,e.id)),this.dragState.updateCursor(this.element,e.row,t,this.animate))}mousemove=t=>{this.isDragging&&(t.preventDefault(),this.ticking||(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateCursor(this.element,this.dragItem.row,t,this.animate)}))))};scroll=t=>{this.isDragging&&!this.ticking&&(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateScroll(this.element,this.dragItem.row,this.animate)})))};mouseup=t=>{this.isDragging&&(this.drop(),this.stopDragging(),this.tablesOrderableFormOutlets.forEach((t=>delete t.dragState)))};tablesOrderableFormOutletConnected(t,e){t.dragState&&this.startDragging(t.dragState)}tablesOrderableFormOutletDisconnected(t,e){this.isDragging&&(t.dragState=this.stopDragging())}animate=t=>{const e=this.dragItem;e.dragUpdate(t),this.#i.forEach(((t,s)=>{t!==e&&t.updateVisually(s)}))};get isDragging(){return!!this.dragState}get dragItem(){return this.isDragging?this.tablesOrderableItemOutlets.find((t=>t.id===this.dragState.targetId)):null}get#i(){return this.tablesOrderableItemOutlets.toSorted(((t,e)=>t.comparisonIndex-e.comparisonIndex))}#r(t){return this.tablesOrderableItemOutlets.find((e=>e.element===t))}}},{identifier:"tables--orderable--form",controllerConstructor:class extends t{add(t){const{id_name:e,id_value:s,index_name:r}=t.paramsValue;this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${e}" value="${s}" data-generated>\n <input type="hidden" name="${r}" value="${t.index}" data-generated>`)}submit(){0!==this.inputs.length&&this.element.requestSubmit()}clear(){this.inputs.forEach((t=>t.remove()))}get inputs(){return this.element.querySelectorAll("input[data-generated]")}}}];export{r as default};
|
2
|
+
//# sourceMappingURL=tables.min.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"tables.min.js","sources":["../../../javascript/tables/orderable/list_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/turbo/turbo_collection_controller.js","../../../javascript/tables/orderable/item_controller.js","../../../javascript/tables/orderable/form_controller.js"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableListController extends Controller {\n static outlets = [\"tables--orderable--item\", \"tables--orderable--form\"];\n\n //region State transitions\n\n startDragging(dragState) {\n this.dragState = dragState;\n\n document.addEventListener(\"mousemove\", this.mousemove);\n document.addEventListener(\"mouseup\", this.mouseup);\n window.addEventListener(\"scroll\", this.scroll, true);\n\n this.element.style.position = \"relative\";\n }\n\n stopDragging() {\n const dragState = this.dragState;\n delete this.dragState;\n\n document.removeEventListener(\"mousemove\", this.mousemove);\n document.removeEventListener(\"mouseup\", this.mouseup);\n window.removeEventListener(\"scroll\", this.scroll, true);\n\n this.element.removeAttribute(\"style\");\n this.tablesOrderableItemOutlets.forEach((item) => item.reset());\n\n return dragState;\n }\n\n drop() {\n // note: early returns guard against turbo updates that prevent us finding\n // the right item to drop on. In this situation it's better to discard the\n // drop than to drop in the wrong place.\n\n const dragItem = this.dragItem;\n\n if (!dragItem) return;\n\n const newIndex = dragItem.dragIndex;\n const targetItem = this.tablesOrderableItemOutlets[newIndex];\n\n if (!targetItem) return;\n\n // swap the dragged item into the correct position for its current offset\n if (newIndex < dragItem.index) {\n targetItem.row.insertAdjacentElement(\"beforebegin\", dragItem.row);\n } else if (newIndex > dragItem.index) {\n targetItem.row.insertAdjacentElement(\"afterend\", dragItem.row);\n }\n\n // reindex all items based on their new positions\n this.tablesOrderableItemOutlets.forEach((item, index) =>\n item.updateIndex(index)\n );\n\n // save the changes\n this.commitChanges();\n }\n\n commitChanges() {\n // clear any existing inputs to prevent duplicates\n this.tablesOrderableFormOutlet.clear();\n\n // insert any items that have changed position\n this.tablesOrderableItemOutlets.forEach((item) => {\n if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);\n });\n\n this.tablesOrderableFormOutlet.submit();\n }\n\n //endregion\n\n //region Events\n\n mousedown(event) {\n if (this.isDragging) return;\n\n const target = this.#targetItem(event.target);\n\n if (!target) return;\n\n event.preventDefault(); // prevent built-in drag\n\n this.startDragging(new DragState(this.element, event, target.id));\n\n this.dragState.updateCursor(this.element, target.row, event, this.animate);\n }\n\n mousemove = (event) => {\n if (!this.isDragging) return;\n\n event.preventDefault(); // prevent build-in drag\n\n if (this.ticking) return;\n\n this.ticking = true;\n\n window.requestAnimationFrame(() => {\n this.ticking = false;\n this.dragState.updateCursor(\n this.element,\n this.dragItem.row,\n event,\n this.animate\n );\n });\n };\n\n scroll = (event) => {\n if (!this.isDragging || this.ticking) return;\n\n this.ticking = true;\n\n window.requestAnimationFrame(() => {\n this.ticking = false;\n this.dragState.updateScroll(\n this.element,\n this.dragItem.row,\n this.animate\n );\n });\n };\n\n mouseup = (event) => {\n if (!this.isDragging) return;\n\n this.drop();\n this.stopDragging();\n this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);\n };\n\n tablesOrderableFormOutletConnected(form, element) {\n if (form.dragState) {\n // restore the previous controller's state\n this.startDragging(form.dragState);\n }\n }\n\n tablesOrderableFormOutletDisconnected(form, element) {\n if (this.isDragging) {\n // cache drag state in the form\n form.dragState = this.stopDragging();\n }\n }\n\n //endregion\n\n //region Helpers\n\n /**\n * Updates the position of the drag item with a relative offset. Updates\n * other items relative to the new position of the drag item, as required.\n *\n * @callback {OrderableListController~animate}\n * @param {number} offset\n */\n animate = (offset) => {\n const dragItem = this.dragItem;\n\n // Visually update the dragItem so it follows the cursor\n dragItem.dragUpdate(offset);\n\n // Visually updates the position of all items in the list relative to the\n // dragged item. No actual changes to orderings at this stage.\n this.#currentItems.forEach((item, index) => {\n if (item === dragItem) return;\n item.updateVisually(index);\n });\n };\n\n get isDragging() {\n return !!this.dragState;\n }\n\n get dragItem() {\n if (!this.isDragging) return null;\n\n return this.tablesOrderableItemOutlets.find(\n (item) => item.id === this.dragState.targetId\n );\n }\n\n /**\n * Returns the current items in the list, sorted by their current index.\n * Current uses the drag index if the item is being dragged, if set.\n *\n * @returns {Array[OrderableRowController]}\n */\n get #currentItems() {\n return this.tablesOrderableItemOutlets.toSorted(\n (a, b) => a.comparisonIndex - b.comparisonIndex\n );\n }\n\n /**\n * Returns the item outlet that was clicked on, if any.\n *\n * @param element {HTMLElement} the clicked ordinal cell\n * @returns {OrderableRowController}\n */\n #targetItem(element) {\n return this.tablesOrderableItemOutlets.find(\n (item) => item.element === element\n );\n }\n\n //endregion\n}\n\n/**\n * During drag we want to be able to translate a document-relative coordinate\n * into a coordinate relative to the list element. This state object calculates\n * and stores internal state so that we can translate absolute page coordinates\n * from mouse events into relative offsets for the list items within the list\n * element.\n *\n * We also keep track of the drag target so that if the controller is attached\n * to a new element during the drag we can continue after the turbo update.\n */\nclass DragState {\n /**\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param event {MouseEvent} the initial event\n * @param id {String} the id of the element being dragged\n */\n constructor(list, event, id) {\n // cursor offset is the offset of the cursor relative to the drag item\n this.cursorOffset = event.offsetY;\n\n // initial offset is the offset position of the drag item at drag start\n this.initialPosition = event.target.offsetTop - list.offsetTop;\n\n // id of the item being dragged\n this.targetId = id;\n }\n\n /**\n * Calculates the offset of the drag item relative to its initial position.\n *\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param row {HTMLElement} the row being dragged\n * @param event {MouseEvent} the current event\n * @param callback {OrderableListController~animate} updates the drag item with a relative offset\n */\n updateCursor(list, row, event, callback) {\n // Calculate and store the list offset relative to the viewport\n // This value is cached so we can calculate the outcome of any scroll events\n this.listOffset = list.getBoundingClientRect().top;\n\n // Calculate the position of the cursor relative to the list.\n // Accounts for scroll offsets by using the item's bounding client rect.\n const cursorPosition = event.clientY - this.listOffset;\n\n // intended item position relative to the list, from cursor position\n let itemPosition = cursorPosition - this.cursorOffset;\n\n this.#updateItemPosition(list, row, itemPosition, callback);\n }\n\n /**\n * Animates the item's position as the list scrolls. Requires a previous call\n * to set the scroll offset.\n *\n * @param list {HTMLElement} the list controller's element (tbody)\n * @param row {HTMLElement} the row being dragged\n * @param callback {OrderableListController~animate} updates the drag item with a relative offset\n */\n updateScroll(list, row, callback) {\n const previousScrollOffset = this.listOffset;\n\n // Calculate and store the list offset relative to the viewport\n // This value is cached so we can calculate the outcome of any scroll events\n this.listOffset = list.getBoundingClientRect().top;\n\n // Calculate the change in scroll offset since the last update\n const scrollDelta = previousScrollOffset - this.listOffset;\n\n // intended item position relative to the list, from cursor position\n const position = this.position + scrollDelta;\n\n this.#updateItemPosition(list, row, position, callback);\n }\n\n #updateItemPosition(list, row, position, callback) {\n // ensure itemPosition is within the bounds of the list (tbody)\n position = Math.max(position, 0);\n position = Math.min(position, list.offsetHeight - row.offsetHeight);\n\n // cache the item's position relative to the list for use in scroll events\n this.position = position;\n\n // Item has position: relative, so we want to calculate the amount to move\n // the item relative to it's DOM position to represent how much it has been\n // dragged by.\n const offset = position - this.initialPosition;\n\n // Convert itemPosition from offset relative to list to offset relative to\n // its position within the DOM (if it hadn't moved).\n callback(offset);\n }\n}\n","import TurboCollectionController from \"./turbo/turbo_collection_controller\";\nimport ItemController from \"./orderable/item_controller\";\nimport ListController from \"./orderable/list_controller\";\nimport FormController from \"./orderable/form_controller\";\n\nconst Definitions = [\n {\n identifier: \"tables--turbo--turbo-collection\",\n controllerConstructor: TurboCollectionController,\n },\n {\n identifier: \"tables--orderable--item\",\n controllerConstructor: ItemController,\n },\n {\n identifier: \"tables--orderable--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"tables--orderable--form\",\n controllerConstructor: FormController,\n },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\nimport { Turbo } from \"@hotwired/turbo-rails\";\n\nexport default class TurboCollectionController extends Controller {\n static values = {\n query: String,\n sort: String,\n };\n\n queryValueChanged(query) {\n Turbo.navigator.history.replace(this.#url(query));\n }\n\n sortValueChanged(sort) {\n document.querySelectorAll(this.#sortSelector).forEach((input) => {\n if (input) input.value = sort;\n });\n }\n\n get #sortSelector() {\n return \"input[name='sort']\";\n }\n\n #url(query) {\n const frame = this.element.closest(\"turbo-frame\");\n let url;\n\n if (frame) {\n url = new URL(frame.baseURI);\n } else {\n url = new URL(window.location.href);\n }\n\n url.search = query;\n\n return url;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableRowController extends Controller {\n static values = {\n params: Object,\n };\n\n connect() {\n // index from server may be inconsistent with the visual ordering,\n // especially if this is a new node. Use positional indexes instead,\n // as these are the values we will send on save.\n this.index = domIndex(this.row);\n }\n\n paramsValueChanged(params) {\n this.id = params.id_value;\n }\n\n dragUpdate(offset) {\n this.dragOffset = offset;\n this.row.style.position = \"relative\";\n this.row.style.top = offset + \"px\";\n this.row.style.zIndex = \"1\";\n this.row.toggleAttribute(\"dragging\", true);\n }\n\n /**\n * Called on items that are not the dragged item during drag. Updates the\n * visual position of the item relative to the dragged item.\n *\n * @param index {number} intended index of the item during drag\n */\n updateVisually(index) {\n this.row.style.position = \"relative\";\n this.row.style.top = `${\n this.row.offsetHeight * (index - this.dragIndex)\n }px`;\n }\n\n /**\n * Set the index value of the item. This is called on all items after a drop\n * event. If the index is different to the params index then this item has\n * changed.\n *\n * @param index {number} the new index value\n */\n updateIndex(index) {\n this.index = index;\n }\n\n /**\n * Restore any visual changes made during drag and remove the drag state.\n */\n reset() {\n delete this.dragOffset;\n this.row.removeAttribute(\"style\");\n this.row.removeAttribute(\"dragging\");\n }\n\n /**\n * @returns {boolean} true when the item has a change to its index value\n */\n get hasChanges() {\n return this.paramsValue.index_value !== this.index;\n }\n\n /**\n * Calculate the relative index of the item during drag. This is used to\n * sort items during drag as it takes into account any uncommitted changes\n * to index caused by the drag offset.\n *\n * @returns {number} index for the purposes of drag and drop ordering\n */\n get dragIndex() {\n if (this.dragOffset && this.dragOffset !== 0) {\n return this.index + Math.round(this.dragOffset / this.row.offsetHeight);\n } else {\n return this.index;\n }\n }\n\n /**\n * Index value for use in comparisons during drag. This is used to determine\n * whether the dragged item is above or below another item. If this item is\n * being dragged then we offset the index by 0.5 to ensure that it jumps up\n * or down when it reaches the midpoint of the item above or below it.\n *\n * @returns {number}\n */\n get comparisonIndex() {\n if (this.dragOffset) {\n return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);\n } else {\n return this.index;\n }\n }\n\n /**\n * The containing row element.\n *\n * @returns {HTMLElement}\n */\n get row() {\n return this.element.parentElement;\n }\n}\n\nfunction domIndex(element) {\n return Array.from(element.parentElement.children).indexOf(element);\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class OrderableFormController extends Controller {\n add(item) {\n const { id_name, id_value, index_name } = item.paramsValue;\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${id_name}\" value=\"${id_value}\" data-generated>\n <input type=\"hidden\" name=\"${index_name}\" value=\"${item.index}\" data-generated>`\n );\n }\n\n submit() {\n if (this.inputs.length === 0) return;\n\n this.element.requestSubmit();\n }\n\n clear() {\n this.inputs.forEach((input) => input.remove());\n }\n\n get inputs() {\n return this.element.querySelectorAll(\"input[data-generated]\");\n }\n}\n"],"names":["DragState","constructor","list","event","id","this","cursorOffset","offsetY","initialPosition","target","offsetTop","targetId","updateCursor","row","callback","listOffset","getBoundingClientRect","top","itemPosition","clientY","updateItemPosition","updateScroll","previousScrollOffset","scrollDelta","position","Math","max","min","offsetHeight","Definitions","identifier","controllerConstructor","Controller","static","query","String","sort","queryValueChanged","Turbo","navigator","history","replace","url","sortValueChanged","document","querySelectorAll","sortSelector","forEach","input","value","frame","element","closest","URL","baseURI","window","location","href","search","params","Object","connect","index","Array","from","parentElement","children","indexOf","paramsValueChanged","id_value","dragUpdate","offset","dragOffset","style","zIndex","toggleAttribute","updateVisually","dragIndex","updateIndex","reset","removeAttribute","hasChanges","paramsValue","index_value","round","comparisonIndex","startDragging","dragState","addEventListener","mousemove","mouseup","scroll","stopDragging","removeEventListener","tablesOrderableItemOutlets","item","drop","dragItem","newIndex","targetItem","insertAdjacentElement","commitChanges","tablesOrderableFormOutlet","clear","add","submit","mousedown","isDragging","preventDefault","animate","ticking","requestAnimationFrame","tablesOrderableFormOutlets","form","tablesOrderableFormOutletConnected","tablesOrderableFormOutletDisconnected","currentItems","find","toSorted","a","b","id_name","index_name","insertAdjacentHTML","inputs","length","requestSubmit","remove"],"mappings":"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"}
|
@@ -1 +1 @@
|
|
1
|
-
//= link_tree ../
|
1
|
+
//= link_tree ../builds
|
@@ -11,7 +11,7 @@ module Katalyst
|
|
11
11
|
ITEM_CONTROLLER = "tables--orderable--item"
|
12
12
|
LIST_CONTROLLER = "tables--orderable--list"
|
13
13
|
|
14
|
-
using Katalyst::HtmlAttributes
|
14
|
+
using Katalyst::HtmlAttributes::HasHtmlAttributes
|
15
15
|
|
16
16
|
# Support for inclusion in a table component class
|
17
17
|
# Adds an `orderable` slot and component configuration
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import TurboCollectionController from "./turbo/turbo_collection_controller";
|
2
|
+
import ItemController from "./orderable/item_controller";
|
3
|
+
import ListController from "./orderable/list_controller";
|
4
|
+
import FormController from "./orderable/form_controller";
|
5
|
+
|
6
|
+
const Definitions = [
|
7
|
+
{
|
8
|
+
identifier: "tables--turbo--turbo-collection",
|
9
|
+
controllerConstructor: TurboCollectionController,
|
10
|
+
},
|
11
|
+
{
|
12
|
+
identifier: "tables--orderable--item",
|
13
|
+
controllerConstructor: ItemController,
|
14
|
+
},
|
15
|
+
{
|
16
|
+
identifier: "tables--orderable--list",
|
17
|
+
controllerConstructor: ListController,
|
18
|
+
},
|
19
|
+
{
|
20
|
+
identifier: "tables--orderable--form",
|
21
|
+
controllerConstructor: FormController,
|
22
|
+
},
|
23
|
+
];
|
24
|
+
|
25
|
+
export { Definitions as default };
|
@@ -6,7 +6,7 @@ export default class OrderableFormController extends Controller {
|
|
6
6
|
this.element.insertAdjacentHTML(
|
7
7
|
"beforeend",
|
8
8
|
`<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
|
9
|
-
<input type="hidden" name="${index_name}" value="${item.index}" data-generated
|
9
|
+
<input type="hidden" name="${index_name}" value="${item.index}" data-generated>`
|
10
10
|
);
|
11
11
|
}
|
12
12
|
|
@@ -52,7 +52,7 @@ export default class OrderableListController extends Controller {
|
|
52
52
|
|
53
53
|
// reindex all items based on their new positions
|
54
54
|
this.tablesOrderableItemOutlets.forEach((item, index) =>
|
55
|
-
item.updateIndex(index)
|
55
|
+
item.updateIndex(index)
|
56
56
|
);
|
57
57
|
|
58
58
|
// save the changes
|
@@ -104,7 +104,7 @@ export default class OrderableListController extends Controller {
|
|
104
104
|
this.element,
|
105
105
|
this.dragItem.row,
|
106
106
|
event,
|
107
|
-
this.animate
|
107
|
+
this.animate
|
108
108
|
);
|
109
109
|
});
|
110
110
|
};
|
@@ -119,7 +119,7 @@ export default class OrderableListController extends Controller {
|
|
119
119
|
this.dragState.updateScroll(
|
120
120
|
this.element,
|
121
121
|
this.dragItem.row,
|
122
|
-
this.animate
|
122
|
+
this.animate
|
123
123
|
);
|
124
124
|
});
|
125
125
|
};
|
@@ -179,7 +179,7 @@ export default class OrderableListController extends Controller {
|
|
179
179
|
if (!this.isDragging) return null;
|
180
180
|
|
181
181
|
return this.tablesOrderableItemOutlets.find(
|
182
|
-
(item) => item.id === this.dragState.targetId
|
182
|
+
(item) => item.id === this.dragState.targetId
|
183
183
|
);
|
184
184
|
}
|
185
185
|
|
@@ -191,7 +191,7 @@ export default class OrderableListController extends Controller {
|
|
191
191
|
*/
|
192
192
|
get #currentItems() {
|
193
193
|
return this.tablesOrderableItemOutlets.toSorted(
|
194
|
-
(a, b) => a.comparisonIndex - b.comparisonIndex
|
194
|
+
(a, b) => a.comparisonIndex - b.comparisonIndex
|
195
195
|
);
|
196
196
|
}
|
197
197
|
|
@@ -203,7 +203,7 @@ export default class OrderableListController extends Controller {
|
|
203
203
|
*/
|
204
204
|
#targetItem(element) {
|
205
205
|
return this.tablesOrderableItemOutlets.find(
|
206
|
-
(item) => item.element === element
|
206
|
+
(item) => item.element === element
|
207
207
|
);
|
208
208
|
}
|
209
209
|
|
data/config/importmap.rb
CHANGED
@@ -19,7 +19,7 @@ module Katalyst
|
|
19
19
|
#
|
20
20
|
# @return [[SortForm, ActiveRecord::Relation]]
|
21
21
|
def table_sort(collection)
|
22
|
-
column, direction = params[:sort]&.split
|
22
|
+
column, direction = params[:sort]&.split
|
23
23
|
direction = "asc" unless SortForm::DIRECTIONS.include?(direction)
|
24
24
|
|
25
25
|
SortForm.new(column: column,
|
@@ -9,14 +9,16 @@ module Katalyst
|
|
9
9
|
|
10
10
|
initializer "katalyst-tables.asset" do
|
11
11
|
config.after_initialize do |app|
|
12
|
-
|
12
|
+
if app.config.respond_to?(:assets)
|
13
|
+
app.config.assets.precompile += %w[katalyst-tables.js]
|
14
|
+
end
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
18
|
initializer "katalyst-tables.importmap", before: "importmap" do |app|
|
17
19
|
if app.config.respond_to?(:importmap)
|
18
20
|
app.config.importmap.paths << root.join("config/importmap.rb")
|
19
|
-
app.config.importmap.cache_sweepers << root.join("app/assets/
|
21
|
+
app.config.importmap.cache_sweepers << root.join("app/assets/builds")
|
20
22
|
end
|
21
23
|
end
|
22
24
|
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.
|
4
|
+
version: 2.3.0
|
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-12-
|
11
|
+
date: 2023-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: katalyst-html-attributes
|
@@ -49,11 +49,11 @@ files:
|
|
49
49
|
- CHANGELOG.md
|
50
50
|
- LICENSE.txt
|
51
51
|
- README.md
|
52
|
+
- app/assets/builds/katalyst/tables.esm.js
|
53
|
+
- app/assets/builds/katalyst/tables.js
|
54
|
+
- app/assets/builds/katalyst/tables.min.js
|
55
|
+
- app/assets/builds/katalyst/tables.min.js.map
|
52
56
|
- app/assets/config/katalyst-tables.js
|
53
|
-
- app/assets/javascripts/controllers/tables/orderable/form_controller.js
|
54
|
-
- app/assets/javascripts/controllers/tables/orderable/item_controller.js
|
55
|
-
- app/assets/javascripts/controllers/tables/orderable/list_controller.js
|
56
|
-
- app/assets/javascripts/controllers/tables/turbo_collection_controller.js
|
57
57
|
- app/components/concerns/katalyst/tables/configurable_component.rb
|
58
58
|
- app/components/concerns/katalyst/tables/has_table_content.rb
|
59
59
|
- app/components/concerns/katalyst/tables/orderable.rb
|
@@ -70,6 +70,11 @@ files:
|
|
70
70
|
- app/components/katalyst/tables/pagy_nav_component.rb
|
71
71
|
- app/components/katalyst/turbo/pagy_nav_component.rb
|
72
72
|
- app/components/katalyst/turbo/table_component.rb
|
73
|
+
- app/javascript/tables/application.js
|
74
|
+
- app/javascript/tables/orderable/form_controller.js
|
75
|
+
- app/javascript/tables/orderable/item_controller.js
|
76
|
+
- app/javascript/tables/orderable/list_controller.js
|
77
|
+
- app/javascript/tables/turbo/turbo_collection_controller.js
|
73
78
|
- app/models/concerns/katalyst/tables/collection/core.rb
|
74
79
|
- app/models/concerns/katalyst/tables/collection/pagination.rb
|
75
80
|
- app/models/concerns/katalyst/tables/collection/reducers.rb
|
@@ -100,14 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
100
105
|
requirements:
|
101
106
|
- - ">="
|
102
107
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
108
|
+
version: 3.0.0
|
104
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
110
|
requirements:
|
106
111
|
- - ">="
|
107
112
|
- !ruby/object:Gem::Version
|
108
113
|
version: '0'
|
109
114
|
requirements: []
|
110
|
-
rubygems_version: 3.4.
|
115
|
+
rubygems_version: 3.4.10
|
111
116
|
signing_key:
|
112
117
|
specification_version: 4
|
113
118
|
summary: HTML table generator for Rails views
|
File without changes
|
File without changes
|