katalyst-tables 2.3.0 → 2.4.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.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/app/assets/builds/katalyst/tables.esm.js +8 -8
  4. data/app/assets/builds/katalyst/tables.js +8 -8
  5. data/app/assets/builds/katalyst/tables.min.js +1 -1
  6. data/app/assets/builds/katalyst/tables.min.js.map +1 -1
  7. data/app/components/concerns/katalyst/tables/configurable_component.rb +3 -3
  8. data/app/components/concerns/katalyst/tables/has_table_content.rb +17 -3
  9. data/app/components/katalyst/table_component.rb +2 -1
  10. data/app/components/katalyst/turbo/table_component.rb +3 -3
  11. data/{lib → app/controllers/concerns}/katalyst/tables/backend.rb +4 -5
  12. data/{lib → app/helpers}/katalyst/tables/frontend.rb +0 -2
  13. data/app/javascript/tables/application.js +2 -2
  14. data/app/javascript/tables/orderable/form_controller.js +1 -1
  15. data/app/javascript/tables/orderable/list_controller.js +6 -6
  16. data/app/models/concerns/katalyst/tables/collection/core.rb +2 -29
  17. data/app/models/concerns/katalyst/tables/collection/filtering.rb +23 -0
  18. data/app/models/concerns/katalyst/tables/collection/has_params.rb +43 -0
  19. data/app/models/concerns/katalyst/tables/collection/sorting.rb +2 -2
  20. data/app/models/katalyst/tables/{collection.rb → collection/base.rb} +27 -1
  21. data/app/models/katalyst/tables/collection/filter.rb +80 -0
  22. data/{lib/katalyst/tables/backend → app/models/katalyst/tables/collection}/sort_form.rb +13 -5
  23. data/lib/katalyst/tables.rb +0 -4
  24. metadata +17 -14
  25. /data/{lib → app/helpers}/katalyst/tables/frontend/helper.rb +0 -0
  26. /data/app/javascript/tables/turbo/{turbo_collection_controller.js → collection_controller.js} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f02c8db5c8d61ac1a1e678ac9e4c020826c2289dc25a584b82a7786e859d9023
4
- data.tar.gz: 9d32b27a803509807facf9981f8b53eb8e696f5daa6e816427e32bc5d7c1bd03
3
+ metadata.gz: 5324d0bccadf307f48cb2199b8ba9123cb519756dda81cfc0c4c88a4abdfd886
4
+ data.tar.gz: ad299f1c8f62454aa3433b00cf4c0b69dacfe59cd6cfe598ab3ea2969cedecde
5
5
  SHA512:
6
- metadata.gz: e1c10b35c516f19094d4447db4fdb597847d650dce403a7f1062e04a3d35b17a2fb706e6e10f253b14300999e3570057bbc287d414c163c5e4440b5ac389349c
7
- data.tar.gz: c5ff9bbe6c0f0e159e8254eb97f92805528fc7a5d2f4b53f616d0a1a13211d512d421f10843568026b6b6788d8c5753a96444fd7d5ec7785efb508b20df03d56
6
+ metadata.gz: bd4daa1c09acaab6e85cbf787cfafb51303c9f030f26f15ca5f0a593da02c5dd526bf73d07b5cf6a99bfc267f2ea2a17d837e5eae81d607df2ad7788a9a99260
7
+ data.tar.gz: 143552907f2bf01ad256fc6e24b06404db1f173fe2ce3f4dca802eacd903fc5ab2f950a7c8c9ff6b086efe00c8838b8eca9dfbb2edcc0cc61d76717f6ca171ac
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.4.0]
4
+
5
+ - Internal refactor of filters to make it easier to add custom extensions
6
+ and define nested attributes.
7
+
3
8
  ## [2.3.0]
4
9
 
5
10
  - Remove support for Ruby < 3.0 (no longer used at Katalyst)
