katalyst-tables 3.3.4 → 3.4.1

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: 3d7d6dcd92a6643324d8dbc5cf4c94981dc44d4a1a9735cc9ea3fc3af7527ef3
4
- data.tar.gz: 85a3f731c82146be98b237e3bfe79c8ee412629cf4115928b04ed8e701306be5
3
+ metadata.gz: f5923255962191599c9ddf2eef5a5fa1dee03b8b1513336d0f2e849ebe5748ea
4
+ data.tar.gz: d169ccb2f68d196c1c0472070e6ef4f57aabb1d93189acadbc253ec860d05b76
5
5
  SHA512:
6
- metadata.gz: 70b38e57a58ffb9d4bc1ba4d5044edd9624e90261e576b95a854aba4622fb9ed2e1b1b184643d244cfdb6647bb9509aa7e6402dd06a149bc32b6ad6ddc623883
7
- data.tar.gz: c6d0d0810eaaf9b742789008375979f793953c979f0a8a1fbb436b84d06958cb0b24b9aecccb0070b5158f899858de252c63f5aeb72b97ffe038387cf04146af
6
+ metadata.gz: 3437fad480724057194bb31a26508d19e139ee1b118a3896c4abc2181da3a2d6d45e62d492c84304b30abb2c4917a0e805c4be04d1a0feac753546733962d84c
7
+ data.tar.gz: 542774d195ca0b1a1818b96c162f57dda6a0ac6feb3b8620ee76cfa9aee3f018c20b1120ce4e671c1069783ff5a72827b119a53af0feda6d361ba2fd787f072d
@@ -475,7 +475,23 @@ class SelectionFormController extends Controller {
475
475
  );
476
476
  }
477
477
 
478
- this.countValue = this.inputs.length;
478
+ this.countValue = this.visibleInputs.length;
479
+
480
+ return !input;
481
+ }
482
+
483
+ /**
484
+ * @param id to toggle visibility
485
+ * @return {boolean} true if visible, false if not visible
486
+ */
487
+ visible(id, visible) {
488
+ const input = this.input(id);
489
+
490
+ if (input) {
491
+ input.disabled = !visible;
492
+ }
493
+
494
+ this.countValue = this.visibleInputs.length;
479
495
 
480
496
  return !input;
481
497
  }
@@ -493,6 +509,10 @@ class SelectionFormController extends Controller {
493
509
  );
494
510
  }
495
511
 
512
+ get visibleInputs() {
513
+ return Array.from(this.inputs).filter((i) => !i.disabled);
514
+ }
515
+
496
516
  input(id) {
497
517
  return this.element.querySelector(
498
518
  `input[name="${this.primaryKeyValue}[]"][value="${id}"]`,
@@ -507,6 +527,17 @@ class SelectionFormController extends Controller {
507
527
  }
508
528
  }
509
529
 
530
+ /**
531
+ * Couples an input element in a row to the selection form which is turbo-permanent and outside the table.
532
+ * When the input is toggled, the form will create/destroy hidden inputs. The checkbox inside this cell will follow
533
+ * the hidden inputs.
534
+ *
535
+ * Cell value may change when:
536
+ * * cell connects, e.g. when the user paginates
537
+ * * cell is re-used by turbo-morph, e.g. pagination
538
+ * * cell is toggled
539
+ * * select-all/de-select-all on table header
540
+ */
510
541
  class SelectionItemController extends Controller {
511
542
  static outlets = ["tables--selection--form"];
512
543
  static values = {
@@ -515,9 +546,18 @@ class SelectionItemController extends Controller {
515
546
  };
516
547
 
517
548
  tablesSelectionFormOutletConnected(form) {
549
+ form.visible(this.id, true);
518
550
  this.checkedValue = form.isSelected(this.id);
519
551
  }
520
552
 
553
+ disconnect() {
554
+ // Remove from form's list of visible selections.
555
+ // This should be an outlet disconnect, but those events are not reliable in turbo 8.0
556
+ if (this.hasTablesSelectionFormOutlet) {
557
+ this.tablesSelectionFormOutlet.visible(this.id, false);
558
+ }
559
+ }
560
+
521
561
  change(e) {
522
562
  e.preventDefault();
523
563
 
@@ -528,8 +568,122 @@ class SelectionItemController extends Controller {
528
568
  return this.paramsValue.id;
529
569
  }
530
570
 
531
- checkedValueChanged(checked) {
532
- this.element.querySelector("input").checked = checked;
571
+ /**
572
+ * Update checked to match match selection form. This occurs when the item is re-used by turbo-morph.
573
+ */
574
+ paramsValueChanged(params, previous) {
575
+ if (!this.hasTablesSelectionFormOutlet) return;
576
+
577
+ // if id is changing (e.g. morph) then let the form know that the previous id is now not visible
578
+ if (previous.id !== params.id) {
579
+ this.tablesSelectionFormOutlet.visible(previous.id, false);
580
+ }
581
+
582
+ // tell form that our id is now visible in the table
583
+ this.tablesSelectionFormOutlet.visible(params.id, true);
584
+
585
+ // id has changed, so update checked from form
586
+ this.checkedValue = this.tablesSelectionFormOutlet.isSelected(params.id);
587
+
588
+ // propagate changes
589
+ this.update();
590
+ }
591
+
592
+ /**
593
+ * Update input to match checked. This occurs when the item is toggled, connected, or morphed.
594
+ */
595
+ checkedValueChanged() {
596
+ if (!this.hasTablesSelectionFormOutlet) return;
597
+
598
+ // ensure that checked matches the form, i.e. if morphed
599
+ this.checkedValue = this.tablesSelectionFormOutlet.isSelected(this.id);
600
+
601
+ // propagate changes
602
+ this.update();
603
+ }
604
+
605
+ /**
606
+ * Notify table that id or value may have changed. Note that this may fire when nothing has changed.
607
+ *
608
+ * Debouncing to minimise dom updates.
609
+ */
610
+ async update() {
611
+ this.updating ||= Promise.resolve().then(() => {
612
+ this.#update();
613
+ delete this.updating;
614
+ });
615
+
616
+ return this.updating;
617
+ }
618
+
619
+ #update() {
620
+ this.element.querySelector("input").checked = this.checkedValue;
621
+ this.dispatch("select", {
622
+ detail: { id: this.id, selected: this.checkedValue },
623
+ });
624
+ }
625
+ }
626
+
627
+ class SelectionTableController extends Controller {
628
+ static targets = ["header", "item"];
629
+ static outlets = ["tables--selection--form"];
630
+
631
+ itemTargetConnected(item) {
632
+ this.update();
633
+ }
634
+
635
+ itemTargetDisconnected(item) {
636
+ this.update();
637
+ }
638
+
639
+ toggleHeader(e) {
640
+ this.items.forEach((item) => {
641
+ if (item.checkedValue === e.target.checked) return;
642
+
643
+ item.checkedValue = this.tablesSelectionFormOutlet.toggle(item.id);
644
+ });
645
+ }
646
+
647
+ async update() {
648
+ this.updating ||= Promise.resolve().then(() => {
649
+ this.#update();
650
+ delete this.updating;
651
+ });
652
+
653
+ return this.updating;
654
+ }
655
+
656
+ #update() {
657
+ let present = 0;
658
+ let checked = 0;
659
+
660
+ this.items.forEach((item) => {
661
+ present++;
662
+ if (item.checkedValue) checked++;
663
+ });
664
+
665
+ this.headerInput.checked = present > 0 && checked === present;
666
+ this.headerInput.indeterminate = checked > 0 && checked !== present;
667
+ }
668
+
669
+ get headerInput() {
670
+ return this.headerTarget.querySelector("input");
671
+ }
672
+
673
+ get items() {
674
+ return this.itemTargets.map((el) => this.#itemOutlet(el)).filter((c) => c);
675
+ }
676
+
677
+ /**
678
+ * Ideally we would be using outlets, but as of turbo 8.0.4 outlets do not fire disconnect events when morphing.
679
+ *
680
+ * Instead, we're using the targets to finds the controller.
681
+ */
682
+ #itemOutlet(el) {
683
+ return this.application.getControllerForElementAndIdentifier(
684
+ el,
685
+ "tables--selection--item",
686
+ );
533
687
  }
534
688
  }
535
689
 
@@ -888,6 +1042,10 @@ const Definitions = [
888
1042
  identifier: "tables--selection--item",
889
1043
  controllerConstructor: SelectionItemController,
890
1044
  },
1045
+ {
1046
+ identifier: "tables--selection--table",
1047
+ controllerConstructor: SelectionTableController,
1048
+ },
891
1049
  {
892
1050
  identifier: "tables--query",
893
1051
  controllerConstructor: QueryController,
@@ -475,7 +475,23 @@ class SelectionFormController extends Controller {
475
475
  );
476
476
  }
477
477
 
478
- this.countValue = this.inputs.length;
478
+ this.countValue = this.visibleInputs.length;
479
+
480
+ return !input;
481
+ }
482
+
483
+ /**
484
+ * @param id to toggle visibility
485
+ * @return {boolean} true if visible, false if not visible
486
+ */
487
+ visible(id, visible) {
488
+ const input = this.input(id);
489
+
490
+ if (input) {
491
+ input.disabled = !visible;
492
+ }
493
+
494
+ this.countValue = this.visibleInputs.length;
479
495
 
480
496
  return !input;
481
497
  }
@@ -493,6 +509,10 @@ class SelectionFormController extends Controller {
493
509
  );
494
510
  }
495
511
 
