katalyst-tables 3.11.3 → 3.12.0

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: f1a656d6c4afdbaaa9775b017ece93c5a1ed47f1fa23b6a629ee902ea6862de8
4
- data.tar.gz: 6ae825a285b001c1a9fdb88f4b5611a1cc7a87273d13819bcf863f3c9c3a4ed6
3
+ metadata.gz: 2c67bc4631e5e9cb0b84c73e61572dd2f7c2d049700a10172d56124a2e59a997
4
+ data.tar.gz: 897729b9bed4774e874fa09df37d6e3c6a1c651c465431d182f047843df7a2df
5
5
  SHA512:
6
- metadata.gz: 24ff7144511a36aac499f10f035eea0fd5c734c0f98e21caf7094e48902796d24ad5f8e30cc14f0f0c8e3c3a68ae7336f732fb974a038b541872a9b4faa8e8cc
7
- data.tar.gz: c8e1be4b57246e7bf3cdd988aeb812b34e95c518c8176f4931040cf53ceb1351f23f8bdfd7b89239bc5a2b139447336e22fa9d7d88055a206ff92f15177160ce
6
+ metadata.gz: f02e1176c948660ecae3fde031492bdcbf1b7ffde9e20ca9018277da2cc80f5c353a7eabd97d070a2c5a4fb27923e52668edea461cd3008bd71d06407675e2e0
7
+ data.tar.gz: f519c5b3518213bebe01a994d0cf3573d11974d16949573fb5ab0f884887f407ed0fed655f2bd14c7d83884cf149b53983fb363bfe186181821d2d67371a63e7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [3.12.0]
2
+
3
+ * Support for ordering variable height rows
4
+
1
5
  ## [3.11.0]
2
6
 
3
7
  * Support for Pagy 43