@@ -198,7 +198,7 @@ class OrderableListController extends Controller {
198
198
 
199
199
  // reindex all items based on their new positions
200
200
  this.tablesOrderableItemOutlets.forEach((item, index) =>
201
- item.updateIndex(index)
201
+ item.updateIndex(index),
202
202
  );
203
203
 
204
204
  // save the changes
@@ -250,7 +250,7 @@ class OrderableListController extends Controller {
250
250
  this.element,
251
251
  this.dragItem.row,
252
252
  event,
253
- this.animate
253
+ this.animate,
254
254
  );
255
255
  });
256
256
  };
@@ -265,7 +265,7 @@ class OrderableListController extends Controller {
265
265
  this.dragState.updateScroll(
266
266
  this.element,
267
267
  this.dragItem.row,
268
- this.animate
268
+ this.animate,
269
269
  );
270
270
  });
271
271
  };
@@ -325,7 +325,7 @@ class OrderableListController extends Controller {
325
325
  if (!this.isDragging) return null;
326
326
 
327
327
  return this.tablesOrderableItemOutlets.find(
328
- (item) => item.id === this.dragState.targetId
328
+ (item) => item.id === this.dragState.targetId,
329
329
  );
330
330
  }
331
331
 
@@ -337,7 +337,7 @@ class OrderableListController extends Controller {
337
337
  */
338
338
  get #currentItems() {
339
339
  return this.tablesOrderableItemOutlets.toSorted(
340
- (a, b) => a.comparisonIndex - b.comparisonIndex
340
+ (a, b) => a.comparisonIndex - b.comparisonIndex,
341
341
  );
342
342
  }
343
343
 
@@ -349,7 +349,7 @@ class OrderableListController extends Controller {
349
349
  */
350
350
  #targetItem(element) {
351
351
  return this.tablesOrderableItemOutlets.find(
352
- (item) => item.element === element
352
+ (item) => item.element === element,
353
353
  );
354
354
  }
355
355
 
@@ -455,7 +455,7 @@ class OrderableFormController extends Controller {
455
455
  this.element.insertAdjacentHTML(
456
456
  "beforeend",
457
457
  `<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
458
- <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`
458
+ <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
459
459
  );
460
460
  }
461
461
 
@@ -476,7 +476,7 @@ class OrderableFormController extends Controller {
476
476
 
477
477
  const Definitions = [
478
478
  {
479
- identifier: "tables--turbo--turbo-collection",
479
+ identifier: "tables--turbo--collection",
480
480
  controllerConstructor: TurboCollectionController,
481
481
  },
482
482
  {
@@ -198,7 +198,7 @@ class OrderableListController extends Controller {
198
198
 
199
199
  // reindex all items based on their new positions
200
200
  this.tablesOrderableItemOutlets.forEach((item, index) =>
201
- item.updateIndex(index)
201
+ item.updateIndex(index),
202
202
  );
203
203
 
204
204
  // save the changes
@@ -250,7 +250,7 @@ class OrderableListController extends Controller {
250
250
  this.element,
251
251
  this.dragItem.row,
252
252
  event,
253
- this.animate
253
+ this.animate,
254
254
  );
255
255
  });
256
256
  };
@@ -265,7 +265,7 @@ class OrderableListController extends Controller {
265
265
  this.dragState.updateScroll(
266
266
  this.element,
267
267
  this.dragItem.row,
268
- this.animate
268
+ this.animate,
269
269
  );
270
270
  });
271
271
  };
@@ -325,7 +325,7 @@ class OrderableListController extends Controller {
325
325
  if (!this.isDragging) return null;
326
326
 
327
327
  return this.tablesOrderableItemOutlets.find(
328
- (item) => item.id === this.dragState.targetId
328
+ (item) => item.id === this.dragState.targetId,
329
329
  );
330
330
  }
331
331
 
@@ -337,7 +337,7 @@ class OrderableListController extends Controller {
337
337
  */
338
338
  get #currentItems() {
339
339
  return this.tablesOrderableItemOutlets.toSorted(
340
- (a, b) => a.comparisonIndex - b.comparisonIndex
340
+ (a, b) => a.comparisonIndex - b.comparisonIndex,
341
341
  );
342
342
  }