512
+ get visibleInputs() {
513
+ return Array.from(this.inputs).filter((i) => !i.disabled);
514
+ }
515
+
496
516
  input(id) {
497
517
  return this.element.querySelector(
498
518
  `input[name="${this.primaryKeyValue}[]"][value="${id}"]`,
@@ -507,6 +527,17 @@ class SelectionFormController extends Controller {
507
527
  }
508
528
  }
509
529
 
530
+ /**
531
+ * Couples an input element in a row to the selection form which is turbo-permanent and outside the table.
532
+ * When the input is toggled, the form will create/destroy hidden inputs. The checkbox inside this cell will follow
533
+ * the hidden inputs.
534
+ *
535
+ * Cell value may change when:
536
+ * * cell connects, e.g. when the user paginates
537
+ * * cell is re-used by turbo-morph, e.g. pagination
538
+ * * cell is toggled
539
+ * * select-all/de-select-all on table header
540
+ */
510
541
  class SelectionItemController extends Controller {
511
542
  static outlets = ["tables--selection--form"];
512
543
  static values = {
@@ -515,9 +546,18 @@ class SelectionItemController extends Controller {
515
546
  };
516
547
 
517
548
  tablesSelectionFormOutletConnected(form) {
549
+ form.visible(this.id, true);
518
550
  this.checkedValue = form.isSelected(this.id);
519
551
  }
520
552
 
553
+ disconnect() {
554
+ // Remove from form's list of visible selections.
555
+ // This should be an outlet disconnect, but those events are not reliable in turbo 8.0
556
+ if (this.hasTablesSelectionFormOutlet) {
557
+ this.tablesSelectionFormOutlet.visible(this.id, false);
558
+ }
559
+ }
560
+
521
561
  change(e) {
522
562
  e.preventDefault();
523
563
 
@@ -528,8 +568,122 @@ class SelectionItemController extends Controller {
528
568
  return this.paramsValue.id;
529
569
  }
530
570
 
531
- checkedValueChanged(checked) {
532
- this.element.querySelector("input").checked = checked;
571
+ /**
572
+ * Update checked to match match selection form. This occurs when the item is re-used by turbo-morph.
573
+ */
574
+ paramsValueChanged(params, previous) {
575
+ if (!this.hasTablesSelectionFormOutlet) return;
576
+
577
+ // if id is changing (e.g. morph) then let the form know that the previous id is now not visible
578
+ if (previous.id !== params.id) {
579
+ this.tablesSelectionFormOutlet.visible(previous.id, false);
580
+ }
581
+
582
+ // tell form that our id is now visible in the table
583
+ this.tablesSelectionFormOutlet.visible(params.id, true);
584
+
585
+ // id has changed, so update checked from form
586
+ this.checkedValue = this.tablesSelectionFormOutlet.isSelected(params.id);
587
+
588
+ // propagate changes
589
+ this.update();
590
+ }
591
+
592
+ /**
593
+ * Update input to match checked. This occurs when the item is toggled, connected, or morphed.
594
+ */
595
+ checkedValueChanged() {
596
+ if (!this.hasTablesSelectionFormOutlet) return;
597
+
598
+ // ensure that checked matches the form, i.e. if morphed
599
+ this.checkedValue = this.tablesSelectionFormOutlet.isSelected(this.id);
600
+
601
+ // propagate changes
602
+ this.update();
603
+ }
604
+
605
+ /**
606
+ * Notify table that id or value may have changed. Note that this may fire when nothing has changed.
607
+ *
608
+ * Debouncing to minimise dom updates.
609
+ */
610
+ async update() {
611
+ this.updating ||= Promise.resolve().then(() => {
612
+ this.#update();
613
+ delete this.updating;
614
+ });
615
+
616
+ return this.updating;
617
+ }
618
+
619
+ #update() {
620
+ this.element.querySelector("input").checked = this.checkedValue;
621
+ this.dispatch("select", {
622
+ detail: { id: this.id, selected: this.checkedValue },
623
+ });
624
+ }
625
+ }
626
+
627
+ class SelectionTableController extends Controller {
628
+ static targets = ["header", "item"];
629
+ static outlets = ["tables--selection--form"];
630
+
631
+ itemTargetConnected(item) {
632
+ this.update();
633
+ }
634
+
635
+ itemTargetDisconnected(item) {
636
+ this.update();
637
+ }
638
+
639
+ toggleHeader(e) {
640
+ this.items.forEach((item) => {
641
+ if (item.checkedValue === e.target.checked) return;
642
+
643
+ item.checkedValue = this.tablesSelectionFormOutlet.toggle(item.id);
644
+ });
645
+ }
646
+
647
+ async update() {
648
+ this.updating ||= Promise.resolve().then(() => {
649
+ this.#update();
650
+ delete this.updating;
651
+ });
652
+
653
+ return this.updating;
654
+ }
655
+
656
+ #update() {
657
+ let present = 0;
658
+ let checked = 0;
659
+
660
+ this.items.forEach((item) => {
661
+ present++;
662
+ if (item.checkedValue) checked++;
663
+ });
664
+
665
+ this.headerInput.checked = present > 0 && checked === present;
666
+ this.headerInput.indeterminate = checked > 0 && checked !== present;
667
+ }
668
+
669
+ get headerInput() {
670
+ return this.headerTarget.querySelector("input");
671
+ }
672
+
673
+ get items() {
674
+ return this.itemTargets.map((el) => this.#itemOutlet(el)).filter((c) => c);
675
+ }
676
+
677
+ /**
678
+ * Ideally we would be using outlets, but as of turbo 8.0.4 outlets do not fire disconnect events when morphing.
679
+ *
680
+ * Instead, we're using the targets to finds the controller.
681
+ */
682
+ #itemOutlet(el) {
683
+ return this.application.getControllerForElementAndIdentifier(
684
+ el,
685
+ "tables--selection--item",
686
+ );
533
687
  }
534
688
  }
535
689
 
@@ -888,6 +1042,10 @@ const Definitions = [
888
1042
  identifier: "tables--selection--item",
889
1043
  controllerConstructor: SelectionItemController,
890
1044
  },
1045
+ {
1046
+ identifier: "tables--selection--table",
1047
+ controllerConstructor: SelectionTableController,
1048
+ },
891
1049
  {
892
1050
  identifier: "tables--query",
893
1051
  controllerConstructor: QueryController,
@@ -1,2 +1,2 @@
1
- import{Controller as e}from"@hotwired/stimulus";class t{constructor(e,t,s){this.cursorOffset=t.offsetY,this.initialPosition=t.target.offsetTop-e.offsetTop,this.targetId=s}updateCursor(e,t,s,i){this.listOffset=e.getBoundingClientRect().top;let r=s.clientY-this.listOffset-this.cursorOffset;this.#e(e,t,r,i)}updateScroll(e,t,s){const i=this.listOffset;this.listOffset=e.getBoundingClientRect().top;const r=i-this.listOffset,n=this.position+r;this.#e(e,t,n,s)}#e(e,t,s,i){s=Math.max(s,0),s=Math.min(s,e.offsetHeight-t.offsetHeight),this.position=s;i(s-this.initialPosition)}}class s extends e{static outlets=["tables--selection--form"];static values={params:Object,checked:Boolean};tablesSelectionFormOutletConnected(e){this.checkedValue=e.isSelected(this.id)}change(e){e.preventDefault(),this.checkedValue=this.tablesSelectionFormOutlet.toggle(this.id)}get id(){return this.paramsValue.id}checkedValueChanged(e){this.element.querySelector("input").checked=e}}class i{constructor(){this.tokens=[],this.values=null}parse(e){const t=new u(e);for(;!t.isEos();){this.push(this.skipWhitespace(t));const e=this.takeTagged(t)||this.takeUntagged(t);if(!this.push(e))break}return this}push(e){return e&&(this.values?this.values.push(e):this.tokens.push(e)),!!e}skipWhitespace(e){if(e.scan(/\s+/))return new r(e.matched())}takeUntagged(e){if(e.scan(/\S+/))return new l(e.matched())}takeTagged(e){if(!e.scan(/(\w+(?:\.\w+)?)(:\s*)/))return;const t=e.valueAt(1),s=e.valueAt(2),i=this.takeArrayValue(e)||this.takeSingleValue(e)||new r;return new a(t,s,i)}takeArrayValue(e){if(!e.scan(/\[\s*/))return;const t=new r(e.matched()),s=this.values=[];for(;!e.isEos()&&this.push(this.takeSingleValue(e))&&this.push(this.takeDelimiter(e)););e.scan(/\s*]/);const i=new r(e.matched());return this.values=null,new o(t,s,i)}takeDelimiter(e){if(e.scan(/\s*,\s*/))return new r(e.matched())}takeSingleValue(e){return this.takeQuotedValue(e)||this.takeUnquotedValue(e)}takeQuotedValue(e){if(e.scan(/"([^"]*)"/))return new n(e.matched())}takeUnquotedValue(e){if(e.scan(/[^ \],]*/))return new n(e.matched())}}class r{constructor(e=""){this.value=e}render(){return document.createTextNode(this.value)}}class n extends r{render(){const e=document.createElement("span");return e.className="value",e.innerText=this.value,e}}class a extends r{constructor(e,t,s){super(),this.key=e,this.separator=t,this.value=s}render(){const e=document.createElement("span");e.className="tag";const t=document.createElement("span");return t.className="key",t.innerText=this.key,e.appendChild(t),e.appendChild(document.createTextNode(this.separator)),e.appendChild(this.value.render()),e}}class l extends r{render(){const e=document.createElement("span");return e.className="untagged",e.innerText=this.value,e}}let o=class extends r{constructor(e,t,s){super(),this.start=e,this.values=t,this.end=s}render(){const e=document.createElement("span");return e.className="array-values",e.appendChild(this.start.render()),this.values.forEach((t=>{const s=document.createElement("span");s.appendChild(t.render()),e.appendChild(s)})),e.appendChild(this.end.render()),e}};class u{constructor(e){this.input=e,this.position=0,this.last=null}isEos(){return this.position>=this.input.length}scan(e){const t=e.exec(this.input.substring(this.position));return 0===t?.index?(this.last=t,this.position+=t[0].length,!0):(this.last={},!1)}matched(){return this.last&&this.last[0]}valueAt(e){return this.last&&this.last[e]}}const h=[{identifier:"tables--orderable--item",controllerConstructor:class extends e{static values={params:Object};connect(){var e;this.index=(e=this.row,Array.from(e.parentElement.children).indexOf(e))}paramsValueChanged(e){this.id=e.id_value}dragUpdate(e){this.dragOffset=e,this.row.style.position="relative",this.row.style.top=e+"px",this.row.style.zIndex="1",this.row.toggleAttribute("dragging",!0)}updateVisually(e){this.row.style.position="relative",this.row.style.top=this.row.offsetHeight*(e-this.dragIndex)+"px"}updateIndex(e){this.index=e}params(e){const{id_name:t,id_value:s,index_name:i}=this.paramsValue;return[{name:`${e}[${s}][${t}]`,value:this.id},{name:`${e}[${s}][${i}]`,value:this.index}]}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 e{static outlets=["tables--orderable--item","tables--orderable--form"];startDragging(e){this.dragState=e,document.addEventListener("mousemove",this.mousemove),document.addEventListener("mouseup",this.mouseup),window.addEventListener("scroll",this.scroll,!0),this.element.style.position="relative"}stopDragging(){const e=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((e=>e.reset())),e}drop(){const e=this.dragItem;if(!e)return;const t=e.dragIndex,s=this.tablesOrderableItemOutlets[t];s&&(t<e.index?s.row.insertAdjacentElement("beforebegin",e.row):t>e.index&&s.row.insertAdjacentElement("afterend",e.row),this.tablesOrderableItemOutlets.forEach(((e,t)=>e.updateIndex(t))),this.commitChanges())}commitChanges(){this.tablesOrderableFormOutlet.clear(),this.tablesOrderableItemOutlets.forEach((e=>{e.hasChanges&&this.tablesOrderableFormOutlet.add(e)})),this.tablesOrderableFormOutlet.submit()}mousedown(e){if(this.isDragging)return;const s=this.#t(e.target);s&&(e.preventDefault(),this.startDragging(new t(this.element,e,s.id)),this.dragState.updateCursor(this.element,s.row,e,this.animate))}mousemove=e=>{this.isDragging&&(e.preventDefault(),this.ticking||(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateCursor(this.element,this.dragItem.row,e,this.animate)}))))};scroll=e=>{this.isDragging&&!this.ticking&&(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateScroll(this.element,this.dragItem.row,this.animate)})))};mouseup=e=>{this.isDragging&&(this.drop(),this.stopDragging(),this.tablesOrderableFormOutlets.forEach((e=>delete e.dragState)))};tablesOrderableFormOutletConnected(e,t){e.dragState&&this.startDragging(e.dragState)}tablesOrderableFormOutletDisconnected(e,t){this.isDragging&&(e.dragState=this.stopDragging())}animate=e=>{const t=this.dragItem;t.dragUpdate(e),this.#s.forEach(((e,s)=>{e!==t&&e.updateVisually(s)}))};get isDragging(){return!!this.dragState}get dragItem(){return this.isDragging?this.tablesOrderableItemOutlets.find((e=>e.id===this.dragState.targetId)):null}get#s(){return this.tablesOrderableItemOutlets.toSorted(((e,t)=>e.comparisonIndex-t.comparisonIndex))}#t(e){return this.tablesOrderableItemOutlets.find((t=>t.element===e))}}},{identifier:"tables--orderable--form",controllerConstructor:class extends e{static values={scope:String};add(e){e.params(this.scopeValue).forEach((({name:e,value:t})=>{this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${e}" value="${t}" data-generated>`)}))}submit(){0!==this.inputs.length&&this.element.requestSubmit()}clear(){this.inputs.forEach((e=>e.remove()))}get inputs(){return this.element.querySelectorAll("input[data-generated]")}}},{identifier:"tables--selection--form",controllerConstructor:class extends e{static values={count:Number,primaryKey:{type:String,default:"id"}};static targets=["count","singular","plural"];connect(){this.countValue=this.inputs.length}toggle(e){const t=this.input(e);return t?t.remove():this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${this.primaryKeyValue}[]" value="${e}">`),this.countValue=this.inputs.length,!t}isSelected(e){return!!this.input(e)}get inputs(){return this.element.querySelectorAll(`input[name="${this.primaryKeyValue}[]"]`)}input(e){return this.element.querySelector(`input[name="${this.primaryKeyValue}[]"][value="${e}"]`)}countValueChanged(e){this.element.toggleAttribute("hidden",0===e),this.countTarget.textContent=e,this.singularTarget.toggleAttribute("hidden",1!==e),this.pluralTarget.toggleAttribute("hidden",1===e)}}},{identifier:"tables--selection--item",controllerConstructor:s},{identifier:"tables--query",controllerConstructor:class extends e{static targets=["modal"];disconnect(){delete this.pending,document.removeEventListener("selectionchange",this.selection)}focus(){document.activeElement!==this.query&&(this.query.addEventListener("focusin",(e=>{e.target.setSelectionRange(-1,-1)}),{once:!0}),this.query.focus())}closeModal(){delete this.modalTarget.dataset.open,document.activeElement===this.query&&document.activeElement.blur(),document.removeEventListener("selectionchange",this.selection)}openModal(){this.modalTarget.dataset.open=!0,document.addEventListener("selectionchange",this.selection)}clear(){""===this.query.value&&this.closeModal()}submit(){const e=this.isFocused,t=e&&this.query.selectionStart;this.pending&&(clearTimeout(this.pending),delete this.pending),""===this.query.value&&(this.query.disabled=!0,setTimeout((()=>{this.query.disabled=!1,e&&this.query.focus()}),0)),e&&t?(this.position.value=t,this.position.disabled=!1):(this.position.value="",this.position.disabled=!0)}update=()=>{this.pending&&clearTimeout(this.pending),this.pending=setTimeout((()=>{this.element.requestSubmit()}),300)};selection=()=>{this.isFocused&&this.update()};beforeMorphAttribute(e){if("data-open"===e.detail.attributeName)e.preventDefault()}get query(){return this.element.querySelector("input[type=search]")}get position(){return this.element.querySelector("input[name=p]")}get isFocused(){return this.query===document.activeElement}}},{identifier:"tables--query-input",controllerConstructor:class extends e{static targets=["input","highlight"];static values={query:String};connect(){this.queryValue=this.inputTarget.value}update(){this.queryValue=this.inputTarget.value}queryValueChanged(e){this.highlightTarget.innerHTML="",(new i).parse(e).tokens.forEach((e=>{this.highlightTarget.appendChild(e.render())}))}}}];export{h as default};
1
+ import{Controller as e}from"@hotwired/stimulus";class t{constructor(e,t,s){this.cursorOffset=t.offsetY,this.initialPosition=t.target.offsetTop-e.offsetTop,this.targetId=s}updateCursor(e,t,s,i){this.listOffset=e.getBoundingClientRect().top;let r=s.clientY-this.listOffset-this.cursorOffset;this.#e(e,t,r,i)}updateScroll(e,t,s){const i=this.listOffset;this.listOffset=e.getBoundingClientRect().top;const r=i-this.listOffset,n=this.position+r;this.#e(e,t,n,s)}#e(e,t,s,i){s=Math.max(s,0),s=Math.min(s,e.offsetHeight-t.offsetHeight),this.position=s;i(s-this.initialPosition)}}class s extends e{static outlets=["tables--selection--form"];static values={params:Object,checked:Boolean};tablesSelectionFormOutletConnected(e){e.visible(this.id,!0),this.checkedValue=e.isSelected(this.id)}disconnect(){this.hasTablesSelectionFormOutlet&&this.tablesSelectionFormOutlet.visible(this.id,!1)}change(e){e.preventDefault(),this.checkedValue=this.tablesSelectionFormOutlet.toggle(this.id)}get id(){return this.paramsValue.id}paramsValueChanged(e,t){this.hasTablesSelectionFormOutlet&&(t.id!==e.id&&this.tablesSelectionFormOutlet.visible(t.id,!1),this.tablesSelectionFormOutlet.visible(e.id,!0),this.checkedValue=this.tablesSelectionFormOutlet.isSelected(e.id),this.update())}checkedValueChanged(){this.hasTablesSelectionFormOutlet&&(this.checkedValue=this.tablesSelectionFormOutlet.isSelected(this.id),this.update())}async update(){return this.updating||=Promise.resolve().then((()=>{this.#t(),delete this.updating})),this.updating}#t(){this.element.querySelector("input").checked=this.checkedValue,this.dispatch("select",{detail:{id:this.id,selected:this.checkedValue}})}}class i{constructor(){this.tokens=[],this.values=null}parse(e){const t=new h(e);for(;!t.isEos();){this.push(this.skipWhitespace(t));const e=this.takeTagged(t)||this.takeUntagged(t);if(!this.push(e))break}return this}push(e){return e&&(this.values?this.values.push(e):this.tokens.push(e)),!!e}skipWhitespace(e){if(e.scan(/\s+/))return new r(e.matched())}takeUntagged(e){if(e.scan(/\S+/))return new l(e.matched())}takeTagged(e){if(!e.scan(/(\w+(?:\.\w+)?)(:\s*)/))return;const t=e.valueAt(1),s=e.valueAt(2),i=this.takeArrayValue(e)||this.takeSingleValue(e)||new r;return new a(t,s,i)}takeArrayValue(e){if(!e.scan(/\[\s*/))return;const t=new r(e.matched()),s=this.values=[];for(;!e.isEos()&&this.push(this.takeSingleValue(e))&&this.push(this.takeDelimiter(e)););e.scan(/\s*]/);const i=new r(e.matched());return this.values=null,new o(t,s,i)}takeDelimiter(e){if(e.scan(/\s*,\s*/))return new r(e.matched())}takeSingleValue(e){return this.takeQuotedValue(e)||this.takeUnquotedValue(e)}takeQuotedValue(e){if(e.scan(/"([^"]*)"/))return new n(e.matched())}takeUnquotedValue(e){if(e.scan(/[^ \],]*/))return new n(e.matched())}}class r{constructor(e=""){this.value=e}render(){return document.createTextNode(this.value)}}class n extends r{render(){const e=document.createElement("span");return e.className="value",e.innerText=this.value,e}}class a extends r{constructor(e,t,s){super(),this.key=e,this.separator=t,this.value=s}render(){const e=document.createElement("span");e.className="tag";const t=document.createElement("span");return t.className="key",t.innerText=this.key,e.appendChild(t),e.appendChild(document.createTextNode(this.separator)),e.appendChild(this.value.render()),e}}class l extends r{render(){const e=document.createElement("span");return e.className="untagged",e.innerText=this.value,e}}let o=class extends r{constructor(e,t,s){super(),this.start=e,this.values=t,this.end=s}render(){const e=document.createElement("span");return e.className="array-values",e.appendChild(this.start.render()),this.values.forEach((t=>{const s=document.createElement("span");s.appendChild(t.render()),e.appendChild(s)})),e.appendChild(this.end.render()),e}};class h{constructor(e){this.input=e,this.position=0,this.last=null}isEos(){return this.position>=this.input.length}scan(e){const t=e.exec(this.input.substring(this.position));return 0===t?.index?(this.last=t,this.position+=t[0].length,!0):(this.last={},!1)}matched(){return this.last&&this.last[0]}valueAt(e){return this.last&&this.last[e]}}const u=[{identifier:"tables--orderable--item",controllerConstructor:class extends e{static values={params:Object};connect(){var e;this.index=(e=this.row,Array.from(e.parentElement.children).indexOf(e))}paramsValueChanged(e){this.id=e.id_value}dragUpdate(e){this.dragOffset=e,this.row.style.position="relative",this.row.style.top=e+"px",this.row.style.zIndex="1",this.row.toggleAttribute("dragging",!0)}updateVisually(e){this.row.style.position="relative",this.row.style.top=this.row.offsetHeight*(e-this.dragIndex)+"px"}updateIndex(e){this.index=e}params(e){const{id_name:t,id_value:s,index_name:i}=this.paramsValue;return[{name:`${e}[${s}][${t}]`,value:this.id},{name:`${e}[${s}][${i}]`,value:this.index}]}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 e{static outlets=["tables--orderable--item","tables--orderable--form"];startDragging(e){this.dragState=e,document.addEventListener("mousemove",this.mousemove),document.addEventListener("mouseup",this.mouseup),window.addEventListener("scroll",this.scroll,!0),this.element.style.position="relative"}stopDragging(){const e=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((e=>e.reset())),e}drop(){const e=this.dragItem;if(!e)return;const t=e.dragIndex,s=this.tablesOrderableItemOutlets[t];s&&(t<e.index?s.row.insertAdjacentElement("beforebegin",e.row):t>e.index&&s.row.insertAdjacentElement("afterend",e.row),this.tablesOrderableItemOutlets.forEach(((e,t)=>e.updateIndex(t))),this.commitChanges())}commitChanges(){this.tablesOrderableFormOutlet.clear(),this.tablesOrderableItemOutlets.forEach((e=>{e.hasChanges&&this.tablesOrderableFormOutlet.add(e)})),this.tablesOrderableFormOutlet.submit()}mousedown(e){if(this.isDragging)return;const s=this.#s(e.target);s&&(e.preventDefault(),this.startDragging(new t(this.element,e,s.id)),this.dragState.updateCursor(this.element,s.row,e,this.animate))}mousemove=e=>{this.isDragging&&(e.preventDefault(),this.ticking||(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateCursor(this.element,this.dragItem.row,e,this.animate)}))))};scroll=e=>{this.isDragging&&!this.ticking&&(this.ticking=!0,window.requestAnimationFrame((()=>{this.ticking=!1,this.dragState.updateScroll(this.element,this.dragItem.row,this.animate)})))};mouseup=e=>{this.isDragging&&(this.drop(),this.stopDragging(),this.tablesOrderableFormOutlets.forEach((e=>delete e.dragState)))};tablesOrderableFormOutletConnected(e,t){e.dragState&&this.startDragging(e.dragState)}tablesOrderableFormOutletDisconnected(e,t){this.isDragging&&(e.dragState=this.stopDragging())}animate=e=>{const t=this.dragItem;t.dragUpdate(e),this.#i.forEach(((e,s)=>{e!==t&&e.updateVisually(s)}))};get isDragging(){return!!this.dragState}get dragItem(){return this.isDragging?this.tablesOrderableItemOutlets.find((e=>e.id===this.dragState.targetId)):null}get#i(){return this.tablesOrderableItemOutlets.toSorted(((e,t)=>e.comparisonIndex-t.comparisonIndex))}#s(e){return this.tablesOrderableItemOutlets.find((t=>t.element===e))}}},{identifier:"tables--orderable--form",controllerConstructor:class extends e{static values={scope:String};add(e){e.params(this.scopeValue).forEach((({name:e,value:t})=>{this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${e}" value="${t}" data-generated>`)}))}submit(){0!==this.inputs.length&&this.element.requestSubmit()}clear(){this.inputs.forEach((e=>e.remove()))}get inputs(){return this.element.querySelectorAll("input[data-generated]")}}},{identifier:"tables--selection--form",controllerConstructor:class extends e{static values={count:Number,primaryKey:{type:String,default:"id"}};static targets=["count","singular","plural"];connect(){this.countValue=this.inputs.length}toggle(e){const t=this.input(e);return t?t.remove():this.element.insertAdjacentHTML("beforeend",`<input type="hidden" name="${this.primaryKeyValue}[]" value="${e}">`),this.countValue=this.visibleInputs.length,!t}visible(e,t){const s=this.input(e);return s&&(s.disabled=!t),this.countValue=this.visibleInputs.length,!s}isSelected(e){return!!this.input(e)}get inputs(){return this.element.querySelectorAll(`input[name="${this.primaryKeyValue}[]"]`)}get visibleInputs(){return Array.from(this.inputs).filter((e=>!e.disabled))}input(e){return this.element.querySelector(`input[name="${this.primaryKeyValue}[]"][value="${e}"]`)}countValueChanged(e){this.element.toggleAttribute("hidden",0===e),this.countTarget.textContent=e,this.singularTarget.toggleAttribute("hidden",1!==e),this.pluralTarget.toggleAttribute("hidden",1===e)}}},{identifier:"tables--selection--item",controllerConstructor:s},{identifier:"tables--selection--table",controllerConstructor:class extends e{static targets=["header","item"];static outlets=["tables--selection--form"];itemTargetConnected(e){this.update()}itemTargetDisconnected(e){this.update()}toggleHeader(e){this.items.forEach((t=>{t.checkedValue!==e.target.checked&&(t.checkedValue=this.tablesSelectionFormOutlet.toggle(t.id))}))}async update(){return this.updating||=Promise.resolve().then((()=>{this.#t(),delete this.updating})),this.updating}#t(){let e=0,t=0;this.items.forEach((s=>{e++,s.checkedValue&&t++})),this.headerInput.checked=e>0&&t===e,this.headerInput.indeterminate=t>0&&t!==e}get headerInput(){return this.headerTarget.querySelector("input")}get items(){return this.itemTargets.map((e=>this.#r(e))).filter((e=>e))}#r(e){return this.application.getControllerForElementAndIdentifier(e,"tables--selection--item")}}},{identifier:"tables--query",controllerConstructor:class extends e{static targets=["modal"];disconnect(){delete this.pending,document.removeEventListener("selectionchange",this.selection)}focus(){document.activeElement!==this.query&&(this.query.addEventListener("focusin",(e=>{e.target.setSelectionRange(-1,-1)}),{once:!0}),this.query.focus())}closeModal(){delete this.modalTarget.dataset.open,document.activeElement===this.query&&document.activeElement.blur(),document.removeEventListener("selectionchange",this.selection)}openModal(){this.modalTarget.dataset.open=!0,document.addEventListener("selectionchange",this.selection)}clear(){""===this.query.value&&this.closeModal()}submit(){const e=this.isFocused,t=e&&this.query.selectionStart;this.pending&&(clearTimeout(this.pending),delete this.pending),""===this.query.value&&(this.query.disabled=!0,setTimeout((()=>{this.query.disabled=!1,e&&this.query.focus()}),0)),e&&t?(this.position.value=t,this.position.disabled=!1):(this.position.value="",this.position.disabled=!0)}update=()=>{this.pending&&clearTimeout(this.pending),this.pending=setTimeout((()=>{this.element.requestSubmit()}),300)};selection=()=>{this.isFocused&&this.update()};beforeMorphAttribute(e){if("data-open"===e.detail.attributeName)e.preventDefault()}get query(){return this.element.querySelector("input[type=search]")}get position(){return this.element.querySelector("input[name=p]")}get isFocused(){return this.query===document.activeElement}}},{identifier:"tables--query-input",controllerConstructor:class extends e{static targets=["input","highlight"];static values={query:String};connect(){this.queryValue=this.inputTarget.value}update(){this.queryValue=this.inputTarget.value}queryValueChanged(e){this.highlightTarget.innerHTML="",(new i).parse(e).tokens.forEach((e=>{this.highlightTarget.appendChild(e.render())}))}}}];export{u 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/selection/item_controller.js","../../../javascript/tables/query_input_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/orderable/item_controller.js","../../../javascript/tables/orderable/form_controller.js","../../../javascript/tables/selection/form_controller.js","../../../javascript/tables/query_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 { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionItemController extends Controller {\n static outlets = [\"tables--selection--form\"];\n static values = {\n params: Object,\n checked: Boolean,\n };\n\n tablesSelectionFormOutletConnected(form) {\n this.checkedValue = form.isSelected(this.id);\n }\n\n change(e) {\n e.preventDefault();\n\n this.checkedValue = this.tablesSelectionFormOutlet.toggle(this.id);\n }\n\n get id() {\n return this.paramsValue.id;\n }\n\n checkedValueChanged(checked) {\n this.element.querySelector(\"input\").checked = checked;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class QueryInputController extends Controller {\n static targets = [\"input\", \"highlight\"];\n static values = { query: String };\n\n connect() {\n this.queryValue = this.inputTarget.value;\n }\n\n update() {\n this.queryValue = this.inputTarget.value;\n }\n\n queryValueChanged(query) {\n this.highlightTarget.innerHTML = \"\";\n\n new Parser().parse(query).tokens.forEach((token) => {\n this.highlightTarget.appendChild(token.render());\n });\n }\n}\n\nclass Parser {\n constructor() {\n this.tokens = [];\n this.values = null;\n }\n\n parse(input) {\n const query = new StringScanner(input);\n\n while (!query.isEos()) {\n this.push(this.skipWhitespace(query));\n\n const value = this.takeTagged(query) || this.takeUntagged(query);\n\n if (!this.push(value)) break;\n }\n\n return this;\n }\n\n push(token) {\n if (token) {\n this.values ? this.values.push(token) : this.tokens.push(token);\n }\n\n return !!token;\n }\n\n skipWhitespace(query) {\n if (!query.scan(/\\s+/)) return;\n\n return new Token(query.matched());\n }\n\n takeUntagged(query) {\n if (!query.scan(/\\S+/)) return;\n\n return new Untagged(query.matched());\n }\n\n takeTagged(query) {\n if (!query.scan(/(\\w+(?:\\.\\w+)?)(:\\s*)/)) return;\n\n const key = query.valueAt(1);\n const separator = query.valueAt(2);\n\n const value =\n this.takeArrayValue(query) || this.takeSingleValue(query) || new Token();\n\n return new Tagged(key, separator, value);\n }\n\n takeArrayValue(query) {\n if (!query.scan(/\\[\\s*/)) return;\n\n const start = new Token(query.matched());\n const values = (this.values = []);\n\n while (!query.isEos()) {\n if (!this.push(this.takeSingleValue(query))) break;\n if (!this.push(this.takeDelimiter(query))) break;\n }\n\n query.scan(/\\s*]/);\n const end = new Token(query.matched());\n\n this.values = null;\n\n return new Array(start, values, end);\n }\n\n takeDelimiter(query) {\n if (!query.scan(/\\s*,\\s*/)) return;\n\n return new Token(query.matched());\n }\n\n takeSingleValue(query) {\n return this.takeQuotedValue(query) || this.takeUnquotedValue(query);\n }\n\n takeQuotedValue(query) {\n if (!query.scan(/\"([^\"]*)\"/)) return;\n\n return new Value(query.matched());\n }\n\n takeUnquotedValue(query) {\n if (!query.scan(/[^ \\],]*/)) return;\n\n return new Value(query.matched());\n }\n}\n\nclass Token {\n constructor(value = \"\") {\n this.value = value;\n }\n\n render() {\n return document.createTextNode(this.value);\n }\n}\n\nclass Value extends Token {\n render() {\n const span = document.createElement(\"span\");\n span.className = \"value\";\n span.innerText = this.value;\n\n return span;\n }\n}\n\nclass Tagged extends Token {\n constructor(key, separator, value) {\n super();\n\n this.key = key;\n this.separator = separator;\n this.value = value;\n }\n\n render() {\n const span = document.createElement(\"span\");\n span.className = \"tag\";\n\n const key = document.createElement(\"span\");\n key.className = \"key\";\n key.innerText = this.key;\n\n span.appendChild(key);\n span.appendChild(document.createTextNode(this.separator));\n span.appendChild(this.value.render());\n\n return span;\n }\n}\n\nclass Untagged extends Token {\n render() {\n const span = document.createElement(\"span\");\n span.className = \"untagged\";\n span.innerText = this.value;\n return span;\n }\n}\n\nclass Array extends Token {\n constructor(start, values, end) {\n super();\n\n this.start = start;\n this.values = values;\n this.end = end;\n }\n\n render() {\n const array = document.createElement(\"span\");\n array.className = \"array-values\";\n array.appendChild(this.start.render());\n\n this.values.forEach((value) => {\n const span = document.createElement(\"span\");\n span.appendChild(value.render());\n array.appendChild(span);\n });\n\n array.appendChild(this.end.render());\n\n return array;\n }\n}\n\nclass StringScanner {\n constructor(input) {\n this.input = input;\n this.position = 0;\n this.last = null;\n }\n\n isEos() {\n return this.position >= this.input.length;\n }\n\n scan(regex) {\n const match = regex.exec(this.input.substring(this.position));\n if (match?.index === 0) {\n this.last = match;\n this.position += match[0].length;\n return true;\n } else {\n this.last = {};\n return false;\n }\n }\n\n matched() {\n return this.last && this.last[0];\n }\n\n valueAt(index) {\n return this.last && this.last[index];\n }\n}\n","import OrderableItemController from \"./orderable/item_controller\";\nimport OrderableListController from \"./orderable/list_controller\";\nimport OrderableFormController from \"./orderable/form_controller\";\nimport SelectionFormController from \"./selection/form_controller\";\nimport SelectionItemController from \"./selection/item_controller\";\nimport QueryController from \"./query_controller\";\nimport QueryInputController from \"./query_input_controller\";\n\nconst Definitions = [\n {\n identifier: \"tables--orderable--item\",\n controllerConstructor: OrderableItemController,\n },\n {\n identifier: \"tables--orderable--list\",\n controllerConstructor: OrderableListController,\n },\n {\n identifier: \"tables--orderable--form\",\n controllerConstructor: OrderableFormController,\n },\n {\n identifier: \"tables--selection--form\",\n controllerConstructor: SelectionFormController,\n },\n {\n identifier: \"tables--selection--item\",\n controllerConstructor: SelectionItemController,\n },\n {\n identifier: \"tables--query\",\n controllerConstructor: QueryController,\n },\n {\n identifier: \"tables--query-input\",\n controllerConstructor: QueryInputController,\n },\n];\n\nexport { Definitions as default };\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 /** Retrieve params for use in the form */\n params(scope) {\n const { id_name, id_value, index_name } = this.paramsValue;\n return [\n { name: `${scope}[${id_value}][${id_name}]`, value: this.id },\n { name: `${scope}[${id_value}][${index_name}]`, value: this.index },\n ];\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 static values = { scope: String };\n\n add(item) {\n item.params(this.scopeValue).forEach(({ name, value }) => {\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${name}\" value=\"${value}\" data-generated>`,\n );\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","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionFormController extends Controller {\n static values = {\n count: Number,\n primaryKey: { type: String, default: \"id\" },\n };\n static targets = [\"count\", \"singular\", \"plural\"];\n\n connect() {\n this.countValue = this.inputs.length;\n }\n\n /**\n * @param id to toggle\n * @return {boolean} true if selected, false if unselected\n */\n toggle(id) {\n const input = this.input(id);\n\n if (input) {\n input.remove();\n } else {\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${this.primaryKeyValue}[]\" value=\"${id}\">`,\n );\n }\n\n this.countValue = this.inputs.length;\n\n return !input;\n }\n\n /**\n * @returns {boolean} true if the given id is currently selected\n */\n isSelected(id) {\n return !!this.input(id);\n }\n\n get inputs() {\n return this.element.querySelectorAll(\n `input[name=\"${this.primaryKeyValue}[]\"]`,\n );\n }\n\n input(id) {\n return this.element.querySelector(\n `input[name=\"${this.primaryKeyValue}[]\"][value=\"${id}\"]`,\n );\n }\n\n countValueChanged(count) {\n this.element.toggleAttribute(\"hidden\", count === 0);\n this.countTarget.textContent = count;\n this.singularTarget.toggleAttribute(\"hidden\", count !== 1);\n this.pluralTarget.toggleAttribute(\"hidden\", count === 1);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class QueryController extends Controller {\n static targets = [\"modal\"];\n\n disconnect() {\n delete this.pending;\n\n document.removeEventListener(\"selectionchange\", this.selection);\n }\n\n focus() {\n if (document.activeElement === this.query) return;\n\n this.query.addEventListener(\n \"focusin\",\n (e) => {\n e.target.setSelectionRange(-1, -1);\n },\n { once: true },\n );\n\n this.query.focus();\n }\n\n closeModal() {\n delete this.modalTarget.dataset.open;\n\n if (document.activeElement === this.query) document.activeElement.blur();\n\n document.removeEventListener(\"selectionchange\", this.selection);\n }\n\n openModal() {\n this.modalTarget.dataset.open = true;\n\n document.addEventListener(\"selectionchange\", this.selection);\n }\n\n clear() {\n if (this.query.value === \"\") {\n // if the user presses escape once, browser clears the input\n // if the user presses escape again, get them out of here\n this.closeModal();\n }\n }\n\n submit() {\n const hasFocus = this.isFocused;\n const position = hasFocus && this.query.selectionStart;\n\n if (this.pending) {\n clearTimeout(this.pending);\n delete this.pending;\n }\n\n // prevent an unnecessary `?q=` parameter from appearing in the URL\n if (this.query.value === \"\") {\n this.query.disabled = true;\n\n // restore input and focus after form submission\n setTimeout(() => {\n this.query.disabled = false;\n if (hasFocus) this.query.focus();\n }, 0);\n }\n\n // add/remove current cursor position\n if (hasFocus && position) {\n this.position.value = position;\n this.position.disabled = false;\n } else {\n this.position.value = \"\";\n this.position.disabled = true;\n }\n }\n\n update = () => {\n if (this.pending) clearTimeout(this.pending);\n this.pending = setTimeout(() => {\n this.element.requestSubmit();\n }, 300);\n };\n\n selection = () => {\n if (this.isFocused) this.update();\n };\n\n beforeMorphAttribute(e) {\n switch (e.detail.attributeName) {\n case \"data-open\":\n e.preventDefault();\n break;\n }\n }\n\n get query() {\n return this.element.querySelector(\"input[type=search]\");\n }\n\n get position() {\n return this.element.querySelector(\"input[name=p]\");\n }\n\n get isFocused() {\n return this.query === document.activeElement;\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","SelectionItemController","Controller","static","params","Object","checked","Boolean","tablesSelectionFormOutletConnected","form","checkedValue","isSelected","change","e","preventDefault","tablesSelectionFormOutlet","toggle","paramsValue","checkedValueChanged","element","querySelector","Parser","tokens","values","parse","input","query","StringScanner","isEos","push","skipWhitespace","value","takeTagged","takeUntagged","token","scan","Token","matched","Untagged","key","valueAt","separator","takeArrayValue","takeSingleValue","Tagged","start","takeDelimiter","end","Array","takeQuotedValue","takeUnquotedValue","Value","render","document","createTextNode","span","createElement","className","innerText","super","appendChild","array","forEach","last","length","regex","match","exec","substring","index","Definitions","identifier","controllerConstructor","connect","from","parentElement","children","indexOf","paramsValueChanged","id_value","dragUpdate","offset","dragOffset","style","zIndex","toggleAttribute","updateVisually","dragIndex","updateIndex","scope","id_name","index_name","name","reset","removeAttribute","hasChanges","index_value","round","comparisonIndex","startDragging","dragState","addEventListener","mousemove","mouseup","window","scroll","stopDragging","removeEventListener","tablesOrderableItemOutlets","item","drop","dragItem","newIndex","targetItem","insertAdjacentElement","commitChanges","tablesOrderableFormOutlet","clear","add","submit","mousedown","isDragging","animate","ticking","requestAnimationFrame","tablesOrderableFormOutlets","tablesOrderableFormOutletConnected","tablesOrderableFormOutletDisconnected","currentItems","find","toSorted","a","b","String","scopeValue","insertAdjacentHTML","inputs","requestSubmit","remove","querySelectorAll","count","Number","primaryKey","type","default","countValue","primaryKeyValue","countValueChanged","countTarget","textContent","singularTarget","pluralTarget","disconnect","pending","selection","focus","activeElement","setSelectionRange","once","closeModal","modalTarget","dataset","open","blur","openModal","hasFocus","isFocused","selectionStart","clearTimeout","disabled","setTimeout","update","beforeMorphAttribute","detail","attributeName","queryValue","inputTarget","queryValueChanged","highlightTarget","innerHTML"],"mappings":"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"}
1
+ {"version":3,"file":"tables.min.js","sources":["../../../javascript/tables/orderable/list_controller.js","../../../javascript/tables/selection/item_controller.js","../../../javascript/tables/query_input_controller.js","../../../javascript/tables/application.js","../../../javascript/tables/orderable/item_controller.js","../../../javascript/tables/orderable/form_controller.js","../../../javascript/tables/selection/form_controller.js","../../../javascript/tables/selection/table_controller.js","../../../javascript/tables/query_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 { Controller } from \"@hotwired/stimulus\";\n\n/**\n * Couples an input element in a row to the selection form which is turbo-permanent and outside the table.\n * When the input is toggled, the form will create/destroy hidden inputs. The checkbox inside this cell will follow\n * the hidden inputs.\n *\n * Cell value may change when:\n * * cell connects, e.g. when the user paginates\n * * cell is re-used by turbo-morph, e.g. pagination\n * * cell is toggled\n * * select-all/de-select-all on table header\n */\nexport default class SelectionItemController extends Controller {\n static outlets = [\"tables--selection--form\"];\n static values = {\n params: Object,\n checked: Boolean,\n };\n\n tablesSelectionFormOutletConnected(form) {\n form.visible(this.id, true);\n this.checkedValue = form.isSelected(this.id);\n }\n\n disconnect() {\n // Remove from form's list of visible selections.\n // This should be an outlet disconnect, but those events are not reliable in turbo 8.0\n if (this.hasTablesSelectionFormOutlet) {\n this.tablesSelectionFormOutlet.visible(this.id, false);\n }\n }\n\n change(e) {\n e.preventDefault();\n\n this.checkedValue = this.tablesSelectionFormOutlet.toggle(this.id);\n }\n\n get id() {\n return this.paramsValue.id;\n }\n\n /**\n * Update checked to match match selection form. This occurs when the item is re-used by turbo-morph.\n */\n paramsValueChanged(params, previous) {\n if (!this.hasTablesSelectionFormOutlet) return;\n\n // if id is changing (e.g. morph) then let the form know that the previous id is now not visible\n if (previous.id !== params.id) {\n this.tablesSelectionFormOutlet.visible(previous.id, false);\n }\n\n // tell form that our id is now visible in the table\n this.tablesSelectionFormOutlet.visible(params.id, true);\n\n // id has changed, so update checked from form\n this.checkedValue = this.tablesSelectionFormOutlet.isSelected(params.id);\n\n // propagate changes\n this.update();\n }\n\n /**\n * Update input to match checked. This occurs when the item is toggled, connected, or morphed.\n */\n checkedValueChanged() {\n if (!this.hasTablesSelectionFormOutlet) return;\n\n // ensure that checked matches the form, i.e. if morphed\n this.checkedValue = this.tablesSelectionFormOutlet.isSelected(this.id);\n\n // propagate changes\n this.update();\n }\n\n /**\n * Notify table that id or value may have changed. Note that this may fire when nothing has changed.\n *\n * Debouncing to minimise dom updates.\n */\n async update() {\n this.updating ||= Promise.resolve().then(() => {\n this.#update();\n delete this.updating;\n });\n\n return this.updating;\n }\n\n #update() {\n this.element.querySelector(\"input\").checked = this.checkedValue;\n this.dispatch(\"select\", {\n detail: { id: this.id, selected: this.checkedValue },\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class QueryInputController extends Controller {\n static targets = [\"input\", \"highlight\"];\n static values = { query: String };\n\n connect() {\n this.queryValue = this.inputTarget.value;\n }\n\n update() {\n this.queryValue = this.inputTarget.value;\n }\n\n queryValueChanged(query) {\n this.highlightTarget.innerHTML = \"\";\n\n new Parser().parse(query).tokens.forEach((token) => {\n this.highlightTarget.appendChild(token.render());\n });\n }\n}\n\nclass Parser {\n constructor() {\n this.tokens = [];\n this.values = null;\n }\n\n parse(input) {\n const query = new StringScanner(input);\n\n while (!query.isEos()) {\n this.push(this.skipWhitespace(query));\n\n const value = this.takeTagged(query) || this.takeUntagged(query);\n\n if (!this.push(value)) break;\n }\n\n return this;\n }\n\n push(token) {\n if (token) {\n this.values ? this.values.push(token) : this.tokens.push(token);\n }\n\n return !!token;\n }\n\n skipWhitespace(query) {\n if (!query.scan(/\\s+/)) return;\n\n return new Token(query.matched());\n }\n\n takeUntagged(query) {\n if (!query.scan(/\\S+/)) return;\n\n return new Untagged(query.matched());\n }\n\n takeTagged(query) {\n if (!query.scan(/(\\w+(?:\\.\\w+)?)(:\\s*)/)) return;\n\n const key = query.valueAt(1);\n const separator = query.valueAt(2);\n\n const value =\n this.takeArrayValue(query) || this.takeSingleValue(query) || new Token();\n\n return new Tagged(key, separator, value);\n }\n\n takeArrayValue(query) {\n if (!query.scan(/\\[\\s*/)) return;\n\n const start = new Token(query.matched());\n const values = (this.values = []);\n\n while (!query.isEos()) {\n if (!this.push(this.takeSingleValue(query))) break;\n if (!this.push(this.takeDelimiter(query))) break;\n }\n\n query.scan(/\\s*]/);\n const end = new Token(query.matched());\n\n this.values = null;\n\n return new Array(start, values, end);\n }\n\n takeDelimiter(query) {\n if (!query.scan(/\\s*,\\s*/)) return;\n\n return new Token(query.matched());\n }\n\n takeSingleValue(query) {\n return this.takeQuotedValue(query) || this.takeUnquotedValue(query);\n }\n\n takeQuotedValue(query) {\n if (!query.scan(/\"([^\"]*)\"/)) return;\n\n return new Value(query.matched());\n }\n\n takeUnquotedValue(query) {\n if (!query.scan(/[^ \\],]*/)) return;\n\n return new Value(query.matched());\n }\n}\n\nclass Token {\n constructor(value = \"\") {\n this.value = value;\n }\n\n render() {\n return document.createTextNode(this.value);\n }\n}\n\nclass Value extends Token {\n render() {\n const span = document.createElement(\"span\");\n span.className = \"value\";\n span.innerText = this.value;\n\n return span;\n }\n}\n\nclass Tagged extends Token {\n constructor(key, separator, value) {\n super();\n\n this.key = key;\n this.separator = separator;\n this.value = value;\n }\n\n render() {\n const span = document.createElement(\"span\");\n span.className = \"tag\";\n\n const key = document.createElement(\"span\");\n key.className = \"key\";\n key.innerText = this.key;\n\n span.appendChild(key);\n span.appendChild(document.createTextNode(this.separator));\n span.appendChild(this.value.render());\n\n return span;\n }\n}\n\nclass Untagged extends Token {\n render() {\n const span = document.createElement(\"span\");\n span.className = \"untagged\";\n span.innerText = this.value;\n return span;\n }\n}\n\nclass Array extends Token {\n constructor(start, values, end) {\n super();\n\n this.start = start;\n this.values = values;\n this.end = end;\n }\n\n render() {\n const array = document.createElement(\"span\");\n array.className = \"array-values\";\n array.appendChild(this.start.render());\n\n this.values.forEach((value) => {\n const span = document.createElement(\"span\");\n span.appendChild(value.render());\n array.appendChild(span);\n });\n\n array.appendChild(this.end.render());\n\n return array;\n }\n}\n\nclass StringScanner {\n constructor(input) {\n this.input = input;\n this.position = 0;\n this.last = null;\n }\n\n isEos() {\n return this.position >= this.input.length;\n }\n\n scan(regex) {\n const match = regex.exec(this.input.substring(this.position));\n if (match?.index === 0) {\n this.last = match;\n this.position += match[0].length;\n return true;\n } else {\n this.last = {};\n return false;\n }\n }\n\n matched() {\n return this.last && this.last[0];\n }\n\n valueAt(index) {\n return this.last && this.last[index];\n }\n}\n","import OrderableItemController from \"./orderable/item_controller\";\nimport OrderableListController from \"./orderable/list_controller\";\nimport OrderableFormController from \"./orderable/form_controller\";\nimport SelectionFormController from \"./selection/form_controller\";\nimport SelectionItemController from \"./selection/item_controller\";\nimport SelectionTableController from \"./selection/table_controller\";\nimport QueryController from \"./query_controller\";\nimport QueryInputController from \"./query_input_controller\";\n\nconst Definitions = [\n {\n identifier: \"tables--orderable--item\",\n controllerConstructor: OrderableItemController,\n },\n {\n identifier: \"tables--orderable--list\",\n controllerConstructor: OrderableListController,\n },\n {\n identifier: \"tables--orderable--form\",\n controllerConstructor: OrderableFormController,\n },\n {\n identifier: \"tables--selection--form\",\n controllerConstructor: SelectionFormController,\n },\n {\n identifier: \"tables--selection--item\",\n controllerConstructor: SelectionItemController,\n },\n {\n identifier: \"tables--selection--table\",\n controllerConstructor: SelectionTableController,\n },\n {\n identifier: \"tables--query\",\n controllerConstructor: QueryController,\n },\n {\n identifier: \"tables--query-input\",\n controllerConstructor: QueryInputController,\n },\n];\n\nexport { Definitions as default };\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 /** Retrieve params for use in the form */\n params(scope) {\n const { id_name, id_value, index_name } = this.paramsValue;\n return [\n { name: `${scope}[${id_value}][${id_name}]`, value: this.id },\n { name: `${scope}[${id_value}][${index_name}]`, value: this.index },\n ];\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 static values = { scope: String };\n\n add(item) {\n item.params(this.scopeValue).forEach(({ name, value }) => {\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${name}\" value=\"${value}\" data-generated>`,\n );\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","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionFormController extends Controller {\n static values = {\n count: Number,\n primaryKey: { type: String, default: \"id\" },\n };\n static targets = [\"count\", \"singular\", \"plural\"];\n\n connect() {\n this.countValue = this.inputs.length;\n }\n\n /**\n * @param id to toggle\n * @return {boolean} true if selected, false if unselected\n */\n toggle(id) {\n const input = this.input(id);\n\n if (input) {\n input.remove();\n } else {\n this.element.insertAdjacentHTML(\n \"beforeend\",\n `<input type=\"hidden\" name=\"${this.primaryKeyValue}[]\" value=\"${id}\">`,\n );\n }\n\n this.countValue = this.visibleInputs.length;\n\n return !input;\n }\n\n /**\n * @param id to toggle visibility\n * @return {boolean} true if visible, false if not visible\n */\n visible(id, visible) {\n const input = this.input(id);\n\n if (input) {\n input.disabled = !visible;\n }\n\n this.countValue = this.visibleInputs.length;\n\n return !input;\n }\n\n /**\n * @returns {boolean} true if the given id is currently selected\n */\n isSelected(id) {\n return !!this.input(id);\n }\n\n get inputs() {\n return this.element.querySelectorAll(\n `input[name=\"${this.primaryKeyValue}[]\"]`,\n );\n }\n\n get visibleInputs() {\n return Array.from(this.inputs).filter((i) => !i.disabled);\n }\n\n input(id) {\n return this.element.querySelector(\n `input[name=\"${this.primaryKeyValue}[]\"][value=\"${id}\"]`,\n );\n }\n\n countValueChanged(count) {\n this.element.toggleAttribute(\"hidden\", count === 0);\n this.countTarget.textContent = count;\n this.singularTarget.toggleAttribute(\"hidden\", count !== 1);\n this.pluralTarget.toggleAttribute(\"hidden\", count === 1);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class SelectionTableController extends Controller {\n static targets = [\"header\", \"item\"];\n static outlets = [\"tables--selection--form\"];\n\n itemTargetConnected(item) {\n this.update();\n }\n\n itemTargetDisconnected(item) {\n this.update();\n }\n\n toggleHeader(e) {\n this.items.forEach((item) => {\n if (item.checkedValue === e.target.checked) return;\n\n item.checkedValue = this.tablesSelectionFormOutlet.toggle(item.id);\n });\n }\n\n async update() {\n this.updating ||= Promise.resolve().then(() => {\n this.#update();\n delete this.updating;\n });\n\n return this.updating;\n }\n\n #update() {\n let present = 0;\n let checked = 0;\n\n this.items.forEach((item) => {\n present++;\n if (item.checkedValue) checked++;\n });\n\n this.headerInput.checked = present > 0 && checked === present;\n this.headerInput.indeterminate = checked > 0 && checked !== present;\n }\n\n get headerInput() {\n return this.headerTarget.querySelector(\"input\");\n }\n\n get items() {\n return this.itemTargets.map((el) => this.#itemOutlet(el)).filter((c) => c);\n }\n\n /**\n * Ideally we would be using outlets, but as of turbo 8.0.4 outlets do not fire disconnect events when morphing.\n *\n * Instead, we're using the targets to finds the controller.\n */\n #itemOutlet(el) {\n return this.application.getControllerForElementAndIdentifier(\n el,\n \"tables--selection--item\",\n );\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class QueryController extends Controller {\n static targets = [\"modal\"];\n\n disconnect() {\n delete this.pending;\n\n document.removeEventListener(\"selectionchange\", this.selection);\n }\n\n focus() {\n if (document.activeElement === this.query) return;\n\n this.query.addEventListener(\n \"focusin\",\n (e) => {\n e.target.setSelectionRange(-1, -1);\n },\n { once: true },\n );\n\n this.query.focus();\n }\n\n closeModal() {\n delete this.modalTarget.dataset.open;\n\n if (document.activeElement === this.query) document.activeElement.blur();\n\n document.removeEventListener(\"selectionchange\", this.selection);\n }\n\n openModal() {\n this.modalTarget.dataset.open = true;\n\n document.addEventListener(\"selectionchange\", this.selection);\n }\n\n clear() {\n if (this.query.value === \"\") {\n // if the user presses escape once, browser clears the input\n // if the user presses escape again, get them out of here\n this.closeModal();\n }\n }\n\n submit() {\n const hasFocus = this.isFocused;\n const position = hasFocus && this.query.selectionStart;\n\n if (this.pending) {\n clearTimeout(this.pending);\n delete this.pending;\n }\n\n // prevent an unnecessary `?q=` parameter from appearing in the URL\n if (this.query.value === \"\") {\n this.query.disabled = true;\n\n // restore input and focus after form submission\n setTimeout(() => {\n this.query.disabled = false;\n if (hasFocus) this.query.focus();\n }, 0);\n }\n\n // add/remove current cursor position\n if (hasFocus && position) {\n this.position.value = position;\n this.position.disabled = false;\n } else {\n this.position.value = \"\";\n this.position.disabled = true;\n }\n }\n\n update = () => {\n if (this.pending) clearTimeout(this.pending);\n this.pending = setTimeout(() => {\n this.element.requestSubmit();\n }, 300);\n };\n\n selection = () => {\n if (this.isFocused) this.update();\n };\n\n beforeMorphAttribute(e) {\n switch (e.detail.attributeName) {\n case \"data-open\":\n e.preventDefault();\n break;\n }\n }\n\n get query() {\n return this.element.querySelector(\"input[type=search]\");\n }\n\n get position() {\n return this.element.querySelector(\"input[name=p]\");\n }\n\n get isFocused() {\n return this.query === document.activeElement;\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","SelectionItemController","Controller","static","params","Object","checked","Boolean","tablesSelectionFormOutletConnected","form","visible","checkedValue","isSelected","disconnect","hasTablesSelectionFormOutlet","tablesSelectionFormOutlet","change","e","preventDefault","toggle","paramsValue","paramsValueChanged","previous","update","checkedValueChanged","updating","Promise","resolve","then","element","querySelector","dispatch","detail","selected","Parser","tokens","values","parse","input","query","StringScanner","isEos","push","skipWhitespace","value","takeTagged","takeUntagged","token","scan","Token","matched","Untagged","key","valueAt","separator","takeArrayValue","takeSingleValue","Tagged","start","takeDelimiter","end","Array","takeQuotedValue","takeUnquotedValue","Value","render","document","createTextNode","span","createElement","className","innerText","super","appendChild","array","forEach","last","length","regex","match","exec","substring","index","Definitions","identifier","controllerConstructor","connect","from","parentElement","children","indexOf","id_value","dragUpdate","offset","dragOffset","style","zIndex","toggleAttribute","updateVisually","dragIndex","updateIndex","scope","id_name","index_name","name","reset","removeAttribute","hasChanges","index_value","round","comparisonIndex","startDragging","dragState","addEventListener","mousemove","mouseup","window","scroll","stopDragging","removeEventListener","tablesOrderableItemOutlets","item","drop","dragItem","newIndex","targetItem","insertAdjacentElement","commitChanges","tablesOrderableFormOutlet","clear","add","submit","mousedown","isDragging","animate","ticking","requestAnimationFrame","tablesOrderableFormOutlets","tablesOrderableFormOutletConnected","tablesOrderableFormOutletDisconnected","currentItems","find","toSorted","a","b","String","scopeValue","insertAdjacentHTML","inputs","requestSubmit","remove","querySelectorAll","count","Number","primaryKey","type","default","countValue","primaryKeyValue","visibleInputs","disabled","filter","i","countValueChanged","countTarget","textContent","singularTarget","pluralTarget","itemTargetConnected","itemTargetDisconnected","toggleHeader","items","present","headerInput","indeterminate","headerTarget","itemTargets","map","el","itemOutlet","c","application","getControllerForElementAndIdentifier","pending","selection","focus","activeElement","setSelectionRange","once","closeModal","modalTarget","dataset","open","blur","openModal","hasFocus","isFocused","selectionStart","clearTimeout","setTimeout","beforeMorphAttribute","attributeName","queryValue","inputTarget","queryValueChanged","highlightTarget","innerHTML"],"mappings":"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"}
@@ -1,3 +1,7 @@
1
1
  :where(th.selection, td.selection) {
2
2
  width: 2rem;
3
+
4
+ label {
5
+ display: block;
6
+ }
3
7
  }
@@ -8,6 +8,7 @@ module Katalyst
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  FORM_CONTROLLER = "tables--selection--form"
11
+ TABLE_CONTROLLER = "tables--selection--table"
11
12
  ITEM_CONTROLLER = "tables--selection--item"
12
13
 
13
14
  # Returns the default dom id for the selection form, uses the table's
@@ -27,10 +28,24 @@ module Katalyst
27
28
  # @example Render a select column
28
29
  # <% row.select %> # => <td><input type="checkbox" ...></td>
29
30
  def select(params: { id: record&.id }, form_id: Selectable.default_form_id(collection), **, &)
31
+ update_html_attributes(**table_selection_attributes(form_id:)) if header?
32
+
30
33
  with_cell(Cells::SelectComponent.new(
31
34
  collection:, row:, column: :_select, record:, label: "", heading: false, params:, form_id:, **,
32
35
  ), &)
33
36
  end
37
+
38
+ private
39
+
40
+ def table_selection_attributes(form_id:)
41
+ {
42
+ data: {
43
+ controller: TABLE_CONTROLLER,
44
+ "#{TABLE_CONTROLLER}-#{FORM_CONTROLLER}-outlet" => "##{form_id}",
45
+ action: ["#{ITEM_CONTROLLER}:select->#{TABLE_CONTROLLER}#update"],
46
+ },
47
+ }
48
+ end
34
49
  end
35
50
  end
36
51
  end
@@ -6,10 +6,8 @@
6
6
  <% end %>
7
7
  <% end %>
8
8
 
9
- <%= tag.table(**html_attributes) do %>
10
- <%= tag.tbody(**tbody_attributes) do %>
11
- <% summary_rows.each do |summary_row| %>
12
- <%= summary_row %>
13
- <% end %>
9
+ <%= tag.tbody(**tbody_attributes) do %>
10
+ <% summary_rows.each do |summary_row| %>
11
+ <%= summary_row %>
14
12
  <% end %>
15
13
  <% end %>
@@ -1,13 +1,11 @@
1
- <%= tag.table(**html_attributes) do %>
2
- <%= render caption if caption? %>
3
- <% if header_row? %>
4
- <%= tag.thead(**thead_attributes) do %>
5
- <%= header_row %>
6
- <% end %>
1
+ <%= render caption if caption? %>
2
+ <% if header_row? %>
3
+ <%= tag.thead(**thead_attributes) do %>
4
+ <%= header_row %>
7
5
  <% end %>
8
- <%= tag.tbody(**tbody_attributes) do %>
9
- <% body_rows.each do |body_row| %>
10
- <%= body_row %>
11
- <% end %>
6
+ <% end %>
7
+ <%= tag.tbody(**tbody_attributes) do %>
8
+ <% body_rows.each do |body_row| %>
9
+ <%= body_row %>
12
10
  <% end %>
13
11
  <% end %>
@@ -359,5 +359,14 @@ module Katalyst
359
359
  collection
360
360
  end
361
361
  end
362
+
363
+ # Output bare tags using preamble/postamble so html_attributes are rendered after content
364
+ def output_preamble
365
+ "<table #{tag.attributes(html_attributes)}>".html_safe # rubocop:disable Rails/OutputSafety
366
+ end
367
+
368
+ def output_postamble
369
+ "</table>".html_safe
370
+ end
362
371
  end
363
372
  end
@@ -7,28 +7,38 @@ module Katalyst
7
7
  def initialize(params:, form_id:, **)
8
8
  super(**)
9
9
 
10
- @params = params
10
+ @params = params
11
11
  @form_id = form_id
12
12
  end
13
13
 
14
+ def label
15
+ tag.label(tag.input(type: :checkbox))
16
+ end
17
+
14
18
  def rendered_value
15
- tag.input(type: :checkbox)
19
+ tag.label(tag.input(type: :checkbox))
16
20
  end
17
21
 
18
22
  private
19
23
 
20
24
  def default_html_attributes
21
25
  if @row.header?
22
- { class: "selection" }
26
+ {
27
+ class: "selection",
28
+ data: {
29
+ "#{Selectable::TABLE_CONTROLLER}-target" => "header",
30
+ action: "change->#{Selectable::TABLE_CONTROLLER}#toggleHeader",
31
+ },
32
+ }
23
33
  else
24
34
  {
25
35
  class: "selection",
26
36
  data: {
27
- controller: Selectable::ITEM_CONTROLLER,
28
- "#{Selectable::ITEM_CONTROLLER}-params-value" => @params.to_json,
37
+ controller: Selectable::ITEM_CONTROLLER,
38
+ action: "change->#{Selectable::ITEM_CONTROLLER}#change",
39
+ "#{Selectable::ITEM_CONTROLLER}-params-value" => @params.to_json,
29
40
  "#{Selectable::ITEM_CONTROLLER}-#{Selectable::FORM_CONTROLLER}-outlet" => "##{@form_id}",
30
- action: "change->#{Selectable::ITEM_CONTROLLER}#change",
31
- turbo_permanent: "",
41
+ "#{Selectable::TABLE_CONTROLLER}-target" => "item",
32
42
  },
33
43
  }
34
44
  end
@@ -7,8 +7,8 @@
7
7
  html: { action: false, hidden: "" }) do |form| %>
8
8
  <p class="tables--selection--summary">
9
9
  <span data-<%= form_target("count") %>>0</span>
10
- <span data-<%= form_target("singular") %> hidden><%= @collection.model_name.singular %></span>
11
- <span data-<%= form_target("plural") %>><%= @collection.model_name.plural %></span>
10
+ <span data-<%= form_target("singular") %> hidden><%= singular_name %></span>
11
+ <span data-<%= form_target("plural") %>><%= plural_name %></span>
12
12
  selected
13
13
  </p>
14
14
  <%= content %>
@@ -34,6 +34,14 @@ module Katalyst
34
34
  def form_target(value)
35
35
  "#{FORM_CONTROLLER}-target=#{value}"
36
36
  end
37
+
38
+ def singular_name
39
+ @collection.model_name.human(count: 1, default: @collection.model_name.human).downcase
40
+ end
41
+
42
+ def plural_name
43
+ @collection.model_name.human(count: 2, default: @collection.model_name.human.pluralize).downcase
44
+ end
37
45
  end
38
46
  end
39
47
  end
@@ -3,6 +3,7 @@ import OrderableListController from "./orderable/list_controller";
3
3
  import OrderableFormController from "./orderable/form_controller";
4
4
  import SelectionFormController from "./selection/form_controller";
5
5
  import SelectionItemController from "./selection/item_controller";
6
+ import SelectionTableController from "./selection/table_controller";
6
7
  import QueryController from "./query_controller";
7
8
  import QueryInputController from "./query_input_controller";
8
9
 
@@ -27,6 +28,10 @@ const Definitions = [
27
28
  identifier: "tables--selection--item",
28
29
  controllerConstructor: SelectionItemController,
29
30
  },
31
+ {
32
+ identifier: "tables--selection--table",
33
+ controllerConstructor: SelectionTableController,
34
+ },
30
35
  {
31
36
  identifier: "tables--query",
32
37
  controllerConstructor: QueryController,
@@ -27,7 +27,23 @@ export default class SelectionFormController extends Controller {
27
27
  );
28
28
  }
29
29
 
30
- this.countValue = this.inputs.length;
30
+ this.countValue = this.visibleInputs.length;
31
+
32
+ return !input;
33
+ }
34
+
35
+ /**
36
+ * @param id to toggle visibility
37
+ * @return {boolean} true if visible, false if not visible
38
+ */
39
+ visible(id, visible) {
40
+ const input = this.input(id);
41
+
42
+ if (input) {
43
+ input.disabled = !visible;
44
+ }
45
+
46
+ this.countValue = this.visibleInputs.length;
31
47
 
32
48
  return !input;
33
49
  }
@@ -45,6 +61,10 @@ export default class SelectionFormController extends Controller {
45
61
  );
46
62
  }
47
63
 
64
+ get visibleInputs() {
65
+ return Array.from(this.inputs).filter((i) => !i.disabled);
66
+ }
67
+
48
68
  input(id) {
49
69
  return this.element.querySelector(
50
70
  `input[name="${this.primaryKeyValue}[]"][value="${id}"]`,
@@ -1,5 +1,16 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
+ /**
4
+ * Couples an input element in a row to the selection form which is turbo-permanent and outside the table.
5
+ * When the input is toggled, the form will create/destroy hidden inputs. The checkbox inside this cell will follow
6
+ * the hidden inputs.
7
+ *
8
+ * Cell value may change when:
9
+ * * cell connects, e.g. when the user paginates
10
+ * * cell is re-used by turbo-morph, e.g. pagination
11
+ * * cell is toggled
12
+ * * select-all/de-select-all on table header
13
+ */
3
14
  export default class SelectionItemController extends Controller {
4
15
  static outlets = ["tables--selection--form"];
5
16
  static values = {
@@ -8,9 +19,18 @@ export default class SelectionItemController extends Controller {
8
19
  };
9
20
 
10
21
  tablesSelectionFormOutletConnected(form) {
22
+ form.visible(this.id, true);
11
23
  this.checkedValue = form.isSelected(this.id);
12
24
  }
13
25
 
26
+ disconnect() {
27
+ // Remove from form's list of visible selections.
28
+ // This should be an outlet disconnect, but those events are not reliable in turbo 8.0
29
+ if (this.hasTablesSelectionFormOutlet) {
30
+ this.tablesSelectionFormOutlet.visible(this.id, false);
31
+ }
32
+ }
33
+
14
34
  change(e) {
15
35
  e.preventDefault();
16
36
 
@@ -21,7 +41,58 @@ export default class SelectionItemController extends Controller {
21
41
  return this.paramsValue.id;
22
42
  }
23
43
 
24
- checkedValueChanged(checked) {
25
- this.element.querySelector("input").checked = checked;
44
+ /**
45
+ * Update checked to match match selection form. This occurs when the item is re-used by turbo-morph.
46
+ */
47
+ paramsValueChanged(params, previous) {
48
+ if (!this.hasTablesSelectionFormOutlet) return;
49
+
50
+ // if id is changing (e.g. morph) then let the form know that the previous id is now not visible
51
+ if (previous.id !== params.id) {
52
+ this.tablesSelectionFormOutlet.visible(previous.id, false);
53
+ }
54
+
55
+ // tell form that our id is now visible in the table
56
+ this.tablesSelectionFormOutlet.visible(params.id, true);
57
+
58
+ // id has changed, so update checked from form
59
+ this.checkedValue = this.tablesSelectionFormOutlet.isSelected(params.id);
60
+
61
+ // propagate changes
62
+ this.update();
63
+ }
64
+
65
+ /**
66
+ * Update input to match checked. This occurs when the item is toggled, connected, or morphed.
67
+ */
68
+ checkedValueChanged() {
69
+ if (!this.hasTablesSelectionFormOutlet) return;
70
+
71
+ // ensure that checked matches the form, i.e. if morphed
72
+ this.checkedValue = this.tablesSelectionFormOutlet.isSelected(this.id);
73
+
74
+ // propagate changes
75
+ this.update();
76
+ }
77
+
78
+ /**
79
+ * Notify table that id or value may have changed. Note that this may fire when nothing has changed.
80
+ *
81
+ * Debouncing to minimise dom updates.
82
+ */
83
+ async update() {
84
+ this.updating ||= Promise.resolve().then(() => {
85
+ this.#update();
86
+ delete this.updating;
87
+ });
88
+
89
+ return this.updating;
90
+ }
91
+
92
+ #update() {
93
+ this.element.querySelector("input").checked = this.checkedValue;
94
+ this.dispatch("select", {
95
+ detail: { id: this.id, selected: this.checkedValue },
96
+ });
26
97
  }
27
98
  }
@@ -0,0 +1,64 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class SelectionTableController extends Controller {
4
+ static targets = ["header", "item"];
5
+ static outlets = ["tables--selection--form"];
6
+
7
+ itemTargetConnected(item) {
8
+ this.update();
9
+ }
10
+
11
+ itemTargetDisconnected(item) {
12
+ this.update();
13
+ }
14
+
15
+ toggleHeader(e) {
16
+ this.items.forEach((item) => {
17
+ if (item.checkedValue === e.target.checked) return;
18
+
19
+ item.checkedValue = this.tablesSelectionFormOutlet.toggle(item.id);
20
+ });
21
+ }
22
+
23
+ async update() {
24
+ this.updating ||= Promise.resolve().then(() => {
25
+ this.#update();
26
+ delete this.updating;
27
+ });
28
+
29
+ return this.updating;
30
+ }
31
+
32
+ #update() {
33
+ let present = 0;
34
+ let checked = 0;
35
+
36
+ this.items.forEach((item) => {
37
+ present++;
38
+ if (item.checkedValue) checked++;
39
+ });
40
+
41
+ this.headerInput.checked = present > 0 && checked === present;
42
+ this.headerInput.indeterminate = checked > 0 && checked !== present;
43
+ }
44
+
45
+ get headerInput() {
46
+ return this.headerTarget.querySelector("input");
47
+ }
48
+
49
+ get items() {
50
+ return this.itemTargets.map((el) => this.#itemOutlet(el)).filter((c) => c);
51
+ }
52
+
53
+ /**
54
+ * Ideally we would be using outlets, but as of turbo 8.0.4 outlets do not fire disconnect events when morphing.
55
+ *
56
+ * Instead, we're using the targets to finds the controller.
57
+ */
58
+ #itemOutlet(el) {
59
+ return this.application.getControllerForElementAndIdentifier(
60
+ el,
61
+ "tables--selection--item",
62
+ );
63
+ }
64
+ }
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: 3.3.4
4
+ version: 3.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-01 00:00:00.000000000 Z
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: katalyst-html-attributes
@@ -121,6 +121,7 @@ files:
121
121
  - app/javascript/tables/query_input_controller.js
122
122
  - app/javascript/tables/selection/form_controller.js
123
123
  - app/javascript/tables/selection/item_controller.js
124
+ - app/javascript/tables/selection/table_controller.js
124
125
  - app/models/concerns/katalyst/tables/collection/core.rb
125
126
  - app/models/concerns/katalyst/tables/collection/filtering.rb
126
127
  - app/models/concerns/katalyst/tables/collection/has_params.rb