@@ -19,7 +19,7 @@ class OrderableRowController extends Controller {
19
19
  dragUpdate(offset) {
20
20
  this.dragOffset = offset;
21
21
  this.row.style.position = "relative";
22
- this.row.style.top = offset + "px";
22
+ this.row.style.transform = `scale(1.01) translateY(${offset}px)`;
23
23
  this.row.style.zIndex = "1";
24
24
  this.row.toggleAttribute("dragging", true);
25
25
  }
@@ -28,13 +28,27 @@ class OrderableRowController extends Controller {
28
28
  * Called on items that are not the dragged item during drag. Updates the
29
29
  * visual position of the item relative to the dragged item.
30
30
  *
31
- * @param index {number} intended index of the item during drag
31
+ * @param offset {number} intended offset of the item during drag
32
32
  */
33
- updateVisually(index) {
34
- this.row.style.position = "relative";
35
- this.row.style.top = `${
36
- this.row.offsetHeight * (index - this.dragIndex)
37
- }px`;
33
+ updateVisually(offset) {
34
+ this.row.style.transform = `translateY(${offset - this.#offsetTop}px)`;
35
+ }
36
+
37
+ captureDropPosition() {
38
+ this.dropTop = this.#viewportTop;
39
+ }
40
+
41
+ invertDropPosition() {
42
+ delete this.dragOffset;
43
+ this.row.removeAttribute("dragging");
44
+ this.row.style.transition = "transform 0s";
45
+ this.row.style.transform = "";
46
+ this.row.style.transform = `translateY(${this.dropTop - this.#viewportTop}px)`;
47
+ }
48
+
49
+ playDrop() {
50
+ this.row.style.transition = "";
51
+ this.row.style.transform = "";
38
52
  }
39
53
 
40
54
  /**
@@ -62,6 +76,7 @@ class OrderableRowController extends Controller {
62
76
  */
63
77
  reset() {
64
78
  delete this.dragOffset;
79
+ delete this.dropTop;
65
80
  this.row.removeAttribute("style");
66
81
  this.row.removeAttribute("dragging");
67
82
  }
@@ -74,34 +89,15 @@ class OrderableRowController extends Controller {
74
89
  }
75
90
 
76
91
  /**
77
- * Calculate the relative index of the item during drag. This is used to
78
- * sort items during drag as it takes into account any uncommitted changes
79
- * to index caused by the drag offset.
80
- *
81
- * @returns {number} index for the purposes of drag and drop ordering
82
- */
83
- get dragIndex() {
84
- if (this.dragOffset && this.dragOffset !== 0) {
85
- return this.index + Math.round(this.dragOffset / this.row.offsetHeight);
86
- } else {
87
- return this.index;
88
- }
89
- }
90
-
91
- /**
92
- * Index value for use in comparisons during drag. This is used to determine
93
- * whether the dragged item is above or below another item. If this item is
94
- * being dragged then we offset the index by 0.5 to ensure that it jumps up
95
- * or down when it reaches the midpoint of the item above or below it.
92
+ * Value for use in comparisons during drag. Used by ListController to
93
+ * determine whether the dragged item is above or below another item.
96
94
  *
97
95
  * @returns {number}
98
96
  */
99
- get comparisonIndex() {
100
- if (this.dragOffset) {
101
- return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);
102
- } else {
103
- return this.index;
104
- }
97
+ get dragPosition() {
98
+ return this.dragOffset && this.dragOffset !== 0
99
+ ? this.#leadingEdge
100
+ : this.#midpoint;
105
101
  }
106
102
 
107
103
  /**
@@ -112,6 +108,28 @@ class OrderableRowController extends Controller {
112
108
  get row() {
113
109
  return this.element.parentElement;
114
110
  }
111
+
112
+ get height() {
113
+ return this.row.offsetHeight;
114
+ }
115
+
116
+ get #midpoint() {
117
+ return this.#offsetTop + this.height / 2;
118
+ }
119
+
120
+ get #leadingEdge() {
121
+ const top = this.#offsetTop + this.dragOffset;
122
+
123
+ return this.dragOffset < 0 ? top : top + this.height;
124
+ }
125
+
126
+ get #offsetTop() {
127
+ return this.row.offsetTop;
128
+ }
129
+
130
+ get #viewportTop() {
131
+ return this.row.getBoundingClientRect().top;
132
+ }
115
133
  }
116
134
 
117
135
  function domIndex(element) {
@@ -133,7 +151,7 @@ class OrderableListController extends Controller {
133
151
  this.element.style.position = "relative";
134
152
  }
135
153
 
136
- stopDragging() {
154
+ stopDragging({ reset = true } = {}) {
137
155
  const dragState = this.dragState;
138
156
  delete this.dragState;
139
157
 
@@ -142,7 +160,7 @@ class OrderableListController extends Controller {
142
160
  window.removeEventListener("scroll", this.scroll, true);
143
161
 
144
162
  this.element.removeAttribute("style");
145
- this.tablesOrderableItemOutlets.forEach((item) => item.reset());
163
+ if (reset) this.items.forEach((item) => item.reset());
146
164
 
147
165
  return dragState;
148
166
  }
@@ -156,25 +174,30 @@ class OrderableListController extends Controller {
156
174
 
157
175
  if (!dragItem) return;
158
176
 
159
- const newIndex = dragItem.dragIndex;
160
- const targetItem = this.tablesOrderableItemOutlets[newIndex];
177
+ const items = this.items;
178
+ items.forEach((item) => item.captureDropPosition());
161
179
 
162
- if (!targetItem) return;
180
+ this.#insertDragItem(this.orderedItems, dragItem);
163
181
 
164
- // swap the dragged item into the correct position for its current offset
165
- if (newIndex < dragItem.index) {
166
- targetItem.row.insertAdjacentElement("beforebegin", dragItem.row);
167
- } else if (newIndex > dragItem.index) {
168
- targetItem.row.insertAdjacentElement("afterend", dragItem.row);
169
- }
182
+ items.forEach((item) => item.invertDropPosition());
183
+
184
+ // Commit the inverted transforms before enabling transitions to play out.
185
+ this.element.offsetHeight;
170
186
 
171
187
  // reindex all items based on their new positions
172
- this.tablesOrderableItemOutlets.forEach((item, index) =>
173
- item.updateIndex(index),
174
- );
188
+ this.items.forEach((item, index) => item.updateIndex(index));
175
189
 
176
190
  // save the changes
177
191
  this.commitChanges();
192
+
193
+ window.requestAnimationFrame(() => {
194
+ items.forEach((item) => item.playDrop());
195
+ window.setTimeout(() => {
196
+ items.forEach((item) => item.reset());
197
+ }, this.#animationDuration);
198
+ });
199
+
200
+ return true;
178
201
  }
179
202
 
180
203
  commitChanges() {
@@ -182,7 +205,7 @@ class OrderableListController extends Controller {
182
205
  this.tablesOrderableFormOutlet.clear();
183
206
 
184
207
  // insert any items that have changed position
185
- this.tablesOrderableItemOutlets.forEach((item) => {
208
+ this.items.forEach((item) => {
186
209
  if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);
187
210
  });
188
211
 
@@ -245,9 +268,9 @@ class OrderableListController extends Controller {
245
268
  mouseup = (event) => {
246
269
  if (!this.isDragging) return;
247
270
 
248
- this.drop();
249
- this.stopDragging();
250
- this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);
271
+ const dropped = this.drop();
272
+ this.stopDragging({ reset: !dropped });
273
+ this.items.forEach((form) => delete form.dragState);
251
274
  };
252
275
 
253
276
  tablesOrderableFormOutletConnected(form, element) {
@@ -290,9 +313,11 @@ class OrderableListController extends Controller {
290
313
 
291
314
  // Visually updates the position of all items in the list relative to the
292
315
  // dragged item. No actual changes to orderings at this stage.
293
- this.#currentItems.forEach((item, index) => {
294
- if (item === dragItem) return;
295
- item.updateVisually(index);
316
+ let nextOffset = 0;
317
+
318
+ this.orderedItems.forEach((item) => {
319
+ if (item !== dragItem) item.updateVisually(nextOffset);
320
+ nextOffset += item.height;
296
321
  });
297
322
  };
298
323
 
@@ -303,23 +328,30 @@ class OrderableListController extends Controller {
303
328
  get dragItem() {
304
329
  if (!this.isDragging) return null;
305
330
 
306
- return this.tablesOrderableItemOutlets.find(
307
- (item) => item.id === this.dragState.targetId,
308
- );
331
+ return this.items.find((item) => item.id === this.dragState.targetId);
309
332
  }
310
333
 
311
334
  /**
312
- * Returns the current items in the list, sorted by their current index.
313
- * Current uses the drag index if the item is being dragged, if set.
335
+ * Returns the current items in the list in DOM order.
314
336
  *
315
337
  * @returns {Array[OrderableRowController]}
316
338
  */
317
- get #currentItems() {
318
- return this.tablesOrderableItemOutlets.toSorted(
319
- (a, b) => a.comparisonIndex - b.comparisonIndex,
339
+ get items() {
340
+ // Note, item outlets may include other items as the outlets do not have a
341
+ // mechanism to specify an element scope.
342
+ return this.tablesOrderableItemOutlets.filter((item) =>
343
+ this.element.contains(item.element),
320
344
  );
321
345
  }
322
346
 
347
+ get orderedItems() {
348
+ const dragItem = this.dragItem;
349
+
350
+ if (!dragItem) return this.items;
351
+
352
+ return this.items.toSorted((a, b) => a.dragPosition - b.dragPosition);
353
+ }
354
+
323
355
  /**
324
356
  * Returns the item outlet that was clicked on, if any.
325
357
  *
@@ -327,14 +359,42 @@ class OrderableListController extends Controller {
327
359
  * @returns {OrderableRowController}
328
360
  */
329
361
  #targetItem(element) {
330
- return this.tablesOrderableItemOutlets.find(
331
- (item) => item.element === element,
332
- );
362
+ return this.items.find((item) => item.element === element);
363
+ }
364
+
365
+ #insertDragItem(orderedItems, dragItem) {
366
+ const index = orderedItems.indexOf(dragItem);
367
+ const previousItem = orderedItems[index - 1];
368
+ const nextItem = orderedItems[index + 1];
369
+
370
+ if (previousItem) {
371
+ previousItem.row.insertAdjacentElement("afterend", dragItem.row);
372
+ } else if (nextItem) {
373
+ nextItem.row.insertAdjacentElement("beforebegin", dragItem.row);
374
+ }
375
+ }
376
+
377
+ get #animationDuration() {
378
+ const duration =
379
+ window
380
+ .getComputedStyle(this.element)
381
+ .getPropertyValue("--animation-duration") || "150ms";
382
+
383
+ return parseTime(duration);
333
384
  }
334
385
 
335
386
  //endregion
336
387
  }
337
388
 
389
+ function parseTime(value) {
390
+ value = value.trim();
391
+
392
+ if (value.endsWith("ms")) return parseFloat(value);
393
+ if (value.endsWith("s")) return parseFloat(value) * 1000;
394
+
395
+ return parseFloat(value) || 0;
396
+ }
397
+
338
398
  /**
339
399
  * During drag we want to be able to translate a document-relative coordinate
340
400
  * into a coordinate relative to the list element. This state object calculates
@@ -19,7 +19,7 @@ class OrderableRowController extends Controller {
19
19
  dragUpdate(offset) {
20
20
  this.dragOffset = offset;
21
21
  this.row.style.position = "relative";
22
- this.row.style.top = offset + "px";
22
+ this.row.style.transform = `scale(1.01) translateY(${offset}px)`;
23
23
  this.row.style.zIndex = "1";
24
24
  this.row.toggleAttribute("dragging", true);
25
25
  }
@@ -28,13 +28,27 @@ class OrderableRowController extends Controller {
28
28
  * Called on items that are not the dragged item during drag. Updates the
29
29
  * visual position of the item relative to the dragged item.
30
30
  *
31
- * @param index {number} intended index of the item during drag
31
+ * @param offset {number} intended offset of the item during drag
32
32
  */
33
- updateVisually(index) {
34
- this.row.style.position = "relative";
35
- this.row.style.top = `${
36
- this.row.offsetHeight * (index - this.dragIndex)
37
- }px`;
33
+ updateVisually(offset) {
34
+ this.row.style.transform = `translateY(${offset - this.#offsetTop}px)`;
35
+ }
36
+
37
+ captureDropPosition() {
38
+ this.dropTop = this.#viewportTop;
39
+ }
40
+
41
+ invertDropPosition() {
42
+ delete this.dragOffset;
43
+ this.row.removeAttribute("dragging");
44
+ this.row.style.transition = "transform 0s";
45
+ this.row.style.transform = "";
46
+ this.row.style.transform = `translateY(${this.dropTop - this.#viewportTop}px)`;
47
+ }
48
+
49
+ playDrop() {
50
+ this.row.style.transition = "";
51
+ this.row.style.transform = "";
38
52
  }
39
53
 
40
54
  /**
@@ -62,6 +76,7 @@ class OrderableRowController extends Controller {
62
76
  */
63
77
  reset() {
64
78
  delete this.dragOffset;
79
+ delete this.dropTop;
65
80
  this.row.removeAttribute("style");
66
81
  this.row.removeAttribute("dragging");
67
82
  }
@@ -74,34 +89,15 @@ class OrderableRowController extends Controller {
74
89
  }
75
90
 
76
91
  /**
77
- * Calculate the relative index of the item during drag. This is used to
78
- * sort items during drag as it takes into account any uncommitted changes
79
- * to index caused by the drag offset.
80
- *
81
- * @returns {number} index for the purposes of drag and drop ordering
82
- */
83
- get dragIndex() {
84
- if (this.dragOffset && this.dragOffset !== 0) {
85
- return this.index + Math.round(this.dragOffset / this.row.offsetHeight);
86
- } else {
87
- return this.index;
88
- }
89
- }
90
-
91
- /**
92
- * Index value for use in comparisons during drag. This is used to determine
93
- * whether the dragged item is above or below another item. If this item is
94
- * being dragged then we offset the index by 0.5 to ensure that it jumps up
95
- * or down when it reaches the midpoint of the item above or below it.
92
+ * Value for use in comparisons during drag. Used by ListController to
93
+ * determine whether the dragged item is above or below another item.
96
94
  *
97
95
  * @returns {number}
98
96
  */
99
- get comparisonIndex() {
100
- if (this.dragOffset) {
101
- return this.dragIndex + (this.dragOffset > 0 ? 0.5 : -0.5);
102
- } else {
103
- return this.index;
104
- }
97
+ get dragPosition() {
98
+ return this.dragOffset && this.dragOffset !== 0
99
+ ? this.#leadingEdge
100
+ : this.#midpoint;
105
101
  }
106
102
 
107
103
  /**
@@ -112,6 +108,28 @@ class OrderableRowController extends Controller {
112
108
  get row() {
113
109
  return this.element.parentElement;
114
110
  }
111
+
112
+ get height() {
113
+ return this.row.offsetHeight;
114
+ }
115
+
116
+ get #midpoint() {
117
+ return this.#offsetTop + this.height / 2;
118
+ }
119
+
120
+ get #leadingEdge() {
121
+ const top = this.#offsetTop + this.dragOffset;
122
+
123
+ return this.dragOffset < 0 ? top : top + this.height;
124
+ }
125
+
126
+ get #offsetTop() {
127
+ return this.row.offsetTop;
128
+ }
129
+
130
+ get #viewportTop() {
131
+ return this.row.getBoundingClientRect().top;
132
+ }
115
133
  }
116
134
 
117
135
  function domIndex(element) {
@@ -133,7 +151,7 @@ class OrderableListController extends Controller {
133
151
  this.element.style.position = "relative";
134
152
  }
135
153
 
136
- stopDragging() {
154
+ stopDragging({ reset = true } = {}) {
137
155
  const dragState = this.dragState;
138
156
  delete this.dragState;
139
157
 
@@ -142,7 +160,7 @@ class OrderableListController extends Controller {
142
160
  window.removeEventListener("scroll", this.scroll, true);
143
161
 
144
162
  this.element.removeAttribute("style");
145
- this.tablesOrderableItemOutlets.forEach((item) => item.reset());
163
+ if (reset) this.items.forEach((item) => item.reset());
146
164
 
147
165
  return dragState;
148
166
  }
@@ -156,25 +174,30 @@ class OrderableListController extends Controller {
156
174
 
157
175
  if (!dragItem) return;
158
176
 
159
- const newIndex = dragItem.dragIndex;
160
- const targetItem = this.tablesOrderableItemOutlets[newIndex];
177
+ const items = this.items;
178
+ items.forEach((item) => item.captureDropPosition());
161
179
 
162
- if (!targetItem) return;
180
+ this.#insertDragItem(this.orderedItems, dragItem);
163
181
 
164
- // swap the dragged item into the correct position for its current offset
165
- if (newIndex < dragItem.index) {
166
- targetItem.row.insertAdjacentElement("beforebegin", dragItem.row);
167
- } else if (newIndex > dragItem.index) {
168
- targetItem.row.insertAdjacentElement("afterend", dragItem.row);
169
- }
182
+ items.forEach((item) => item.invertDropPosition());
183
+
184
+ // Commit the inverted transforms before enabling transitions to play out.
185
+ this.element.offsetHeight;
170
186
 
171
187
  // reindex all items based on their new positions
172
- this.tablesOrderableItemOutlets.forEach((item, index) =>
173
- item.updateIndex(index),
174
- );
188
+ this.items.forEach((item, index) => item.updateIndex(index));
175
189
 
176
190
  // save the changes
177
191
  this.commitChanges();
192
+
193
+ window.requestAnimationFrame(() => {
194
+ items.forEach((item) => item.playDrop());
195
+ window.setTimeout(() => {
196
+ items.forEach((item) => item.reset());
197
+ }, this.#animationDuration);
198
+ });
199
+
200
+ return true;
178
201
  }
179
202
 
180
203
  commitChanges() {
@@ -182,7 +205,7 @@ class OrderableListController extends Controller {
182
205
  this.tablesOrderableFormOutlet.clear();
183
206
 
184
207
  // insert any items that have changed position
185
- this.tablesOrderableItemOutlets.forEach((item) => {
208
+ this.items.forEach((item) => {
186
209
  if (item.hasChanges) this.tablesOrderableFormOutlet.add(item);
187
210
  });
188
211
 
@@ -245,9 +268,9 @@ class OrderableListController extends Controller {
245
268
  mouseup = (event) => {
246
269
  if (!this.isDragging) return;
247
270
 
248
- this.drop();
249
- this.stopDragging();
250
- this.tablesOrderableFormOutlets.forEach((form) => delete form.dragState);
271
+ const dropped = this.drop();
272
+ this.stopDragging({ reset: !dropped });
273
+ this.items.forEach((form) => delete form.dragState);
251
274
  };
252
275
 
253
276
  tablesOrderableFormOutletConnected(form, element) {
@@ -290,9 +313,11 @@ class OrderableListController extends Controller {
290
313
 
291
314
  // Visually updates the position of all items in the list relative to the
292
315
  // dragged item. No actual changes to orderings at this stage.
293
- this.#currentItems.forEach((item, index) => {
294
- if (item === dragItem) return;
295
- item.updateVisually(index);
316
+ let nextOffset = 0;
317
+
318
+ this.orderedItems.forEach((item) => {
319
+ if (item !== dragItem) item.updateVisually(nextOffset);
320
+ nextOffset += item.height;
296
321
  });
297
322
  };
298
323
 
@@ -303,23 +328,30 @@ class OrderableListController extends Controller {
303
328
  get dragItem() {
304
329
  if (!this.isDragging) return null;
305
330
 
306
- return this.tablesOrderableItemOutlets.find(
307
- (item) => item.id === this.dragState.targetId,
308
- );
331
+ return this.items.find((item) => item.id === this.dragState.targetId);
309
332
  }
310
333
 
311
334
  /**
312
- * Returns the current items in the list, sorted by their current index.
313
- * Current uses the drag index if the item is being dragged, if set.
335
+ * Returns the current items in the list in DOM order.
314
336
  *
315
337
  * @returns {Array[OrderableRowController]}
316
338
  */
317
- get #currentItems() {
318
- return this.tablesOrderableItemOutlets.toSorted(
319
- (a, b) => a.comparisonIndex - b.comparisonIndex,
339
+ get items() {
340
+ // Note, item outlets may include other items as the outlets do not have a
341
+ // mechanism to specify an element scope.
342
+ return this.tablesOrderableItemOutlets.filter((item) =>
343
+ this.element.contains(item.element),
320
344
  );
321
345
  }
322
346
 
347
+ get orderedItems() {
348
+ const dragItem = this.dragItem;
349
+
350
+ if (!dragItem) return this.items;
351
+
352
+ return this.items.toSorted((a, b) => a.dragPosition - b.dragPosition);
353
+ }
354
+
323
355
  /**
324
356
  * Returns the item outlet that was clicked on, if any.
325
357
  *
@@ -327,14 +359,42 @@ class OrderableListController extends Controller {
327
359
  * @returns {OrderableRowController}
328
360
  */
329
361
  #targetItem(element) {
330
- return this.tablesOrderableItemOutlets.find(
331
- (item) => item.element === element,
332
- );
362
+ return this.items.find((item) => item.element === element);
363
+ }
364
+
365
+ #insertDragItem(orderedItems, dragItem) {
366
+ const index = orderedItems.indexOf(dragItem);
367
+ const previousItem = orderedItems[index - 1];
368
+ const nextItem = orderedItems[index + 1];
369
+
370
+ if (previousItem) {
371
+ previousItem.row.insertAdjacentElement("afterend", dragItem.row);
372
+ } else if (nextItem) {
373
+ nextItem.row.insertAdjacentElement("beforebegin", dragItem.row);
374
+ }
375
+ }
376
+
377
+ get #animationDuration() {
378
+ const duration =
379
+ window
380
+ .getComputedStyle(this.element)
381
+ .getPropertyValue("--animation-duration") || "150ms";
382
+
383
+ return parseTime(duration);
333
384
  }
334
385
 
335
386
  //endregion
336
387
  }
337
388
 
389
+ function parseTime(value) {
390
+ value = value.trim();
391
+
392
+ if (value.endsWith("ms")) return parseFloat(value);
393
+ if (value.endsWith("s")) return parseFloat(value) * 1000;
394
+
395
+ return parseFloat(value) || 0;
396
+ }
397
+
338
398
  /**
339
399
  * During drag we want to be able to translate a document-relative coordinate
340
400
  * into a coordinate relative to the list element. This state object calculates