343
343
 
@@ -349,7 +349,7 @@ class OrderableListController extends Controller {
349
349
  */
350
350
  #targetItem(element) {
351
351
  return this.tablesOrderableItemOutlets.find(
352
- (item) => item.element === element
352
+ (item) => item.element === element,
353
353
  );
354
354
  }
355
355
 
@@ -455,7 +455,7 @@ class OrderableFormController extends Controller {
455
455
  this.element.insertAdjacentHTML(
456
456
  "beforeend",
457
457
  `<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
458
- <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`
458
+ <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
459
459
  );
460
460
  }
461
461
 
@@ -476,7 +476,7 @@ class OrderableFormController extends Controller {
476
476
 
477
477
  const Definitions = [
478
478
  {
479
- identifier: "tables--turbo--turbo-collection",
479
+ identifier: "tables--turbo--collection",
480
480
  controllerConstructor: TurboCollectionController,
481
481
  },
482
482
  {
@@ -1,2 +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};
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,i){this.listOffset=t.getBoundingClientRect().top;let r=s.clientY-this.listOffset-this.cursorOffset;this.#t(t,e,r,i)}updateScroll(t,e,s){const i=this.listOffset;this.listOffset=t.getBoundingClientRect().top;const r=i-this.listOffset,a=this.position+r;this.#t(t,e,a,s)}#t(t,e,s,i){s=Math.max(s,0),s=Math.min(s,t.offsetHeight-e.offsetHeight),this.position=s;i(s-this.initialPosition)}}const i=[{identifier:"tables--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.#i(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.#r.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#r(){return this.tablesOrderableItemOutlets.toSorted(((t,e)=>t.comparisonIndex-e.comparisonIndex))}#i(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:i}=t.paramsValue;this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${e}" value="${s}" data-generated>\n <input type="hidden" name="${i}" 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{i as default};
2
2
  //# sourceMappingURL=tables.min.js.map
@@ -1 +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
+ {"version":3,"file":"tables.min.js","sources":["../../../javascript/tables/orderable/list_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/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/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--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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"}
@@ -24,9 +24,9 @@ module Katalyst
24
24
  # other tables.
25
25
  def config_component(name, component_name: "#{name}_component", default: nil) # rubocop:disable Metrics/MethodLength
26
26
  config_accessor(name)
27
- config.public_send("#{name}=", default)
27
+ config.public_send(:"#{name}=", default)
28
28
  define_method(component_name) do
29
- return instance_variable_get("@#{component_name}") if instance_variable_defined?("@#{component_name}")
29
+ return instance_variable_get(:"@#{component_name}") if instance_variable_defined?(:"@#{component_name}")
30
30
 
31
31
  klass = config.public_send(name)
32
32
  component = klass ? self.class.const_get(klass) : nil
@@ -37,7 +37,7 @@ module Katalyst
37
37
  component.extend(HiddenSubcomponent)
38
38
  end
39
39
 
40
- instance_variable_set("@#{component_name}", component) if component
40
+ instance_variable_set(:"@#{component_name}", component) if component
41
41
  end
42
42
  end
43
43
  end
@@ -24,9 +24,23 @@ module Katalyst
24
24
  end
25
25
 
26
26
  def row_partial(row, record = nil)
27
- partial = @partial || model_name&.param_key&.to_s
28
- as = @as || model_name&.param_key&.to_sym
29
- render(partial: partial, variants: [:row], formats: [:html], locals: { as => record, row: row })
27
+ @partial ||= partial_path
28
+ @as ||= template_name
29
+ render(partial: @partial, variants: [:row], formats: [:html], locals: { @as => record, row: row })
30
+ end
31
+
32
+ def partial_path
33
+ # Collection::Base overwrites param_key for form_with compatibility
34
+ items.model_name.param_key.to_s
35
+ end
36
+
37
+ def template_name
38
+ # Collection::Base overwrites param_key for form_with compatibility
39
+ items.model_name.param_key.to_sym
40
+ end
41
+
42
+ def items
43
+ collection.respond_to?(:items) ? collection.items : collection
30
44
  end
31
45
  end
32
46
  end
@@ -40,7 +40,8 @@ module Katalyst
40
40
  **html_attributes)
41
41
  @collection = collection
42
42
 
43
- # sorting: instance of Katalyst::Tables::Backend::SortForm. If not provided will be inferred from the collection.
43
+ # sorting: instance of Katalyst::Tables::Collection::SortForm.
44
+ # If not provided will be inferred from the collection.
44
45
  @sorting = sorting || html_attributes.delete(:sort) # backwards compatibility with old `sort` option
45
46
 
46
47
  # header: true means render the header row, header: false means no header row, if a hash, passes as options
@@ -26,9 +26,9 @@ module Katalyst
26
26
  def default_html_attributes
27
27
  {
28
28
  data: {
29
- controller: "tables--turbo-collection",
30
- tables__turbo_collection_query_value: current_query,
31
- tables__turbo_collection_sort_value: collection.sort,
29
+ controller: "tables--turbo--collection",
30
+ tables__turbo__collection_query_value: current_query,
31
+ tables__turbo__collection_sort_value: collection.sort,
32
32
  },
33
33
  }
34
34
  end
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support"
4
- require "active_support/concern"
5
-
6
- require_relative "backend/sort_form"
7
-
8
3
  module Katalyst
9
4
  module Tables
10
5
  # Utilities for controllers that are generating collections for visualisation
@@ -14,6 +9,10 @@ module Katalyst
14
9
  module Backend
15
10
  extend ActiveSupport::Concern
16
11
 
12
+ # @deprecated backwards compatibility
13
+ class SortForm < Katalyst::Tables::Collection::SortForm
14
+ end
15
+
17
16
  # Sort the given collection by params[:sort], which is set when a user
18
17
  # interacts with a column header in a frontend table view.
19
18
  #
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "frontend/helper"
4
-
5
3
  module Katalyst
6
4
  module Tables
7
5
  # View Helper for generating HTML tables. Include in your ApplicationHelper, or similar.
@@ -1,11 +1,11 @@
1
- import TurboCollectionController from "./turbo/turbo_collection_controller";
1
+ import TurboCollectionController from "./turbo/collection_controller";
2
2
  import ItemController from "./orderable/item_controller";
3
3
  import ListController from "./orderable/list_controller";
4
4
  import FormController from "./orderable/form_controller";
5
5
 
6
6
  const Definitions = [
7
7
  {
8
- identifier: "tables--turbo--turbo-collection",
8
+ identifier: "tables--turbo--collection",
9
9
  controllerConstructor: TurboCollectionController,
10
10
  },
11
11
  {
@@ -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
 
@@ -11,6 +11,7 @@ module Katalyst
11
11
  include ActiveModel::Dirty
12
12
  include ActiveSupport::Configurable
13
13
 
14
+ include HasParams
14
15
  include Reducers
15
16
 
16
17
  class_methods do
@@ -29,8 +30,7 @@ module Katalyst
29
30
  included do
30
31
  attr_accessor :items
31
32
 
32
- delegate :model, :each, :count, :empty?, to: :items, allow_nil: true
33
- delegate :model_name, to: :model, allow_nil: true
33
+ delegate :each, :count, :empty?, to: :items, allow_nil: true
34
34
  end
35
35
 
36
36
  def initialize(**options)
@@ -39,18 +39,6 @@ module Katalyst
39
39
  clear_changes_information
40
40
  end
41
41
 
42
- def filter
43
- # no-op by default
44
- end
45
-
46
- def filtered?
47
- self.class.new.filters != filters
48
- end
49
-
50
- def filters
51
- attributes.except("page", "sort")
52
- end
53
-
54
42
  def apply(items)
55
43
  @items = items
56
44
  reducers.build do |_|
@@ -59,21 +47,6 @@ module Katalyst
59
47
  end.call(self)
60
48
  self
61
49
  end
62
-
63
- def with_params(params)
64
- self.attributes = params.permit(self.class.permitted_params)
65
-
66
- self
67
- end
68
-
69
- # Returns a hash of the current attributes that have changed from defaults.
70
- def to_params
71
- attributes.slice(*changed)
72
- end
73
-
74
- def inspect
75
- "#<#{self.class.name} @attributes=#{attributes.inspect} @model=\"#{model}\" @count=#{items&.count}>"
76
- end
77
50
  end
78
51
  end
79
52
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Collection
6
+ using HasParams
7
+
8
+ module Filtering # :nodoc:
9
+ def filter
10
+ # no-op by default
11
+ end
12
+
13
+ def filtered?
14
+ filters.any?
15
+ end
16
+
17
+ def filters
18
+ changed_attributes.except("sort", "page")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Collection
6
+ module HasParams # :nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ refine Object do
10
+ def to_params
11
+ as_json
12
+ end
13
+ end
14
+
15
+ refine ActiveModel::AttributeSet do
16
+ def to_params
17
+ to_h.transform_values(&:to_params).as_json
18
+ end
19
+ end
20
+
21
+ refine ActiveModel::Attributes do
22
+ def to_params
23
+ if respond_to?(:changed)
24
+ @attributes.to_params.slice(*changed)
25
+ else
26
+ @attributes.to_params
27
+ end
28
+ end
29
+ end
30
+
31
+ using HasParams
32
+
33
+ # Returns a hash of the current attributes that have changed from defaults.
34
+ # This uses Refinements internally so it needs to be exposed publicly with this super call.
35
+ # rubocop:disable Lint/UselessMethodDefinition
36
+ def to_params
37
+ super
38
+ end
39
+ # rubocop:enable Lint/UselessMethodDefinition
40
+ end
41
+ end
42
+ end
43
+ end
@@ -22,7 +22,7 @@ module Katalyst
22
22
  end
23
23
 
24
24
  def initialize(sorting: config.sorting, **options)
25
- @sorting = Backend::SortForm.parse(sorting) if sorting
25
+ @sorting = SortForm.parse(sorting) if sorting
26
26
 
27
27
  super(sort: sorting, **options) # set default sort based on config
28
28
  end
@@ -31,7 +31,7 @@ module Katalyst
31
31
  return unless @sorting
32
32
 
33
33
  # update internal proxy
34
- @sorting = Backend::SortForm.parse(value, default: attribute_was(:sort))
34
+ @sorting = SortForm.parse(value, default: attribute_was(:sort))
35
35
 
36
36
  # update attribute based on normalized value
37
37
  super(@sorting.to_param)
@@ -3,7 +3,9 @@
3
3
  module Katalyst
4
4
  module Tables
5
5
  module Collection
6
- # Entry point for creating a collection for use with table components.
6
+ # Entry point for creating a collection for use with table components
7
+ # where filter params are flat, e.g. ?search=query
8
+ #
7
9
  # This class is intended to be subclassed, i.e.:
8
10
  #
9
11
  # class ApplicationController < ActionController::Base
@@ -21,11 +23,35 @@ module Katalyst
21
23
  # ````
22
24
  class Base
23
25
  include Core
26
+ include Filtering
24
27
  include Pagination
25
28
  include Sorting
26
29
 
27
30
  use(Sorting::Sort)
28
31
  use(Pagination::Paginate)
32
+
33
+ def self.with_params(params)
34
+ new.with_params(params)
35
+ end
36
+
37
+ def model_name
38
+ @model_name ||= items.model_name.dup.tap do |name|
39
+ name.param_key = ""
40
+ end
41
+ end
42
+
43
+ def with_params(params)
44
+ # test support
45
+ params = ActionController::Parameters.new(params) unless params.is_a?(ActionController::Parameters)
46
+
47
+ self.attributes = params.permit(self.class.permitted_params)
48
+
49
+ self
50
+ end
51
+
52
+ def inspect
53
+ "#<#{self.class.name} @attributes=#{attributes.inspect} @model_name=\"#{model_name}\" @count=#{items&.count}>"
54
+ end
29
55
  end
30
56
  end
31
57
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Collection
6
+ # Entry point for creating a collection for use with table components
7
+ # where filter params are nested, e.g. ?filters[search]=query
8
+ #
9
+ # This class is intended to be subclassed, i.e.:
10
+ #
11
+ # class ApplicationController < ActionController::Base
12
+ # class Collection < Katalyst::Tables::Collection::Base
13
+ # ...
14
+ # end
15
+ # end
16
+ #
17
+ # In the context of a controller action, construct a collection, apply it
18
+ # to a model, then pass the result to the view component:
19
+ # ```
20
+ # collection = Collection.new.with_params(params).apply(People.all)
21
+ # table = Katalyst::TableComponent.new(collection: collection)
22
+ # render table
23
+ # ````
24
+ class Filter
25
+ include Core
26
+ include Filtering
27
+ include Pagination
28
+ include Sorting
29
+
30
+ use(Sorting::Sort)
31
+ use(Pagination::Paginate)
32
+
33
+ def self.with_params(params)
34
+ new.with_params(params)
35
+ end
36
+
37
+ def self.permitted_params
38
+ super.excluding("sort", "page")
39
+ end
40
+
41
+ attr_reader :param_key
42
+
43
+ def initialize(param_key: :filters, **options)
44
+ super(**options)
45
+
46
+ @param_key = param_key.to_sym
47
+ end
48
+
49
+ def model_name
50
+ @model_name ||= items.model_name.tap do |name|
51
+ name.param_key = param_key
52
+ end
53
+ end
54
+
55
+ def with_params(params)
56
+ params = params.permit(:sort, :page, param_key => self.class.permitted_params)
57
+ filters = params[param_key]
58
+
59
+ self.attributes = filters if filters.present?
60
+ self.attributes = params.slice("page", "sort")
61
+
62
+ self
63
+ end
64
+
65
+ def to_params
66
+ if filtered?
67
+ { param_key.to_s => super.except("sort", "page") }.merge(super.slice("sort", "page"))
68
+ else
69
+ super.slice("sort", "page")
70
+ end
71
+ end
72
+
73
+ def inspect
74
+ "#<#{self.class.name} @param_key=#{param_key.inspect} " +
75
+ "@attributes=#{attributes.inspect} @model=\"#{model}\" @count=#{items&.count}>"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Katalyst
4
4
  module Tables
5
- module Backend
5
+ module Collection
6
6
  # A FormObject (model) representing the sort state of controller for a given
7
7
  # collection/parameter.
8
8
  class SortForm
@@ -37,8 +37,8 @@ module Katalyst
37
37
  # @param column [String, Symbol]
38
38
  # @return [true, false]
39
39
  def supports?(collection, column)
40
- collection.respond_to?("order_by_#{column}") ||
41
- collection.model.has_attribute?(column.to_s)
40
+ scope_for(collection).respond_to?(:"order_by_#{column}") ||
41
+ model_for(collection).has_attribute?(column.to_s)
42
42
  end
43
43
 
44
44
  # Returns the current sort behaviour of the given column, for use as a
@@ -72,8 +72,8 @@ module Katalyst
72
72
  def apply(collection)
73
73
  return [self, collection] if column.nil?
74
74
 
75
- if collection.respond_to?("order_by_#{column}")
76
- collection = collection.reorder(nil).public_send("order_by_#{column}", direction.to_sym)
75
+ if collection.respond_to?(:"order_by_#{column}")
76
+ collection = collection.reorder(nil).public_send(:"order_by_#{column}", direction.to_sym)
77
77
  elsif collection.model.has_attribute?(column)
78
78
  collection = collection.reorder(column => direction)
79
79
  else
@@ -88,6 +88,14 @@ module Katalyst
88
88
  def clear!
89
89
  self.column = self.direction = nil
90
90
  end
91
+
92
+ def scope_for(collection)
93
+ collection.is_a?(Core) ? collection.items : collection
94
+ end
95
+
96
+ def model_for(collection)
97
+ scope_for(collection).model
98
+ end
91
99
  end
92
100
  end
93
101
  end
@@ -3,11 +3,7 @@
3
3
  require "view_component"
4
4
  require "katalyst/html_attributes"
5
5
 
6
- require_relative "tables/backend"
7
6
  require_relative "tables/engine"
8
- require_relative "tables/frontend"
9
-
10
- require_relative "tables/engine" if Object.const_defined?(:Rails)
11
7
 
12
8
  module Katalyst
13
9
  module Tables
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.3.0
4
+ version: 2.4.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-15 00:00:00.000000000 Z
11
+ date: 2024-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: katalyst-html-attributes
@@ -38,8 +38,8 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description: Builder-style HTML table generator for building tabular index views.
42
- Supports sorting by columns.
41
+ description: HTML table generator for building tabular index views with filtering,
42
+ sorting, and pagination.
43
43
  email:
44
44
  - devs@katalyst.com.au
45
45
  executables: []
@@ -70,33 +70,36 @@ 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/controllers/concerns/katalyst/tables/backend.rb
74
+ - app/helpers/katalyst/tables/frontend.rb
75
+ - app/helpers/katalyst/tables/frontend/helper.rb
73
76
  - app/javascript/tables/application.js
74
77
  - app/javascript/tables/orderable/form_controller.js
75
78
  - app/javascript/tables/orderable/item_controller.js
76
79
  - app/javascript/tables/orderable/list_controller.js
77
- - app/javascript/tables/turbo/turbo_collection_controller.js
80
+ - app/javascript/tables/turbo/collection_controller.js
78
81
  - app/models/concerns/katalyst/tables/collection/core.rb
82
+ - app/models/concerns/katalyst/tables/collection/filtering.rb
83
+ - app/models/concerns/katalyst/tables/collection/has_params.rb
79
84
  - app/models/concerns/katalyst/tables/collection/pagination.rb
80
85
  - app/models/concerns/katalyst/tables/collection/reducers.rb
81
86
  - app/models/concerns/katalyst/tables/collection/sorting.rb
82
- - app/models/katalyst/tables/collection.rb
87
+ - app/models/katalyst/tables/collection/base.rb
88
+ - app/models/katalyst/tables/collection/filter.rb
89
+ - app/models/katalyst/tables/collection/sort_form.rb
83
90
  - config/importmap.rb
84
91
  - config/locales/tables.en.yml
85
92
  - lib/katalyst/tables.rb
86
- - lib/katalyst/tables/backend.rb
87
- - lib/katalyst/tables/backend/sort_form.rb
88
93
  - lib/katalyst/tables/engine.rb
89
- - lib/katalyst/tables/frontend.rb
90
- - lib/katalyst/tables/frontend/helper.rb
91
- homepage: https://github.com/katalyst/katalyst-tables
94
+ homepage: https://github.com/katalyst/tables
92
95
  licenses:
93
96
  - MIT
94
97
  metadata:
95
98
  allowed_push_host: https://rubygems.org
96
99
  rubygems_mfa_required: 'true'
97
- homepage_uri: https://github.com/katalyst/katalyst-tables
98
- source_code_uri: https://github.com/katalyst/katalyst-tables
99
- changelog_uri: https://github.com/katalyst/katalyst-tables/blobs/main/CHANGELOG.md
100
+ homepage_uri: https://github.com/katalyst/tables
101
+ source_code_uri: https://github.com/katalyst/tables
102
+ changelog_uri: https://github.com/katalyst/tables/blobs/main/CHANGELOG.md
100
103
  post_install_message:
101
104
  rdoc_options: []
102
105
  require_paths: