katalyst-navigation 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/builds/katalyst/navigation.esm.js +112 -28
- data/app/assets/builds/katalyst/navigation.js +112 -28
- data/app/assets/builds/katalyst/navigation.min.js +1 -1
- data/app/assets/builds/katalyst/navigation.min.js.map +1 -1
- data/app/assets/stylesheets/katalyst/navigation/editor/_item-actions.scss +1 -0
- data/app/assets/stylesheets/katalyst/navigation/editor/_new-items.scss +1 -1
- data/app/components/katalyst/navigation/editor/table_component.rb +1 -0
- data/app/javascript/navigation/editor/list_controller.js +113 -28
- metadata +2 -3
- data/app/views/katalyst/navigation/items/new.html.erb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e5f480a3b37362c541fdc405d7ec260378be55458b0dbd88824e69440624268
|
4
|
+
data.tar.gz: f662a3be9d829d081b4dceea36a9f699a02bd4b6e9a2e68a8999ad9aadbcfbc4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9fffb2f84b729bbe3235cddbb70b828b841994ea03075d884e2a3a09e23b27cf0e60aa750f5d58f393341d9104fb1df3582decf8c97372709b2e1c3eae1cbef
|
7
|
+
data.tar.gz: ee5f8d8695a8cbf074560efb47560b1a7e3bd6bb238d24972b55d5e62793df724253d67f08524ef63783dac73a5b707238f766dd08849d03f773e2c8ec981596
|
@@ -744,6 +744,19 @@ class ItemController extends Controller {
|
|
744
744
|
}
|
745
745
|
|
746
746
|
class ListController extends Controller {
|
747
|
+
connect() {
|
748
|
+
this.enterCount = 0;
|
749
|
+
}
|
750
|
+
|
751
|
+
/**
|
752
|
+
* When the user starts a drag within the list, set the item's dataTransfer
|
753
|
+
* properties to indicate that it's being dragged and update its style.
|
754
|
+
*
|
755
|
+
* We delay setting the dataset property until the next animation frame
|
756
|
+
* so that the style updates can be computed before the drag begins.
|
757
|
+
*
|
758
|
+
* @param event {DragEvent}
|
759
|
+
*/
|
747
760
|
dragstart(event) {
|
748
761
|
if (this.element !== event.target.parentElement) return;
|
749
762
|
|
@@ -751,51 +764,89 @@ class ListController extends Controller {
|
|
751
764
|
event.dataTransfer.effectAllowed = "move";
|
752
765
|
|
753
766
|
// update element style after drag has begun
|
754
|
-
|
767
|
+
requestAnimationFrame(() => (target.dataset.dragging = ""));
|
755
768
|
}
|
756
769
|
|
770
|
+
/**
|
771
|
+
* When the user drags an item over another item in the last, swap the
|
772
|
+
* dragging item with the item under the cursor.
|
773
|
+
*
|
774
|
+
* As a special case, if the item is dragged over placeholder space at the end
|
775
|
+
* of the list, move the item to the bottom of the list instead. This allows
|
776
|
+
* users to hit the list element more easily when adding new items to an empty
|
777
|
+
* list.
|
778
|
+
*
|
779
|
+
* @param event {DragEvent}
|
780
|
+
*/
|
757
781
|
dragover(event) {
|
758
|
-
const item = this.dragItem
|
782
|
+
const item = this.dragItem;
|
759
783
|
if (!item) return;
|
760
784
|
|
761
|
-
swap(
|
785
|
+
swap(dropTarget(event.target), item);
|
762
786
|
|
763
787
|
event.preventDefault();
|
764
788
|
return true;
|
765
789
|
}
|
766
790
|
|
791
|
+
/**
|
792
|
+
* When the user drags an item into the list, create a placeholder item to
|
793
|
+
* represent the new item. Note that we can't access the drag data
|
794
|
+
* until drop, so we assume that this is our template item for now.
|
795
|
+
*
|
796
|
+
* Users can cancel the drag by dragging the item out of the list or by
|
797
|
+
* pressing escape. Both are handled by `cancelDrag`.
|
798
|
+
*
|
799
|
+
* @param event {DragEvent}
|
800
|
+
*/
|
767
801
|
dragenter(event) {
|
768
802
|
event.preventDefault();
|
769
803
|
|
770
|
-
|
804
|
+
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
805
|
+
this.enterCount++;
|
806
|
+
|
807
|
+
if (copyAllowed(event) && !this.dragItem) {
|
771
808
|
const item = document.createElement("li");
|
772
809
|
item.dataset.dragging = "";
|
773
810
|
item.dataset.newItem = "";
|
774
|
-
this.element.
|
811
|
+
this.element.appendChild(item);
|
775
812
|
}
|
776
813
|
}
|
777
814
|
|
815
|
+
/**
|
816
|
+
* When the user drags the item out of the list, remove the placeholder.
|
817
|
+
* This allows users to cancel the drag by dragging the item out of the list.
|
818
|
+
*
|
819
|
+
* @param event {DragEvent}
|
820
|
+
*/
|
778
821
|
dragleave(event) {
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
if (
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
822
|
+
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
823
|
+
// https://bugs.webkit.org/show_bug.cgi?id=66547
|
824
|
+
this.enterCount--;
|
825
|
+
|
826
|
+
if (
|
827
|
+
this.enterCount <= 0 &&
|
828
|
+
this.dragItem.dataset.hasOwnProperty("newItem")
|
829
|
+
) {
|
830
|
+
this.cancelDrag(event);
|
788
831
|
}
|
789
832
|
}
|
790
833
|
|
834
|
+
/**
|
835
|
+
* When the user drops an item into the list, end the drag and reindex the list.
|
836
|
+
*
|
837
|
+
* If the item is a new item, we replace the placeholder with the template
|
838
|
+
* item data from the dataTransfer API.
|
839
|
+
*
|
840
|
+
* @param event {DragEvent}
|
841
|
+
*/
|
791
842
|
drop(event) {
|
792
|
-
let item = this.dragItem
|
843
|
+
let item = this.dragItem;
|
793
844
|
|
794
845
|
if (!item) return;
|
795
846
|
|
796
847
|
event.preventDefault();
|
797
848
|
delete item.dataset.dragging;
|
798
|
-
swap(
|
849
|
+
swap(dropTarget(event.target), item);
|
799
850
|
|
800
851
|
if (item.dataset.hasOwnProperty("newItem")) {
|
801
852
|
const placeholder = item;
|
@@ -804,7 +855,7 @@ class ListController extends Controller {
|
|
804
855
|
item = template.content.querySelector("li");
|
805
856
|
|
806
857
|
this.element.replaceChild(item, placeholder);
|
807
|
-
|
858
|
+
requestAnimationFrame(() =>
|
808
859
|
item.querySelector("[role='button'][value='edit']").click()
|
809
860
|
);
|
810
861
|
}
|
@@ -816,23 +867,27 @@ class ListController extends Controller {
|
|
816
867
|
});
|
817
868
|
}
|
818
869
|
|
870
|
+
/**
|
871
|
+
* End an in-progress drag. If the item is a new item, remove it, otherwise
|
872
|
+
* reset the item's style and restore its original position in the list.
|
873
|
+
*/
|
819
874
|
dragend() {
|
820
|
-
const item = this.dragItem
|
821
|
-
if (!item) return;
|
875
|
+
const item = this.dragItem;
|
822
876
|
|
823
|
-
|
824
|
-
|
877
|
+
if (!item) ; else if (item.dataset.hasOwnProperty("newItem")) {
|
878
|
+
item.remove();
|
879
|
+
} else {
|
880
|
+
delete item.dataset.dragging;
|
881
|
+
this.reset();
|
882
|
+
}
|
825
883
|
}
|
826
884
|
|
827
|
-
|
828
|
-
return this.
|
885
|
+
get isDragging() {
|
886
|
+
return !!this.dragItem;
|
829
887
|
}
|
830
888
|
|
831
|
-
|
832
|
-
return (
|
833
|
-
e.closest("[data-controller='navigation--editor--list'] > *") ||
|
834
|
-
e.closest("[data-controller='navigation--editor--list']")
|
835
|
-
);
|
889
|
+
get dragItem() {
|
890
|
+
return this.element.querySelector("[data-dragging]");
|
836
891
|
}
|
837
892
|
|
838
893
|
reindex() {
|
@@ -844,6 +899,12 @@ class ListController extends Controller {
|
|
844
899
|
}
|
845
900
|
}
|
846
901
|
|
902
|
+
/**
|
903
|
+
* Swaps two list items. If target is a list, the item is appended.
|
904
|
+
*
|
905
|
+
* @param target the target element to swap with
|
906
|
+
* @param item the item the user is dragging
|
907
|
+
*/
|
847
908
|
function swap(target, item) {
|
848
909
|
if (!target) return;
|
849
910
|
if (target === item) return;
|
@@ -862,6 +923,29 @@ function swap(target, item) {
|
|
862
923
|
}
|
863
924
|
}
|
864
925
|
|
926
|
+
/**
|
927
|
+
* Returns true if the event supports copy or copy move.
|
928
|
+
*
|
929
|
+
* Chrome and Firefox use copy, but Safari only supports copyMove.
|
930
|
+
*/
|
931
|
+
function copyAllowed(event) {
|
932
|
+
return (
|
933
|
+
event.dataTransfer.effectAllowed === "copy" ||
|
934
|
+
event.dataTransfer.effectAllowed === "copyMove"
|
935
|
+
);
|
936
|
+
}
|
937
|
+
|
938
|
+
/**
|
939
|
+
* Given an event target, return the closest drop target, if any.
|
940
|
+
*/
|
941
|
+
function dropTarget(e) {
|
942
|
+
return (
|
943
|
+
e &&
|
944
|
+
(e.closest("[data-controller='navigation--editor--list'] > *") ||
|
945
|
+
e.closest("[data-controller='navigation--editor--list']"))
|
946
|
+
);
|
947
|
+
}
|
948
|
+
|
865
949
|
class NewItemController extends Controller {
|
866
950
|
static targets = ["template"];
|
867
951
|
|
@@ -744,6 +744,19 @@ class ItemController extends Controller {
|
|
744
744
|
}
|
745
745
|
|
746
746
|
class ListController extends Controller {
|
747
|
+
connect() {
|
748
|
+
this.enterCount = 0;
|
749
|
+
}
|
750
|
+
|
751
|
+
/**
|
752
|
+
* When the user starts a drag within the list, set the item's dataTransfer
|
753
|
+
* properties to indicate that it's being dragged and update its style.
|
754
|
+
*
|
755
|
+
* We delay setting the dataset property until the next animation frame
|
756
|
+
* so that the style updates can be computed before the drag begins.
|
757
|
+
*
|
758
|
+
* @param event {DragEvent}
|
759
|
+
*/
|
747
760
|
dragstart(event) {
|
748
761
|
if (this.element !== event.target.parentElement) return;
|
749
762
|
|
@@ -751,51 +764,89 @@ class ListController extends Controller {
|
|
751
764
|
event.dataTransfer.effectAllowed = "move";
|
752
765
|
|
753
766
|
// update element style after drag has begun
|
754
|
-
|
767
|
+
requestAnimationFrame(() => (target.dataset.dragging = ""));
|
755
768
|
}
|
756
769
|
|
770
|
+
/**
|
771
|
+
* When the user drags an item over another item in the last, swap the
|
772
|
+
* dragging item with the item under the cursor.
|
773
|
+
*
|
774
|
+
* As a special case, if the item is dragged over placeholder space at the end
|
775
|
+
* of the list, move the item to the bottom of the list instead. This allows
|
776
|
+
* users to hit the list element more easily when adding new items to an empty
|
777
|
+
* list.
|
778
|
+
*
|
779
|
+
* @param event {DragEvent}
|
780
|
+
*/
|
757
781
|
dragover(event) {
|
758
|
-
const item = this.dragItem
|
782
|
+
const item = this.dragItem;
|
759
783
|
if (!item) return;
|
760
784
|
|
761
|
-
swap(
|
785
|
+
swap(dropTarget(event.target), item);
|
762
786
|
|
763
787
|
event.preventDefault();
|
764
788
|
return true;
|
765
789
|
}
|
766
790
|
|
791
|
+
/**
|
792
|
+
* When the user drags an item into the list, create a placeholder item to
|
793
|
+
* represent the new item. Note that we can't access the drag data
|
794
|
+
* until drop, so we assume that this is our template item for now.
|
795
|
+
*
|
796
|
+
* Users can cancel the drag by dragging the item out of the list or by
|
797
|
+
* pressing escape. Both are handled by `cancelDrag`.
|
798
|
+
*
|
799
|
+
* @param event {DragEvent}
|
800
|
+
*/
|
767
801
|
dragenter(event) {
|
768
802
|
event.preventDefault();
|
769
803
|
|
770
|
-
|
804
|
+
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
805
|
+
this.enterCount++;
|
806
|
+
|
807
|
+
if (copyAllowed(event) && !this.dragItem) {
|
771
808
|
const item = document.createElement("li");
|
772
809
|
item.dataset.dragging = "";
|
773
810
|
item.dataset.newItem = "";
|
774
|
-
this.element.
|
811
|
+
this.element.appendChild(item);
|
775
812
|
}
|
776
813
|
}
|
777
814
|
|
815
|
+
/**
|
816
|
+
* When the user drags the item out of the list, remove the placeholder.
|
817
|
+
* This allows users to cancel the drag by dragging the item out of the list.
|
818
|
+
*
|
819
|
+
* @param event {DragEvent}
|
820
|
+
*/
|
778
821
|
dragleave(event) {
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
if (
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
822
|
+
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
823
|
+
// https://bugs.webkit.org/show_bug.cgi?id=66547
|
824
|
+
this.enterCount--;
|
825
|
+
|
826
|
+
if (
|
827
|
+
this.enterCount <= 0 &&
|
828
|
+
this.dragItem.dataset.hasOwnProperty("newItem")
|
829
|
+
) {
|
830
|
+
this.cancelDrag(event);
|
788
831
|
}
|
789
832
|
}
|
790
833
|
|
834
|
+
/**
|
835
|
+
* When the user drops an item into the list, end the drag and reindex the list.
|
836
|
+
*
|
837
|
+
* If the item is a new item, we replace the placeholder with the template
|
838
|
+
* item data from the dataTransfer API.
|
839
|
+
*
|
840
|
+
* @param event {DragEvent}
|
841
|
+
*/
|
791
842
|
drop(event) {
|
792
|
-
let item = this.dragItem
|
843
|
+
let item = this.dragItem;
|
793
844
|
|
794
845
|
if (!item) return;
|
795
846
|
|
796
847
|
event.preventDefault();
|
797
848
|
delete item.dataset.dragging;
|
798
|
-
swap(
|
849
|
+
swap(dropTarget(event.target), item);
|
799
850
|
|
800
851
|
if (item.dataset.hasOwnProperty("newItem")) {
|
801
852
|
const placeholder = item;
|
@@ -804,7 +855,7 @@ class ListController extends Controller {
|
|
804
855
|
item = template.content.querySelector("li");
|
805
856
|
|
806
857
|
this.element.replaceChild(item, placeholder);
|
807
|
-
|
858
|
+
requestAnimationFrame(() =>
|
808
859
|
item.querySelector("[role='button'][value='edit']").click()
|
809
860
|
);
|
810
861
|
}
|
@@ -816,23 +867,27 @@ class ListController extends Controller {
|
|
816
867
|
});
|
817
868
|
}
|
818
869
|
|
870
|
+
/**
|
871
|
+
* End an in-progress drag. If the item is a new item, remove it, otherwise
|
872
|
+
* reset the item's style and restore its original position in the list.
|
873
|
+
*/
|
819
874
|
dragend() {
|
820
|
-
const item = this.dragItem
|
821
|
-
if (!item) return;
|
875
|
+
const item = this.dragItem;
|
822
876
|
|
823
|
-
|
824
|
-
|
877
|
+
if (!item) ; else if (item.dataset.hasOwnProperty("newItem")) {
|
878
|
+
item.remove();
|
879
|
+
} else {
|
880
|
+
delete item.dataset.dragging;
|
881
|
+
this.reset();
|
882
|
+
}
|
825
883
|
}
|
826
884
|
|
827
|
-
|
828
|
-
return this.
|
885
|
+
get isDragging() {
|
886
|
+
return !!this.dragItem;
|
829
887
|
}
|
830
888
|
|
831
|
-
|
832
|
-
return (
|
833
|
-
e.closest("[data-controller='navigation--editor--list'] > *") ||
|
834
|
-
e.closest("[data-controller='navigation--editor--list']")
|
835
|
-
);
|
889
|
+
get dragItem() {
|
890
|
+
return this.element.querySelector("[data-dragging]");
|
836
891
|
}
|
837
892
|
|
838
893
|
reindex() {
|
@@ -844,6 +899,12 @@ class ListController extends Controller {
|
|
844
899
|
}
|
845
900
|
}
|
846
901
|
|
902
|
+
/**
|
903
|
+
* Swaps two list items. If target is a list, the item is appended.
|
904
|
+
*
|
905
|
+
* @param target the target element to swap with
|
906
|
+
* @param item the item the user is dragging
|
907
|
+
*/
|
847
908
|
function swap(target, item) {
|
848
909
|
if (!target) return;
|
849
910
|
if (target === item) return;
|
@@ -862,6 +923,29 @@ function swap(target, item) {
|
|
862
923
|
}
|
863
924
|
}
|
864
925
|
|
926
|
+
/**
|
927
|
+
* Returns true if the event supports copy or copy move.
|
928
|
+
*
|
929
|
+
* Chrome and Firefox use copy, but Safari only supports copyMove.
|
930
|
+
*/
|
931
|
+
function copyAllowed(event) {
|
932
|
+
return (
|
933
|
+
event.dataTransfer.effectAllowed === "copy" ||
|
934
|
+
event.dataTransfer.effectAllowed === "copyMove"
|
935
|
+
);
|
936
|
+
}
|
937
|
+
|
938
|
+
/**
|
939
|
+
* Given an event target, return the closest drop target, if any.
|
940
|
+
*/
|
941
|
+
function dropTarget(e) {
|
942
|
+
return (
|
943
|
+
e &&
|
944
|
+
(e.closest("[data-controller='navigation--editor--list'] > *") ||
|
945
|
+
e.closest("[data-controller='navigation--editor--list']"))
|
946
|
+
);
|
947
|
+
}
|
948
|
+
|
865
949
|
class NewItemController extends Controller {
|
866
950
|
static targets = ["template"];
|
867
951
|
|
@@ -1,2 +1,2 @@
|
|
1
|
-
import{Controller as e}from"@hotwired/stimulus";class t{static comparator(e,t){return e.index-t.index}constructor(e){this.node=e}get itemId(){return this.node.dataset.navigationItemId}get#e(){return this.node.querySelector('input[name$="[id]"]')}set itemId(e){this.itemId!==e&&(this.node.dataset.navigationItemId=`${e}`,this.#e.value=`${e}`)}get depth(){return parseInt(this.node.dataset.navigationDepth)||0}get#t(){return this.node.querySelector('input[name$="[depth]"]')}set depth(e){this.depth!==e&&(this.node.dataset.navigationDepth=`${e}`,this.#t.value=`${e}`)}get index(){return parseInt(this.node.dataset.navigationIndex)}get#n(){return this.node.querySelector('input[name$="[index]"]')}set index(e){this.index!==e&&(this.node.dataset.navigationIndex=`${e}`,this.#n.value=`${e}`)}get isLayout(){return this.node.hasAttribute("data-content-layout")}get previousItem(){let e=this.node.previousElementSibling;if(e)return new t(e)}get nextItem(){let e=this.node.nextElementSibling;if(e)return new t(e)}hasCollapsedDescendants(){let e=this.#s;return!!e&&e.children.length>0}hasExpandedDescendants(){let e=this.nextItem;return!!e&&e.depth>this.depth}traverse(e){const t=this.#a;e(this),this.#i(e),t.forEach((t=>t.#i(e)))}#i(e){this.hasCollapsedDescendants()&&this.#d.forEach((t=>{e(t),t.#i(e)}))}collapse(){let e=this.#s;e||(e=function(e){const t=document.createElement("ol");return t.setAttribute("class","hidden"),t.dataset.navigationChildren="",e.appendChild(t),t}(this.node)),this.#a.forEach((t=>e.appendChild(t.node)))}expand(){this.hasCollapsedDescendants()&&Array.from(this.#s.children).reverse().forEach((e=>{this.node.insertAdjacentElement("afterend",e)}))}toggleRule(e,t=!1){this.node.dataset.hasOwnProperty(e)&&!t&&delete this.node.dataset[e],!this.node.dataset.hasOwnProperty(e)&&t&&(this.node.dataset[e]=""),"denyDrag"===e&&(this.node.hasAttribute("draggable")||t||this.node.setAttribute("draggable","true"),this.node.hasAttribute("draggable")&&t&&this.node.removeAttribute("draggable"))}hasItemIdChanged(){return!(this.#e.value===this.itemId)}updateAfterChange(){this.itemId=this.#e.value,this.#n.value=this.index,this.#t.value=this.depth}get#s(){return this.node.querySelector(":scope > [data-navigation-children]")}get#a(){const e=[];let t=this.nextItem;for(;t&&t.depth>this.depth;)e.push(t),t=t.nextItem;return e}get#d(){return this.hasCollapsedDescendants()?Array.from(this.#s.children).map((e=>new t(e))):[]}}class n{constructor(e){this.node=e}get items(){return e=this.node.querySelectorAll("[data-navigation-index]"),Array.from(e).map((e=>new t(e)));var e}get state(){const e=this.node.querySelectorAll("li input[type=hidden]");return Array.from(e).map((e=>e.value)).join("/")}reindex(){this.items.map(((e,t)=>e.index=t))}reset(){this.items.sort(t.comparator).forEach((e=>{this.node.appendChild(e.node)}))}}class s{static rules=["denyDeNest","denyNest","denyCollapse","denyExpand","denyRemove","denyDrag","denyEdit"];constructor(e=null,t=!1){this.maxDepth=e,this.debug=t?(...e)=>console.log(...e):()=>{}}normalize(e){this.firstItemDepthZero(e),this.depthMustBeSet(e),this.itemCannotHaveInvalidDepth(e),this.itemCannotExceedDepthLimit(e),this.parentMustBeLayout(e),this.parentCannotHaveExpandedAndCollapsedChildren(e)}update(e){this.rules={},this.parentsCannotDeNest(e),this.rootsCannotDeNest(e),this.nestingNeedsParent(e),this.nestingCannotExceedMaxDepth(e),this.leavesCannotCollapse(e),this.needHiddenItemsToExpand(e),this.parentsCannotBeDeleted(e),this.parentsCannotBeDragged(e),s.rules.forEach((t=>{e.toggleRule(t,!!this.rules[t])}))}firstItemDepthZero(e){0===e.index&&0!==e.depth&&(this.debug(`enforce depth on item ${e.index}: ${e.depth} => 0`),e.depth=0)}depthMustBeSet(e){(isNaN(e.depth)||e.depth<0)&&(this.debug(`unset depth on item ${e.index}: => 0`),e.depth=0)}itemCannotHaveInvalidDepth(e){const t=e.previousItem;t&&t.depth<e.depth-1&&(this.debug(`invalid depth on item ${e.index}: ${e.depth} => ${t.depth+1}`),e.depth=t.depth+1)}itemCannotExceedDepthLimit(e){this.maxDepth>0&&this.maxDepth<=e.depth&&(e.depth=this.maxDepth-1)}parentMustBeLayout(e){const t=e.previousItem;t&&t.depth<e.depth&&!t.isLayout&&(this.debug(`invalid parent for item ${e.index}: ${e.depth} => ${t.depth}`),e.depth=t.depth)}parentCannotHaveExpandedAndCollapsedChildren(e){e.hasCollapsedDescendants()&&e.hasExpandedDescendants()&&(this.debug(`expanding collapsed children of item ${e.index}`),e.expand())}parentsCannotDeNest(e){e.hasExpandedDescendants()&&this.#r("denyDeNest")}rootsCannotDeNest(e){0===e.depth&&this.#r("denyDeNest")}leavesCannotCollapse(e){e.hasExpandedDescendants()||this.#r("denyCollapse")}needHiddenItemsToExpand(e){e.hasCollapsedDescendants()||this.#r("denyExpand")}nestingNeedsParent(e){const t=e.previousItem;t?t.depth<e.depth?this.#r("denyNest"):t.depth!==e.depth||t.isLayout||this.#r("denyNest"):this.#r("denyNest")}nestingCannotExceedMaxDepth(e){this.maxDepth>0&&this.maxDepth<=e.depth+1&&this.#r("denyNest")}parentsCannotBeDeleted(e){e.itemId&&!e.hasExpandedDescendants()||this.#r("denyRemove")}parentsCannotBeDragged(e){e.hasExpandedDescendants()&&this.#r("denyDrag")}#r(e){this.rules[e]=!0}}function a(e){return new t(e.target.closest("[data-navigation-item]"))}function i(e,t){if(e&&e!==t){if("LI"===e.nodeName){const n=e.compareDocumentPosition(t);n&Node.DOCUMENT_POSITION_FOLLOWING?e.insertAdjacentElement("beforebegin",t):n&Node.DOCUMENT_POSITION_PRECEDING&&e.insertAdjacentElement("afterend",t)}"OL"===e.nodeName&&e.appendChild(t)}}
|
1
|
+
import{Controller as e}from"@hotwired/stimulus";class t{static comparator(e,t){return e.index-t.index}constructor(e){this.node=e}get itemId(){return this.node.dataset.navigationItemId}get#e(){return this.node.querySelector('input[name$="[id]"]')}set itemId(e){this.itemId!==e&&(this.node.dataset.navigationItemId=`${e}`,this.#e.value=`${e}`)}get depth(){return parseInt(this.node.dataset.navigationDepth)||0}get#t(){return this.node.querySelector('input[name$="[depth]"]')}set depth(e){this.depth!==e&&(this.node.dataset.navigationDepth=`${e}`,this.#t.value=`${e}`)}get index(){return parseInt(this.node.dataset.navigationIndex)}get#n(){return this.node.querySelector('input[name$="[index]"]')}set index(e){this.index!==e&&(this.node.dataset.navigationIndex=`${e}`,this.#n.value=`${e}`)}get isLayout(){return this.node.hasAttribute("data-content-layout")}get previousItem(){let e=this.node.previousElementSibling;if(e)return new t(e)}get nextItem(){let e=this.node.nextElementSibling;if(e)return new t(e)}hasCollapsedDescendants(){let e=this.#s;return!!e&&e.children.length>0}hasExpandedDescendants(){let e=this.nextItem;return!!e&&e.depth>this.depth}traverse(e){const t=this.#a;e(this),this.#i(e),t.forEach((t=>t.#i(e)))}#i(e){this.hasCollapsedDescendants()&&this.#d.forEach((t=>{e(t),t.#i(e)}))}collapse(){let e=this.#s;e||(e=function(e){const t=document.createElement("ol");return t.setAttribute("class","hidden"),t.dataset.navigationChildren="",e.appendChild(t),t}(this.node)),this.#a.forEach((t=>e.appendChild(t.node)))}expand(){this.hasCollapsedDescendants()&&Array.from(this.#s.children).reverse().forEach((e=>{this.node.insertAdjacentElement("afterend",e)}))}toggleRule(e,t=!1){this.node.dataset.hasOwnProperty(e)&&!t&&delete this.node.dataset[e],!this.node.dataset.hasOwnProperty(e)&&t&&(this.node.dataset[e]=""),"denyDrag"===e&&(this.node.hasAttribute("draggable")||t||this.node.setAttribute("draggable","true"),this.node.hasAttribute("draggable")&&t&&this.node.removeAttribute("draggable"))}hasItemIdChanged(){return!(this.#e.value===this.itemId)}updateAfterChange(){this.itemId=this.#e.value,this.#n.value=this.index,this.#t.value=this.depth}get#s(){return this.node.querySelector(":scope > [data-navigation-children]")}get#a(){const e=[];let t=this.nextItem;for(;t&&t.depth>this.depth;)e.push(t),t=t.nextItem;return e}get#d(){return this.hasCollapsedDescendants()?Array.from(this.#s.children).map((e=>new t(e))):[]}}class n{constructor(e){this.node=e}get items(){return e=this.node.querySelectorAll("[data-navigation-index]"),Array.from(e).map((e=>new t(e)));var e}get state(){const e=this.node.querySelectorAll("li input[type=hidden]");return Array.from(e).map((e=>e.value)).join("/")}reindex(){this.items.map(((e,t)=>e.index=t))}reset(){this.items.sort(t.comparator).forEach((e=>{this.node.appendChild(e.node)}))}}class s{static rules=["denyDeNest","denyNest","denyCollapse","denyExpand","denyRemove","denyDrag","denyEdit"];constructor(e=null,t=!1){this.maxDepth=e,this.debug=t?(...e)=>console.log(...e):()=>{}}normalize(e){this.firstItemDepthZero(e),this.depthMustBeSet(e),this.itemCannotHaveInvalidDepth(e),this.itemCannotExceedDepthLimit(e),this.parentMustBeLayout(e),this.parentCannotHaveExpandedAndCollapsedChildren(e)}update(e){this.rules={},this.parentsCannotDeNest(e),this.rootsCannotDeNest(e),this.nestingNeedsParent(e),this.nestingCannotExceedMaxDepth(e),this.leavesCannotCollapse(e),this.needHiddenItemsToExpand(e),this.parentsCannotBeDeleted(e),this.parentsCannotBeDragged(e),s.rules.forEach((t=>{e.toggleRule(t,!!this.rules[t])}))}firstItemDepthZero(e){0===e.index&&0!==e.depth&&(this.debug(`enforce depth on item ${e.index}: ${e.depth} => 0`),e.depth=0)}depthMustBeSet(e){(isNaN(e.depth)||e.depth<0)&&(this.debug(`unset depth on item ${e.index}: => 0`),e.depth=0)}itemCannotHaveInvalidDepth(e){const t=e.previousItem;t&&t.depth<e.depth-1&&(this.debug(`invalid depth on item ${e.index}: ${e.depth} => ${t.depth+1}`),e.depth=t.depth+1)}itemCannotExceedDepthLimit(e){this.maxDepth>0&&this.maxDepth<=e.depth&&(e.depth=this.maxDepth-1)}parentMustBeLayout(e){const t=e.previousItem;t&&t.depth<e.depth&&!t.isLayout&&(this.debug(`invalid parent for item ${e.index}: ${e.depth} => ${t.depth}`),e.depth=t.depth)}parentCannotHaveExpandedAndCollapsedChildren(e){e.hasCollapsedDescendants()&&e.hasExpandedDescendants()&&(this.debug(`expanding collapsed children of item ${e.index}`),e.expand())}parentsCannotDeNest(e){e.hasExpandedDescendants()&&this.#r("denyDeNest")}rootsCannotDeNest(e){0===e.depth&&this.#r("denyDeNest")}leavesCannotCollapse(e){e.hasExpandedDescendants()||this.#r("denyCollapse")}needHiddenItemsToExpand(e){e.hasCollapsedDescendants()||this.#r("denyExpand")}nestingNeedsParent(e){const t=e.previousItem;t?t.depth<e.depth?this.#r("denyNest"):t.depth!==e.depth||t.isLayout||this.#r("denyNest"):this.#r("denyNest")}nestingCannotExceedMaxDepth(e){this.maxDepth>0&&this.maxDepth<=e.depth+1&&this.#r("denyNest")}parentsCannotBeDeleted(e){e.itemId&&!e.hasExpandedDescendants()||this.#r("denyRemove")}parentsCannotBeDragged(e){e.hasExpandedDescendants()&&this.#r("denyDrag")}#r(e){this.rules[e]=!0}}function a(e){return new t(e.target.closest("[data-navigation-item]"))}function i(e,t){if(e&&e!==t){if("LI"===e.nodeName){const n=e.compareDocumentPosition(t);n&Node.DOCUMENT_POSITION_FOLLOWING?e.insertAdjacentElement("beforebegin",t):n&Node.DOCUMENT_POSITION_PRECEDING&&e.insertAdjacentElement("afterend",t)}"OL"===e.nodeName&&e.appendChild(t)}}function d(e){return e&&(e.closest("[data-controller='navigation--editor--list'] > *")||e.closest("[data-controller='navigation--editor--list']"))}const r=[{identifier:"navigation--editor--menu",controllerConstructor:class extends e{static targets=["menu"];static values={maxDepth:Number};connect(){this.state=this.menu.state,this.reindex()}get menu(){return new n(this.menuTarget)}reindex(){this.menu.reindex(),this.#h()}reset(){this.menu.reset()}drop(e){this.menu.reindex();const t=a(e),n=t.previousItem;let s=0;s=void 0===n?-t.depth:t.nextItem&&t.nextItem.depth>n.depth?n.depth-t.depth+1:n.depth-t.depth,t.traverse((e=>{e.depth+=s})),this.#h(),e.preventDefault()}remove(e){a(e).node.remove(),this.#h(),e.preventDefault()}nest(e){a(e).traverse((e=>{e.depth+=1})),this.#h(),e.preventDefault()}deNest(e){a(e).traverse((e=>{e.depth-=1})),this.#h(),e.preventDefault()}collapse(e){a(e).collapse(),this.#h(),e.preventDefault()}expand(e){a(e).expand(),this.#h(),e.preventDefault()}#h(){this.updateRequested=!0,setTimeout((()=>{if(!this.updateRequested)return;this.updateRequested=!1;const e=new s(this.maxDepthValue);this.menu.items.forEach((t=>e.normalize(t))),this.menu.items.forEach((t=>e.update(t))),this.#o()}),0)}#o(){this.dispatch("change",{bubbles:!0,prefix:"navigation",detail:{dirty:this.#l()}})}#l(){return this.menu.state!==this.state}}},{identifier:"navigation--editor--item",controllerConstructor:class extends e{get item(){return new t(this.li)}get ol(){return this.element.closest("ol")}get li(){return this.element.closest("li")}connect(){this.element.dataset.hasOwnProperty("delete")?this.remove():this.item.index>=0?this.item.hasItemIdChanged()&&(this.item.updateAfterChange(),this.reindex()):this.reindex()}remove(){this.ol,this.li.remove(),this.reindex()}reindex(){this.dispatch("reindex",{bubbles:!0,prefix:"navigation"})}}},{identifier:"navigation--editor--list",controllerConstructor:class extends e{connect(){this.enterCount=0}dragstart(e){if(this.element!==e.target.parentElement)return;const t=e.target;e.dataTransfer.effectAllowed="move",requestAnimationFrame((()=>t.dataset.dragging=""))}dragover(e){const t=this.dragItem;if(t)return i(d(e.target),t),e.preventDefault(),!0}dragenter(e){if(e.preventDefault(),this.enterCount++,function(e){return"copy"===e.dataTransfer.effectAllowed||"copyMove"===e.dataTransfer.effectAllowed}(e)&&!this.dragItem){const e=document.createElement("li");e.dataset.dragging="",e.dataset.newItem="",this.element.appendChild(e)}}dragleave(e){this.enterCount--,this.enterCount<=0&&this.dragItem.dataset.hasOwnProperty("newItem")&&this.cancelDrag(e)}drop(e){let t=this.dragItem;if(t){if(e.preventDefault(),delete t.dataset.dragging,i(d(e.target),t),t.dataset.hasOwnProperty("newItem")){const n=t,s=document.createElement("template");s.innerHTML=e.dataTransfer.getData("text/html"),t=s.content.querySelector("li"),this.element.replaceChild(t,n),requestAnimationFrame((()=>t.querySelector("[role='button'][value='edit']").click()))}this.dispatch("drop",{target:t,bubbles:!0,prefix:"navigation"})}}dragend(){const e=this.dragItem;e&&(e.dataset.hasOwnProperty("newItem")?e.remove():(delete e.dataset.dragging,this.reset()))}get isDragging(){return!!this.dragItem}get dragItem(){return this.element.querySelector("[data-dragging]")}reindex(){this.dispatch("reindex",{bubbles:!0,prefix:"navigation"})}reset(){this.dispatch("reset",{bubbles:!0,prefix:"navigation"})}}},{identifier:"navigation--editor--new-item",controllerConstructor:class extends e{static targets=["template"];dragstart(e){this.element===e.target&&(e.dataTransfer.setData("text/html",this.templateTarget.innerHTML),e.dataTransfer.effectAllowed="copy")}}},{identifier:"navigation--editor--status-bar",controllerConstructor:class extends e{connect(){this.versionState=this.element.dataset.state}change(e){e.detail&&e.detail.hasOwnProperty("dirty")&&this.update(e.detail)}update({dirty:e}){this.element.dataset.state=e?"dirty":this.versionState}}}];export{r as default};
|
2
2
|
//# sourceMappingURL=navigation.min.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"navigation.min.js","sources":["../../../javascript/navigation/editor/item.js","../../../javascript/navigation/editor/menu.js","../../../javascript/navigation/editor/rules-engine.js","../../../javascript/navigation/editor/menu_controller.js","../../../javascript/navigation/editor/list_controller.js","../../../javascript/navigation/application.js","../../../javascript/navigation/editor/item_controller.js","../../../javascript/navigation/editor/new_item_controller.js","../../../javascript/navigation/editor/status_bar_controller.js"],"sourcesContent":["export default class Item {\n /**\n * Sort items by their index.\n *\n * @param a {Item}\n * @param b {Item}\n * @returns {number}\n */\n static comparator(a, b) {\n return a.index - b.index;\n }\n\n /**\n * @param node {Element} li[data-navigation-index]\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @returns {String} id of the node's item (from data attributes)\n */\n get itemId() {\n return this.node.dataset[`navigationItemId`];\n }\n\n get #itemIdInput() {\n return this.node.querySelector(`input[name$=\"[id]\"]`);\n }\n\n /**\n * @param itemId {String} id\n */\n set itemId(id) {\n if (this.itemId === id) return;\n\n this.node.dataset[`navigationItemId`] = `${id}`;\n this.#itemIdInput.value = `${id}`;\n }\n\n /**\n * @returns {number} logical nesting depth of node in menu\n */\n get depth() {\n return parseInt(this.node.dataset[`navigationDepth`]) || 0;\n }\n\n get #depthInput() {\n return this.node.querySelector(`input[name$=\"[depth]\"]`);\n }\n\n /**\n * @param depth {number} depth >= 0\n */\n set depth(depth) {\n if (this.depth === depth) return;\n\n this.node.dataset[`navigationDepth`] = `${depth}`;\n this.#depthInput.value = `${depth}`;\n }\n\n /**\n * @returns {number} logical index of node in menu (pre-order traversal)\n */\n get index() {\n return parseInt(this.node.dataset[`navigationIndex`]);\n }\n\n get #indexInput() {\n return this.node.querySelector(`input[name$=\"[index]\"]`);\n }\n\n /**\n * @param index {number} index >= 0\n */\n set index(index) {\n if (this.index === index) return;\n\n this.node.dataset[`navigationIndex`] = `${index}`;\n this.#indexInput.value = `${index}`;\n }\n\n /**\n * @returns {boolean} true if this item can have children\n */\n get isLayout() {\n return this.node.hasAttribute(\"data-content-layout\");\n }\n\n /**\n * @returns {Item} nearest neighbour (index - 1)\n */\n get previousItem() {\n let sibling = this.node.previousElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {Item} nearest neighbour (index + 1)\n */\n get nextItem() {\n let sibling = this.node.nextElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {boolean} true if this item has any collapsed children\n */\n hasCollapsedDescendants() {\n let childrenList = this.#childrenListElement;\n return !!childrenList && childrenList.children.length > 0;\n }\n\n /**\n * @returns {boolean} true if this item has any expanded children\n */\n hasExpandedDescendants() {\n let sibling = this.nextItem;\n return !!sibling && sibling.depth > this.depth;\n }\n\n /**\n * Recursively traverse the node and its descendants.\n *\n * @callback {Item}\n */\n traverse(callback) {\n // capture descendants before traversal in case of side-effects\n // specifically, setting depth affects calculation\n const expanded = this.#expandedDescendants;\n\n callback(this);\n this.#traverseCollapsed(callback);\n expanded.forEach((item) => item.#traverseCollapsed(callback));\n }\n\n /**\n * Recursively traverse the node's collapsed descendants, if any.\n *\n * @callback {Item}\n */\n #traverseCollapsed(callback) {\n if (!this.hasCollapsedDescendants()) return;\n\n this.#collapsedDescendants.forEach((item) => {\n callback(item);\n item.#traverseCollapsed(callback);\n });\n }\n\n /**\n * Collapses visible (logical) children into this element's hidden children\n * list, creating it if it doesn't already exist.\n */\n collapse() {\n let listElement = this.#childrenListElement;\n\n if (!listElement) listElement = createChildrenList(this.node);\n\n this.#expandedDescendants.forEach((child) =>\n listElement.appendChild(child.node)\n );\n }\n\n /**\n * Moves any collapsed children back into the parent menu.\n */\n expand() {\n if (!this.hasCollapsedDescendants()) return;\n\n Array.from(this.#childrenListElement.children)\n .reverse()\n .forEach((node) => {\n this.node.insertAdjacentElement(\"afterend\", node);\n });\n }\n\n /**\n * Sets the state of a given rule on the target node.\n *\n * @param rule {String}\n * @param deny {boolean}\n */\n toggleRule(rule, deny = false) {\n if (this.node.dataset.hasOwnProperty(rule) && !deny) {\n delete this.node.dataset[rule];\n }\n if (!this.node.dataset.hasOwnProperty(rule) && deny) {\n this.node.dataset[rule] = \"\";\n }\n\n if (rule === \"denyDrag\") {\n if (!this.node.hasAttribute(\"draggable\") && !deny) {\n this.node.setAttribute(\"draggable\", \"true\");\n }\n if (this.node.hasAttribute(\"draggable\") && deny) {\n this.node.removeAttribute(\"draggable\");\n }\n }\n }\n\n /**\n * Detects turbo item changes by comparing the dataset id with the input\n */\n hasItemIdChanged() {\n return !(this.#itemIdInput.value === this.itemId);\n }\n\n /**\n * Updates inputs, in case they don't match the data values, e.g., when the\n * nested inputs have been hot-swapped by turbo with data from the server.\n *\n * Updates itemId from input as that is the canonical source.\n */\n updateAfterChange() {\n this.itemId = this.#itemIdInput.value;\n this.#indexInput.value = this.index;\n this.#depthInput.value = this.depth;\n }\n\n /**\n * Finds the dom container for storing collapsed (hidden) children, if present.\n *\n * @returns {Element} ol[data-navigation-children]\n */\n get #childrenListElement() {\n return this.node.querySelector(`:scope > [data-navigation-children]`);\n }\n\n /**\n * @returns {Item[]} all items that follow this element that have a greater depth.\n */\n get #expandedDescendants() {\n const descendants = [];\n\n let sibling = this.nextItem;\n while (sibling && sibling.depth > this.depth) {\n descendants.push(sibling);\n sibling = sibling.nextItem;\n }\n\n return descendants;\n }\n\n /**\n * @returns {Item[]} all items directly contained inside this element's hidden children element.\n */\n get #collapsedDescendants() {\n if (!this.hasCollapsedDescendants()) return [];\n\n return Array.from(this.#childrenListElement.children).map(\n (node) => new Item(node)\n );\n }\n}\n\n/**\n * Finds or creates a dom container for storing collapsed (hidden) children.\n *\n * @param node {Element} li[data-navigation-index]\n * @returns {Element} ol[data-navigation-children]\n */\nfunction createChildrenList(node) {\n const childrenList = document.createElement(\"ol\");\n childrenList.setAttribute(\"class\", \"hidden\");\n\n childrenList.dataset[`navigationChildren`] = \"\";\n\n node.appendChild(childrenList);\n\n return childrenList;\n}\n","import Item from \"./item\";\n\n/**\n * @param nodes {NodeList}\n * @returns {Item[]}\n */\nfunction createItemList(nodes) {\n return Array.from(nodes).map((node) => new Item(node));\n}\n\nexport default class Menu {\n /**\n * @param node {Element} navigation editor list\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @return {Item[]} an ordered list of all items in the menu\n */\n get items() {\n return createItemList(\n this.node.querySelectorAll(\"[data-navigation-index]\")\n );\n }\n\n /**\n * @return {String} a serialized description of the structure of the menu\n */\n get state() {\n const inputs = this.node.querySelectorAll(\"li input[type=hidden]\");\n return Array.from(inputs)\n .map((e) => e.value)\n .join(\"/\");\n }\n\n /**\n * Set the index of items based on their current position.\n */\n reindex() {\n this.items.map((item, index) => (item.index = index));\n }\n\n /**\n * Resets the order of items to their defined index.\n * Useful after an aborted drag.\n */\n reset() {\n this.items.sort(Item.comparator).forEach((item) => {\n this.node.appendChild(item.node);\n });\n }\n}\n","export default class RulesEngine {\n static rules = [\n \"denyDeNest\",\n \"denyNest\",\n \"denyCollapse\",\n \"denyExpand\",\n \"denyRemove\",\n \"denyDrag\",\n \"denyEdit\",\n ];\n\n constructor(maxDepth = null, debug = false) {\n this.maxDepth = maxDepth;\n if (debug) {\n this.debug = (...args) => console.log(...args);\n } else {\n this.debug = () => {};\n }\n }\n\n /**\n * Enforce structural rules to ensure that the given item is currently in a\n * valid state.\n *\n * @param {Item} item\n */\n normalize(item) {\n // structural rules enforce a valid tree structure\n this.firstItemDepthZero(item);\n this.depthMustBeSet(item);\n this.itemCannotHaveInvalidDepth(item);\n this.itemCannotExceedDepthLimit(item);\n this.parentMustBeLayout(item);\n this.parentCannotHaveExpandedAndCollapsedChildren(item);\n }\n\n /**\n * Apply rules to the given item to determine what operations are permitted.\n *\n * @param {Item} item\n */\n update(item) {\n this.rules = {};\n\n // behavioural rules define what the user is allowed to do\n this.parentsCannotDeNest(item);\n this.rootsCannotDeNest(item);\n this.nestingNeedsParent(item);\n this.nestingCannotExceedMaxDepth(item);\n this.leavesCannotCollapse(item);\n this.needHiddenItemsToExpand(item);\n this.parentsCannotBeDeleted(item);\n this.parentsCannotBeDragged(item);\n\n RulesEngine.rules.forEach((rule) => {\n item.toggleRule(rule, !!this.rules[rule]);\n });\n }\n\n /**\n * First item can't have a parent, so its depth should always be 0\n */\n firstItemDepthZero(item) {\n if (item.index === 0 && item.depth !== 0) {\n this.debug(`enforce depth on item ${item.index}: ${item.depth} => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Every item should have a non-negative depth set.\n *\n * @param {Item} item\n */\n depthMustBeSet(item) {\n if (isNaN(item.depth) || item.depth < 0) {\n this.debug(`unset depth on item ${item.index}: => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Depth must increase stepwise.\n *\n * @param {Item} item\n */\n itemCannotHaveInvalidDepth(item) {\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth - 1) {\n this.debug(\n `invalid depth on item ${item.index}: ${item.depth} => ${\n previous.depth + 1\n }`\n );\n\n item.depth = previous.depth + 1;\n }\n }\n\n /**\n * Depth must not exceed menu's depth limit.\n *\n * @param {Item} item\n */\n itemCannotExceedDepthLimit(item) {\n if (this.maxDepth > 0 && this.maxDepth <= item.depth) {\n // Note: this change can cause an issue where the previous item is treated\n // like a parent even though it no longer has children. This is because\n // items are processed in order. This issue does not seem worth solving\n // as it only occurs if the max depth is altered. The issue can be worked\n // around by saving the menu.\n item.depth = this.maxDepth - 1;\n }\n }\n\n /**\n * Parent item, if any, must be a layout.\n *\n * @param {Item} item\n */\n parentMustBeLayout(item) {\n // if we're the first child, make sure our parent is a layout\n // if we're a sibling, we know the previous item is valid so we must be too\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth && !previous.isLayout) {\n this.debug(\n `invalid parent for item ${item.index}: ${item.depth} => ${previous.depth}`\n );\n\n item.depth = previous.depth;\n }\n }\n\n /**\n * If a parent has expanded and collapsed children, expand.\n *\n * @param {Item} item\n */\n parentCannotHaveExpandedAndCollapsedChildren(item) {\n if (item.hasCollapsedDescendants() && item.hasExpandedDescendants()) {\n this.debug(`expanding collapsed children of item ${item.index}`);\n\n item.expand();\n }\n }\n\n /**\n * De-nesting an item would create a gap of 2 between itself and its children\n *\n * @param {Item} item\n */\n parentsCannotDeNest(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDeNest\");\n }\n\n /**\n * Item depth can't go below 0.\n *\n * @param {Item} item\n */\n rootsCannotDeNest(item) {\n if (item.depth === 0) this.#deny(\"denyDeNest\");\n }\n\n /**\n * If an item doesn't have children it can't be collapsed.\n *\n * @param {Item} item\n */\n leavesCannotCollapse(item) {\n if (!item.hasExpandedDescendants()) this.#deny(\"denyCollapse\");\n }\n\n /**\n * If an item doesn't have any hidden descendants then it can't be expanded.\n *\n * @param {Item} item\n */\n needHiddenItemsToExpand(item) {\n if (!item.hasCollapsedDescendants()) this.#deny(\"denyExpand\");\n }\n\n /**\n * An item can't be nested (indented) if it doesn't have a valid parent.\n *\n * @param {Item} item\n */\n nestingNeedsParent(item) {\n const previous = item.previousItem;\n // no previous, so cannot nest\n if (!previous) this.#deny(\"denyNest\");\n // previous is too shallow, nesting would increase depth too much\n else if (previous.depth < item.depth) this.#deny(\"denyNest\");\n // new parent is not a layout\n else if (previous.depth === item.depth && !previous.isLayout)\n this.#deny(\"denyNest\");\n }\n\n /**\n * An item can't be nested (indented) if doing so would exceed the max depth.\n *\n * @param {Item} item\n */\n nestingCannotExceedMaxDepth(item) {\n if (this.maxDepth > 0 && this.maxDepth <= item.depth + 1) {\n this.#deny(\"denyNest\");\n }\n }\n\n /**\n * An item can't be deleted if it has visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDeleted(item) {\n if (!item.itemId || item.hasExpandedDescendants()) this.#deny(\"denyRemove\");\n }\n\n /**\n * Items cannot be dragged if they have visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDragged(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDrag\");\n }\n\n /**\n * Record a deny.\n *\n * @param rule {String}\n */\n #deny(rule) {\n this.rules[rule] = true;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport Item from \"./item\";\nimport Menu from \"./menu\";\nimport RulesEngine from \"./rules-engine\";\n\nexport default class MenuController extends Controller {\n static targets = [\"menu\"];\n static values = {\n maxDepth: Number,\n };\n\n connect() {\n this.state = this.menu.state;\n\n this.reindex();\n }\n\n get menu() {\n return new Menu(this.menuTarget);\n }\n\n reindex() {\n this.menu.reindex();\n this.#update();\n }\n\n reset() {\n this.menu.reset();\n }\n\n drop(event) {\n this.menu.reindex(); // set indexes before calculating previous\n\n const item = getEventItem(event);\n const previous = item.previousItem;\n\n let delta = 0;\n if (previous === undefined) {\n // if previous does not exist, set depth to 0\n delta = -item.depth;\n } else if (item.nextItem && item.nextItem.depth > previous.depth) {\n // if next is a child of previous, make item a child of previous\n delta = previous.depth - item.depth + 1;\n } else {\n // otherwise, make item a sibling of previous\n delta = previous.depth - item.depth;\n }\n\n item.traverse((child) => {\n child.depth += delta;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n remove(event) {\n const item = getEventItem(event);\n\n item.node.remove();\n\n this.#update();\n event.preventDefault();\n }\n\n nest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth += 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n deNest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth -= 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n collapse(event) {\n const item = getEventItem(event);\n\n item.collapse();\n\n this.#update();\n event.preventDefault();\n }\n\n expand(event) {\n const item = getEventItem(event);\n\n item.expand();\n\n this.#update();\n event.preventDefault();\n }\n\n /**\n * Re-apply rules to items to enable/disable appropriate actions.\n */\n #update() {\n // debounce requests to ensure that we only update once per tick\n this.updateRequested = true;\n setTimeout(() => {\n if (!this.updateRequested) return;\n\n this.updateRequested = false;\n const engine = new RulesEngine(this.maxDepthValue);\n this.menu.items.forEach((item) => engine.normalize(item));\n this.menu.items.forEach((item) => engine.update(item));\n\n this.#notifyChange();\n }, 0);\n }\n\n #notifyChange() {\n this.dispatch(\"change\", {\n bubbles: true,\n prefix: \"navigation\",\n detail: { dirty: this.#isDirty() },\n });\n }\n\n #isDirty() {\n return this.menu.state !== this.state;\n }\n}\n\nfunction getEventItem(event) {\n return new Item(event.target.closest(\"[data-navigation-item]\"));\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ListController extends Controller {\n dragstart(event) {\n if (this.element !== event.target.parentElement) return;\n\n const target = event.target;\n event.dataTransfer.effectAllowed = \"move\";\n\n // update element style after drag has begun\n setTimeout(() => (target.dataset.dragging = \"\"));\n }\n\n dragover(event) {\n const item = this.dragItem();\n if (!item) return;\n\n swap(this.dropTarget(event.target), item);\n\n event.preventDefault();\n return true;\n }\n\n dragenter(event) {\n event.preventDefault();\n\n if (event.dataTransfer.effectAllowed === \"copy\" && !this.dragItem()) {\n const item = document.createElement(\"li\");\n item.dataset.dragging = \"\";\n item.dataset.newItem = \"\";\n this.element.prepend(item);\n }\n }\n\n dragleave(event) {\n const item = this.dragItem();\n const related = this.dropTarget(event.relatedTarget);\n\n // ignore if item is not set or we're moving into a valid drop target\n if (!item || related) return;\n\n // remove item if it's a new item\n if (item.dataset.hasOwnProperty(\"newItem\")) {\n item.remove();\n }\n }\n\n drop(event) {\n let item = this.dragItem();\n\n if (!item) return;\n\n event.preventDefault();\n delete item.dataset.dragging;\n swap(this.dropTarget(event.target), item);\n\n if (item.dataset.hasOwnProperty(\"newItem\")) {\n const placeholder = item;\n const template = document.createElement(\"template\");\n template.innerHTML = event.dataTransfer.getData(\"text/html\");\n item = template.content.querySelector(\"li\");\n\n this.element.replaceChild(item, placeholder);\n setTimeout(() =>\n item.querySelector(\"[role='button'][value='edit']\").click()\n );\n }\n\n this.dispatch(\"drop\", {\n target: item,\n bubbles: true,\n prefix: \"navigation\",\n });\n }\n\n dragend() {\n const item = this.dragItem();\n if (!item) return;\n\n delete item.dataset.dragging;\n this.reset();\n }\n\n dragItem() {\n return this.element.querySelector(\"[data-dragging]\");\n }\n\n dropTarget(e) {\n return (\n e.closest(\"[data-controller='navigation--editor--list'] > *\") ||\n e.closest(\"[data-controller='navigation--editor--list']\")\n );\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"navigation\" });\n }\n\n reset() {\n this.dispatch(\"reset\", { bubbles: true, prefix: \"navigation\" });\n }\n}\n\nfunction swap(target, item) {\n if (!target) return;\n if (target === item) return;\n\n if (target.nodeName === \"LI\") {\n const positionComparison = target.compareDocumentPosition(item);\n if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {\n target.insertAdjacentElement(\"beforebegin\", item);\n } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {\n target.insertAdjacentElement(\"afterend\", item);\n }\n }\n\n if (target.nodeName === \"OL\") {\n target.appendChild(item);\n }\n}\n","import MenuController from \"./editor/menu_controller\";\nimport ItemController from \"./editor/item_controller\";\nimport ListController from \"./editor/list_controller\";\nimport NewItemController from \"./editor/new_item_controller\";\nimport StatusBarController from \"./editor/status_bar_controller\";\n\nconst Definitions = [\n {\n identifier: \"navigation--editor--menu\",\n controllerConstructor: MenuController,\n },\n {\n identifier: \"navigation--editor--item\",\n controllerConstructor: ItemController,\n },\n {\n identifier: \"navigation--editor--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"navigation--editor--new-item\",\n controllerConstructor: NewItemController,\n },\n {\n identifier: \"navigation--editor--status-bar\",\n controllerConstructor: StatusBarController,\n },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\nimport Item from \"./item\";\n\nexport default class ItemController extends Controller {\n get item() {\n return new Item(this.li);\n }\n\n get ol() {\n return this.element.closest(\"ol\");\n }\n\n get li() {\n return this.element.closest(\"li\");\n }\n\n connect() {\n if (this.element.dataset.hasOwnProperty(\"delete\")) {\n this.remove();\n }\n // if index is not already set, re-index will set it\n else if (!(this.item.index >= 0)) {\n this.reindex();\n }\n // if item has been replaced via turbo, re-index will run the rules engine\n // update our depth and index with values from the li's data attributes\n else if (this.item.hasItemIdChanged()) {\n this.item.updateAfterChange();\n this.reindex();\n }\n }\n\n remove() {\n // capture ol\n const ol = this.ol;\n // remove self from dom\n this.li.remove();\n // reindex ol\n this.reindex();\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"navigation\" });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class NewItemController extends Controller {\n static targets = [\"template\"];\n\n dragstart(event) {\n if (this.element !== event.target) return;\n\n event.dataTransfer.setData(\"text/html\", this.templateTarget.innerHTML);\n event.dataTransfer.effectAllowed = \"copy\";\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class StatusBarController extends Controller {\n connect() {\n // cache the version's state in the controller on connect\n this.versionState = this.element.dataset.state;\n }\n\n change(e) {\n if (e.detail && e.detail.hasOwnProperty(\"dirty\")) {\n this.update(e.detail);\n }\n }\n\n update({ dirty }) {\n if (dirty) {\n this.element.dataset.state = \"dirty\";\n } else {\n this.element.dataset.state = this.versionState;\n }\n }\n}\n"],"names":["Item","comparator","a","b","index","constructor","node","this","itemId","dataset","itemIdInput","querySelector","id","value","depth","parseInt","depthInput","indexInput","isLayout","hasAttribute","previousItem","sibling","previousElementSibling","nextItem","nextElementSibling","hasCollapsedDescendants","childrenList","childrenListElement","children","length","hasExpandedDescendants","traverse","callback","expanded","expandedDescendants","traverseCollapsed","forEach","item","collapsedDescendants","collapse","listElement","document","createElement","setAttribute","appendChild","createChildrenList","child","expand","Array","from","reverse","insertAdjacentElement","toggleRule","rule","deny","hasOwnProperty","removeAttribute","hasItemIdChanged","updateAfterChange","descendants","push","map","Menu","items","nodes","querySelectorAll","state","inputs","e","join","reindex","reset","sort","RulesEngine","static","maxDepth","debug","args","console","log","normalize","firstItemDepthZero","depthMustBeSet","itemCannotHaveInvalidDepth","itemCannotExceedDepthLimit","parentMustBeLayout","parentCannotHaveExpandedAndCollapsedChildren","update","rules","parentsCannotDeNest","rootsCannotDeNest","nestingNeedsParent","nestingCannotExceedMaxDepth","leavesCannotCollapse","needHiddenItemsToExpand","parentsCannotBeDeleted","parentsCannotBeDragged","isNaN","previous","getEventItem","event","target","closest","swap","nodeName","positionComparison","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","DOCUMENT_POSITION_PRECEDING","Definitions","identifier","controllerConstructor","Controller","Number","connect","menu","menuTarget","drop","delta","undefined","preventDefault","remove","nest","deNest","updateRequested","setTimeout","engine","maxDepthValue","notifyChange","dispatch","bubbles","prefix","detail","dirty","isDirty","li","ol","element","dragstart","parentElement","dataTransfer","effectAllowed","dragging","dragover","dragItem","dropTarget","dragenter","newItem","prepend","dragleave","related","relatedTarget","placeholder","template","innerHTML","getData","content","replaceChild","click","dragend","setData","templateTarget","versionState","change"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"navigation.min.js","sources":["../../../javascript/navigation/editor/item.js","../../../javascript/navigation/editor/menu.js","../../../javascript/navigation/editor/rules-engine.js","../../../javascript/navigation/editor/menu_controller.js","../../../javascript/navigation/editor/list_controller.js","../../../javascript/navigation/application.js","../../../javascript/navigation/editor/item_controller.js","../../../javascript/navigation/editor/new_item_controller.js","../../../javascript/navigation/editor/status_bar_controller.js"],"sourcesContent":["export default class Item {\n /**\n * Sort items by their index.\n *\n * @param a {Item}\n * @param b {Item}\n * @returns {number}\n */\n static comparator(a, b) {\n return a.index - b.index;\n }\n\n /**\n * @param node {Element} li[data-navigation-index]\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @returns {String} id of the node's item (from data attributes)\n */\n get itemId() {\n return this.node.dataset[`navigationItemId`];\n }\n\n get #itemIdInput() {\n return this.node.querySelector(`input[name$=\"[id]\"]`);\n }\n\n /**\n * @param itemId {String} id\n */\n set itemId(id) {\n if (this.itemId === id) return;\n\n this.node.dataset[`navigationItemId`] = `${id}`;\n this.#itemIdInput.value = `${id}`;\n }\n\n /**\n * @returns {number} logical nesting depth of node in menu\n */\n get depth() {\n return parseInt(this.node.dataset[`navigationDepth`]) || 0;\n }\n\n get #depthInput() {\n return this.node.querySelector(`input[name$=\"[depth]\"]`);\n }\n\n /**\n * @param depth {number} depth >= 0\n */\n set depth(depth) {\n if (this.depth === depth) return;\n\n this.node.dataset[`navigationDepth`] = `${depth}`;\n this.#depthInput.value = `${depth}`;\n }\n\n /**\n * @returns {number} logical index of node in menu (pre-order traversal)\n */\n get index() {\n return parseInt(this.node.dataset[`navigationIndex`]);\n }\n\n get #indexInput() {\n return this.node.querySelector(`input[name$=\"[index]\"]`);\n }\n\n /**\n * @param index {number} index >= 0\n */\n set index(index) {\n if (this.index === index) return;\n\n this.node.dataset[`navigationIndex`] = `${index}`;\n this.#indexInput.value = `${index}`;\n }\n\n /**\n * @returns {boolean} true if this item can have children\n */\n get isLayout() {\n return this.node.hasAttribute(\"data-content-layout\");\n }\n\n /**\n * @returns {Item} nearest neighbour (index - 1)\n */\n get previousItem() {\n let sibling = this.node.previousElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {Item} nearest neighbour (index + 1)\n */\n get nextItem() {\n let sibling = this.node.nextElementSibling;\n if (sibling) return new Item(sibling);\n }\n\n /**\n * @returns {boolean} true if this item has any collapsed children\n */\n hasCollapsedDescendants() {\n let childrenList = this.#childrenListElement;\n return !!childrenList && childrenList.children.length > 0;\n }\n\n /**\n * @returns {boolean} true if this item has any expanded children\n */\n hasExpandedDescendants() {\n let sibling = this.nextItem;\n return !!sibling && sibling.depth > this.depth;\n }\n\n /**\n * Recursively traverse the node and its descendants.\n *\n * @callback {Item}\n */\n traverse(callback) {\n // capture descendants before traversal in case of side-effects\n // specifically, setting depth affects calculation\n const expanded = this.#expandedDescendants;\n\n callback(this);\n this.#traverseCollapsed(callback);\n expanded.forEach((item) => item.#traverseCollapsed(callback));\n }\n\n /**\n * Recursively traverse the node's collapsed descendants, if any.\n *\n * @callback {Item}\n */\n #traverseCollapsed(callback) {\n if (!this.hasCollapsedDescendants()) return;\n\n this.#collapsedDescendants.forEach((item) => {\n callback(item);\n item.#traverseCollapsed(callback);\n });\n }\n\n /**\n * Collapses visible (logical) children into this element's hidden children\n * list, creating it if it doesn't already exist.\n */\n collapse() {\n let listElement = this.#childrenListElement;\n\n if (!listElement) listElement = createChildrenList(this.node);\n\n this.#expandedDescendants.forEach((child) =>\n listElement.appendChild(child.node)\n );\n }\n\n /**\n * Moves any collapsed children back into the parent menu.\n */\n expand() {\n if (!this.hasCollapsedDescendants()) return;\n\n Array.from(this.#childrenListElement.children)\n .reverse()\n .forEach((node) => {\n this.node.insertAdjacentElement(\"afterend\", node);\n });\n }\n\n /**\n * Sets the state of a given rule on the target node.\n *\n * @param rule {String}\n * @param deny {boolean}\n */\n toggleRule(rule, deny = false) {\n if (this.node.dataset.hasOwnProperty(rule) && !deny) {\n delete this.node.dataset[rule];\n }\n if (!this.node.dataset.hasOwnProperty(rule) && deny) {\n this.node.dataset[rule] = \"\";\n }\n\n if (rule === \"denyDrag\") {\n if (!this.node.hasAttribute(\"draggable\") && !deny) {\n this.node.setAttribute(\"draggable\", \"true\");\n }\n if (this.node.hasAttribute(\"draggable\") && deny) {\n this.node.removeAttribute(\"draggable\");\n }\n }\n }\n\n /**\n * Detects turbo item changes by comparing the dataset id with the input\n */\n hasItemIdChanged() {\n return !(this.#itemIdInput.value === this.itemId);\n }\n\n /**\n * Updates inputs, in case they don't match the data values, e.g., when the\n * nested inputs have been hot-swapped by turbo with data from the server.\n *\n * Updates itemId from input as that is the canonical source.\n */\n updateAfterChange() {\n this.itemId = this.#itemIdInput.value;\n this.#indexInput.value = this.index;\n this.#depthInput.value = this.depth;\n }\n\n /**\n * Finds the dom container for storing collapsed (hidden) children, if present.\n *\n * @returns {Element} ol[data-navigation-children]\n */\n get #childrenListElement() {\n return this.node.querySelector(`:scope > [data-navigation-children]`);\n }\n\n /**\n * @returns {Item[]} all items that follow this element that have a greater depth.\n */\n get #expandedDescendants() {\n const descendants = [];\n\n let sibling = this.nextItem;\n while (sibling && sibling.depth > this.depth) {\n descendants.push(sibling);\n sibling = sibling.nextItem;\n }\n\n return descendants;\n }\n\n /**\n * @returns {Item[]} all items directly contained inside this element's hidden children element.\n */\n get #collapsedDescendants() {\n if (!this.hasCollapsedDescendants()) return [];\n\n return Array.from(this.#childrenListElement.children).map(\n (node) => new Item(node)\n );\n }\n}\n\n/**\n * Finds or creates a dom container for storing collapsed (hidden) children.\n *\n * @param node {Element} li[data-navigation-index]\n * @returns {Element} ol[data-navigation-children]\n */\nfunction createChildrenList(node) {\n const childrenList = document.createElement(\"ol\");\n childrenList.setAttribute(\"class\", \"hidden\");\n\n childrenList.dataset[`navigationChildren`] = \"\";\n\n node.appendChild(childrenList);\n\n return childrenList;\n}\n","import Item from \"./item\";\n\n/**\n * @param nodes {NodeList}\n * @returns {Item[]}\n */\nfunction createItemList(nodes) {\n return Array.from(nodes).map((node) => new Item(node));\n}\n\nexport default class Menu {\n /**\n * @param node {Element} navigation editor list\n */\n constructor(node) {\n this.node = node;\n }\n\n /**\n * @return {Item[]} an ordered list of all items in the menu\n */\n get items() {\n return createItemList(\n this.node.querySelectorAll(\"[data-navigation-index]\")\n );\n }\n\n /**\n * @return {String} a serialized description of the structure of the menu\n */\n get state() {\n const inputs = this.node.querySelectorAll(\"li input[type=hidden]\");\n return Array.from(inputs)\n .map((e) => e.value)\n .join(\"/\");\n }\n\n /**\n * Set the index of items based on their current position.\n */\n reindex() {\n this.items.map((item, index) => (item.index = index));\n }\n\n /**\n * Resets the order of items to their defined index.\n * Useful after an aborted drag.\n */\n reset() {\n this.items.sort(Item.comparator).forEach((item) => {\n this.node.appendChild(item.node);\n });\n }\n}\n","export default class RulesEngine {\n static rules = [\n \"denyDeNest\",\n \"denyNest\",\n \"denyCollapse\",\n \"denyExpand\",\n \"denyRemove\",\n \"denyDrag\",\n \"denyEdit\",\n ];\n\n constructor(maxDepth = null, debug = false) {\n this.maxDepth = maxDepth;\n if (debug) {\n this.debug = (...args) => console.log(...args);\n } else {\n this.debug = () => {};\n }\n }\n\n /**\n * Enforce structural rules to ensure that the given item is currently in a\n * valid state.\n *\n * @param {Item} item\n */\n normalize(item) {\n // structural rules enforce a valid tree structure\n this.firstItemDepthZero(item);\n this.depthMustBeSet(item);\n this.itemCannotHaveInvalidDepth(item);\n this.itemCannotExceedDepthLimit(item);\n this.parentMustBeLayout(item);\n this.parentCannotHaveExpandedAndCollapsedChildren(item);\n }\n\n /**\n * Apply rules to the given item to determine what operations are permitted.\n *\n * @param {Item} item\n */\n update(item) {\n this.rules = {};\n\n // behavioural rules define what the user is allowed to do\n this.parentsCannotDeNest(item);\n this.rootsCannotDeNest(item);\n this.nestingNeedsParent(item);\n this.nestingCannotExceedMaxDepth(item);\n this.leavesCannotCollapse(item);\n this.needHiddenItemsToExpand(item);\n this.parentsCannotBeDeleted(item);\n this.parentsCannotBeDragged(item);\n\n RulesEngine.rules.forEach((rule) => {\n item.toggleRule(rule, !!this.rules[rule]);\n });\n }\n\n /**\n * First item can't have a parent, so its depth should always be 0\n */\n firstItemDepthZero(item) {\n if (item.index === 0 && item.depth !== 0) {\n this.debug(`enforce depth on item ${item.index}: ${item.depth} => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Every item should have a non-negative depth set.\n *\n * @param {Item} item\n */\n depthMustBeSet(item) {\n if (isNaN(item.depth) || item.depth < 0) {\n this.debug(`unset depth on item ${item.index}: => 0`);\n\n item.depth = 0;\n }\n }\n\n /**\n * Depth must increase stepwise.\n *\n * @param {Item} item\n */\n itemCannotHaveInvalidDepth(item) {\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth - 1) {\n this.debug(\n `invalid depth on item ${item.index}: ${item.depth} => ${\n previous.depth + 1\n }`\n );\n\n item.depth = previous.depth + 1;\n }\n }\n\n /**\n * Depth must not exceed menu's depth limit.\n *\n * @param {Item} item\n */\n itemCannotExceedDepthLimit(item) {\n if (this.maxDepth > 0 && this.maxDepth <= item.depth) {\n // Note: this change can cause an issue where the previous item is treated\n // like a parent even though it no longer has children. This is because\n // items are processed in order. This issue does not seem worth solving\n // as it only occurs if the max depth is altered. The issue can be worked\n // around by saving the menu.\n item.depth = this.maxDepth - 1;\n }\n }\n\n /**\n * Parent item, if any, must be a layout.\n *\n * @param {Item} item\n */\n parentMustBeLayout(item) {\n // if we're the first child, make sure our parent is a layout\n // if we're a sibling, we know the previous item is valid so we must be too\n const previous = item.previousItem;\n if (previous && previous.depth < item.depth && !previous.isLayout) {\n this.debug(\n `invalid parent for item ${item.index}: ${item.depth} => ${previous.depth}`\n );\n\n item.depth = previous.depth;\n }\n }\n\n /**\n * If a parent has expanded and collapsed children, expand.\n *\n * @param {Item} item\n */\n parentCannotHaveExpandedAndCollapsedChildren(item) {\n if (item.hasCollapsedDescendants() && item.hasExpandedDescendants()) {\n this.debug(`expanding collapsed children of item ${item.index}`);\n\n item.expand();\n }\n }\n\n /**\n * De-nesting an item would create a gap of 2 between itself and its children\n *\n * @param {Item} item\n */\n parentsCannotDeNest(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDeNest\");\n }\n\n /**\n * Item depth can't go below 0.\n *\n * @param {Item} item\n */\n rootsCannotDeNest(item) {\n if (item.depth === 0) this.#deny(\"denyDeNest\");\n }\n\n /**\n * If an item doesn't have children it can't be collapsed.\n *\n * @param {Item} item\n */\n leavesCannotCollapse(item) {\n if (!item.hasExpandedDescendants()) this.#deny(\"denyCollapse\");\n }\n\n /**\n * If an item doesn't have any hidden descendants then it can't be expanded.\n *\n * @param {Item} item\n */\n needHiddenItemsToExpand(item) {\n if (!item.hasCollapsedDescendants()) this.#deny(\"denyExpand\");\n }\n\n /**\n * An item can't be nested (indented) if it doesn't have a valid parent.\n *\n * @param {Item} item\n */\n nestingNeedsParent(item) {\n const previous = item.previousItem;\n // no previous, so cannot nest\n if (!previous) this.#deny(\"denyNest\");\n // previous is too shallow, nesting would increase depth too much\n else if (previous.depth < item.depth) this.#deny(\"denyNest\");\n // new parent is not a layout\n else if (previous.depth === item.depth && !previous.isLayout)\n this.#deny(\"denyNest\");\n }\n\n /**\n * An item can't be nested (indented) if doing so would exceed the max depth.\n *\n * @param {Item} item\n */\n nestingCannotExceedMaxDepth(item) {\n if (this.maxDepth > 0 && this.maxDepth <= item.depth + 1) {\n this.#deny(\"denyNest\");\n }\n }\n\n /**\n * An item can't be deleted if it has visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDeleted(item) {\n if (!item.itemId || item.hasExpandedDescendants()) this.#deny(\"denyRemove\");\n }\n\n /**\n * Items cannot be dragged if they have visible children.\n *\n * @param {Item} item\n */\n parentsCannotBeDragged(item) {\n if (item.hasExpandedDescendants()) this.#deny(\"denyDrag\");\n }\n\n /**\n * Record a deny.\n *\n * @param rule {String}\n */\n #deny(rule) {\n this.rules[rule] = true;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport Item from \"./item\";\nimport Menu from \"./menu\";\nimport RulesEngine from \"./rules-engine\";\n\nexport default class MenuController extends Controller {\n static targets = [\"menu\"];\n static values = {\n maxDepth: Number,\n };\n\n connect() {\n this.state = this.menu.state;\n\n this.reindex();\n }\n\n get menu() {\n return new Menu(this.menuTarget);\n }\n\n reindex() {\n this.menu.reindex();\n this.#update();\n }\n\n reset() {\n this.menu.reset();\n }\n\n drop(event) {\n this.menu.reindex(); // set indexes before calculating previous\n\n const item = getEventItem(event);\n const previous = item.previousItem;\n\n let delta = 0;\n if (previous === undefined) {\n // if previous does not exist, set depth to 0\n delta = -item.depth;\n } else if (item.nextItem && item.nextItem.depth > previous.depth) {\n // if next is a child of previous, make item a child of previous\n delta = previous.depth - item.depth + 1;\n } else {\n // otherwise, make item a sibling of previous\n delta = previous.depth - item.depth;\n }\n\n item.traverse((child) => {\n child.depth += delta;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n remove(event) {\n const item = getEventItem(event);\n\n item.node.remove();\n\n this.#update();\n event.preventDefault();\n }\n\n nest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth += 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n deNest(event) {\n const item = getEventItem(event);\n\n item.traverse((child) => {\n child.depth -= 1;\n });\n\n this.#update();\n event.preventDefault();\n }\n\n collapse(event) {\n const item = getEventItem(event);\n\n item.collapse();\n\n this.#update();\n event.preventDefault();\n }\n\n expand(event) {\n const item = getEventItem(event);\n\n item.expand();\n\n this.#update();\n event.preventDefault();\n }\n\n /**\n * Re-apply rules to items to enable/disable appropriate actions.\n */\n #update() {\n // debounce requests to ensure that we only update once per tick\n this.updateRequested = true;\n setTimeout(() => {\n if (!this.updateRequested) return;\n\n this.updateRequested = false;\n const engine = new RulesEngine(this.maxDepthValue);\n this.menu.items.forEach((item) => engine.normalize(item));\n this.menu.items.forEach((item) => engine.update(item));\n\n this.#notifyChange();\n }, 0);\n }\n\n #notifyChange() {\n this.dispatch(\"change\", {\n bubbles: true,\n prefix: \"navigation\",\n detail: { dirty: this.#isDirty() },\n });\n }\n\n #isDirty() {\n return this.menu.state !== this.state;\n }\n}\n\nfunction getEventItem(event) {\n return new Item(event.target.closest(\"[data-navigation-item]\"));\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ListController extends Controller {\n connect() {\n this.enterCount = 0;\n }\n\n /**\n * When the user starts a drag within the list, set the item's dataTransfer\n * properties to indicate that it's being dragged and update its style.\n *\n * We delay setting the dataset property until the next animation frame\n * so that the style updates can be computed before the drag begins.\n *\n * @param event {DragEvent}\n */\n dragstart(event) {\n if (this.element !== event.target.parentElement) return;\n\n const target = event.target;\n event.dataTransfer.effectAllowed = \"move\";\n\n // update element style after drag has begun\n requestAnimationFrame(() => (target.dataset.dragging = \"\"));\n }\n\n /**\n * When the user drags an item over another item in the last, swap the\n * dragging item with the item under the cursor.\n *\n * As a special case, if the item is dragged over placeholder space at the end\n * of the list, move the item to the bottom of the list instead. This allows\n * users to hit the list element more easily when adding new items to an empty\n * list.\n *\n * @param event {DragEvent}\n */\n dragover(event) {\n const item = this.dragItem;\n if (!item) return;\n\n swap(dropTarget(event.target), item);\n\n event.preventDefault();\n return true;\n }\n\n /**\n * When the user drags an item into the list, create a placeholder item to\n * represent the new item. Note that we can't access the drag data\n * until drop, so we assume that this is our template item for now.\n *\n * Users can cancel the drag by dragging the item out of the list or by\n * pressing escape. Both are handled by `cancelDrag`.\n *\n * @param event {DragEvent}\n */\n dragenter(event) {\n event.preventDefault();\n\n // Safari doesn't support relatedTarget, so we count enter/leave pairs\n this.enterCount++;\n\n if (copyAllowed(event) && !this.dragItem) {\n const item = document.createElement(\"li\");\n item.dataset.dragging = \"\";\n item.dataset.newItem = \"\";\n this.element.appendChild(item);\n }\n }\n\n /**\n * When the user drags the item out of the list, remove the placeholder.\n * This allows users to cancel the drag by dragging the item out of the list.\n *\n * @param event {DragEvent}\n */\n dragleave(event) {\n // Safari doesn't support relatedTarget, so we count enter/leave pairs\n // https://bugs.webkit.org/show_bug.cgi?id=66547\n this.enterCount--;\n\n if (\n this.enterCount <= 0 &&\n this.dragItem.dataset.hasOwnProperty(\"newItem\")\n ) {\n this.cancelDrag(event);\n }\n }\n\n /**\n * When the user drops an item into the list, end the drag and reindex the list.\n *\n * If the item is a new item, we replace the placeholder with the template\n * item data from the dataTransfer API.\n *\n * @param event {DragEvent}\n */\n drop(event) {\n let item = this.dragItem;\n\n if (!item) return;\n\n event.preventDefault();\n delete item.dataset.dragging;\n swap(dropTarget(event.target), item);\n\n if (item.dataset.hasOwnProperty(\"newItem\")) {\n const placeholder = item;\n const template = document.createElement(\"template\");\n template.innerHTML = event.dataTransfer.getData(\"text/html\");\n item = template.content.querySelector(\"li\");\n\n this.element.replaceChild(item, placeholder);\n requestAnimationFrame(() =>\n item.querySelector(\"[role='button'][value='edit']\").click()\n );\n }\n\n this.dispatch(\"drop\", {\n target: item,\n bubbles: true,\n prefix: \"navigation\",\n });\n }\n\n /**\n * End an in-progress drag. If the item is a new item, remove it, otherwise\n * reset the item's style and restore its original position in the list.\n */\n dragend() {\n const item = this.dragItem;\n\n if (!item) {\n } else if (item.dataset.hasOwnProperty(\"newItem\")) {\n item.remove();\n } else {\n delete item.dataset.dragging;\n this.reset();\n }\n }\n\n get isDragging() {\n return !!this.dragItem;\n }\n\n get dragItem() {\n return this.element.querySelector(\"[data-dragging]\");\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"navigation\" });\n }\n\n reset() {\n this.dispatch(\"reset\", { bubbles: true, prefix: \"navigation\" });\n }\n}\n\n/**\n * Swaps two list items. If target is a list, the item is appended.\n *\n * @param target the target element to swap with\n * @param item the item the user is dragging\n */\nfunction swap(target, item) {\n if (!target) return;\n if (target === item) return;\n\n if (target.nodeName === \"LI\") {\n const positionComparison = target.compareDocumentPosition(item);\n if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {\n target.insertAdjacentElement(\"beforebegin\", item);\n } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {\n target.insertAdjacentElement(\"afterend\", item);\n }\n }\n\n if (target.nodeName === \"OL\") {\n target.appendChild(item);\n }\n}\n\n/**\n * Returns true if the event supports copy or copy move.\n *\n * Chrome and Firefox use copy, but Safari only supports copyMove.\n */\nfunction copyAllowed(event) {\n return (\n event.dataTransfer.effectAllowed === \"copy\" ||\n event.dataTransfer.effectAllowed === \"copyMove\"\n );\n}\n\n/**\n * Given an event target, return the closest drop target, if any.\n */\nfunction dropTarget(e) {\n return (\n e &&\n (e.closest(\"[data-controller='navigation--editor--list'] > *\") ||\n e.closest(\"[data-controller='navigation--editor--list']\"))\n );\n}\n","import MenuController from \"./editor/menu_controller\";\nimport ItemController from \"./editor/item_controller\";\nimport ListController from \"./editor/list_controller\";\nimport NewItemController from \"./editor/new_item_controller\";\nimport StatusBarController from \"./editor/status_bar_controller\";\n\nconst Definitions = [\n {\n identifier: \"navigation--editor--menu\",\n controllerConstructor: MenuController,\n },\n {\n identifier: \"navigation--editor--item\",\n controllerConstructor: ItemController,\n },\n {\n identifier: \"navigation--editor--list\",\n controllerConstructor: ListController,\n },\n {\n identifier: \"navigation--editor--new-item\",\n controllerConstructor: NewItemController,\n },\n {\n identifier: \"navigation--editor--status-bar\",\n controllerConstructor: StatusBarController,\n },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\nimport Item from \"./item\";\n\nexport default class ItemController extends Controller {\n get item() {\n return new Item(this.li);\n }\n\n get ol() {\n return this.element.closest(\"ol\");\n }\n\n get li() {\n return this.element.closest(\"li\");\n }\n\n connect() {\n if (this.element.dataset.hasOwnProperty(\"delete\")) {\n this.remove();\n }\n // if index is not already set, re-index will set it\n else if (!(this.item.index >= 0)) {\n this.reindex();\n }\n // if item has been replaced via turbo, re-index will run the rules engine\n // update our depth and index with values from the li's data attributes\n else if (this.item.hasItemIdChanged()) {\n this.item.updateAfterChange();\n this.reindex();\n }\n }\n\n remove() {\n // capture ol\n const ol = this.ol;\n // remove self from dom\n this.li.remove();\n // reindex ol\n this.reindex();\n }\n\n reindex() {\n this.dispatch(\"reindex\", { bubbles: true, prefix: \"navigation\" });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class NewItemController extends Controller {\n static targets = [\"template\"];\n\n dragstart(event) {\n if (this.element !== event.target) return;\n\n event.dataTransfer.setData(\"text/html\", this.templateTarget.innerHTML);\n event.dataTransfer.effectAllowed = \"copy\";\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class StatusBarController extends Controller {\n connect() {\n // cache the version's state in the controller on connect\n this.versionState = this.element.dataset.state;\n }\n\n change(e) {\n if (e.detail && e.detail.hasOwnProperty(\"dirty\")) {\n this.update(e.detail);\n }\n }\n\n update({ dirty }) {\n if (dirty) {\n this.element.dataset.state = \"dirty\";\n } else {\n this.element.dataset.state = this.versionState;\n }\n }\n}\n"],"names":["Item","comparator","a","b","index","constructor","node","this","itemId","dataset","itemIdInput","querySelector","id","value","depth","parseInt","depthInput","indexInput","isLayout","hasAttribute","previousItem","sibling","previousElementSibling","nextItem","nextElementSibling","hasCollapsedDescendants","childrenList","childrenListElement","children","length","hasExpandedDescendants","traverse","callback","expanded","expandedDescendants","traverseCollapsed","forEach","item","collapsedDescendants","collapse","listElement","document","createElement","setAttribute","appendChild","createChildrenList","child","expand","Array","from","reverse","insertAdjacentElement","toggleRule","rule","deny","hasOwnProperty","removeAttribute","hasItemIdChanged","updateAfterChange","descendants","push","map","Menu","items","nodes","querySelectorAll","state","inputs","e","join","reindex","reset","sort","RulesEngine","static","maxDepth","debug","args","console","log","normalize","firstItemDepthZero","depthMustBeSet","itemCannotHaveInvalidDepth","itemCannotExceedDepthLimit","parentMustBeLayout","parentCannotHaveExpandedAndCollapsedChildren","update","rules","parentsCannotDeNest","rootsCannotDeNest","nestingNeedsParent","nestingCannotExceedMaxDepth","leavesCannotCollapse","needHiddenItemsToExpand","parentsCannotBeDeleted","parentsCannotBeDragged","isNaN","previous","getEventItem","event","target","closest","swap","nodeName","positionComparison","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","DOCUMENT_POSITION_PRECEDING","dropTarget","Definitions","identifier","controllerConstructor","Controller","Number","connect","menu","menuTarget","drop","delta","undefined","preventDefault","remove","nest","deNest","updateRequested","setTimeout","engine","maxDepthValue","notifyChange","dispatch","bubbles","prefix","detail","dirty","isDirty","li","ol","element","enterCount","dragstart","parentElement","dataTransfer","effectAllowed","requestAnimationFrame","dragging","dragover","dragItem","dragenter","copyAllowed","newItem","dragleave","cancelDrag","placeholder","template","innerHTML","getData","content","replaceChild","click","dragend","isDragging","setData","templateTarget","versionState","change"],"mappings":"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"}
|
@@ -1,6 +1,19 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
2
2
|
|
3
3
|
export default class ListController extends Controller {
|
4
|
+
connect() {
|
5
|
+
this.enterCount = 0;
|
6
|
+
}
|
7
|
+
|
8
|
+
/**
|
9
|
+
* When the user starts a drag within the list, set the item's dataTransfer
|
10
|
+
* properties to indicate that it's being dragged and update its style.
|
11
|
+
*
|
12
|
+
* We delay setting the dataset property until the next animation frame
|
13
|
+
* so that the style updates can be computed before the drag begins.
|
14
|
+
*
|
15
|
+
* @param event {DragEvent}
|
16
|
+
*/
|
4
17
|
dragstart(event) {
|
5
18
|
if (this.element !== event.target.parentElement) return;
|
6
19
|
|
@@ -8,51 +21,89 @@ export default class ListController extends Controller {
|
|
8
21
|
event.dataTransfer.effectAllowed = "move";
|
9
22
|
|
10
23
|
// update element style after drag has begun
|
11
|
-
|
24
|
+
requestAnimationFrame(() => (target.dataset.dragging = ""));
|
12
25
|
}
|
13
26
|
|
27
|
+
/**
|
28
|
+
* When the user drags an item over another item in the last, swap the
|
29
|
+
* dragging item with the item under the cursor.
|
30
|
+
*
|
31
|
+
* As a special case, if the item is dragged over placeholder space at the end
|
32
|
+
* of the list, move the item to the bottom of the list instead. This allows
|
33
|
+
* users to hit the list element more easily when adding new items to an empty
|
34
|
+
* list.
|
35
|
+
*
|
36
|
+
* @param event {DragEvent}
|
37
|
+
*/
|
14
38
|
dragover(event) {
|
15
|
-
const item = this.dragItem
|
39
|
+
const item = this.dragItem;
|
16
40
|
if (!item) return;
|
17
41
|
|
18
|
-
swap(
|
42
|
+
swap(dropTarget(event.target), item);
|
19
43
|
|
20
44
|
event.preventDefault();
|
21
45
|
return true;
|
22
46
|
}
|
23
47
|
|
48
|
+
/**
|
49
|
+
* When the user drags an item into the list, create a placeholder item to
|
50
|
+
* represent the new item. Note that we can't access the drag data
|
51
|
+
* until drop, so we assume that this is our template item for now.
|
52
|
+
*
|
53
|
+
* Users can cancel the drag by dragging the item out of the list or by
|
54
|
+
* pressing escape. Both are handled by `cancelDrag`.
|
55
|
+
*
|
56
|
+
* @param event {DragEvent}
|
57
|
+
*/
|
24
58
|
dragenter(event) {
|
25
59
|
event.preventDefault();
|
26
60
|
|
27
|
-
|
61
|
+
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
62
|
+
this.enterCount++;
|
63
|
+
|
64
|
+
if (copyAllowed(event) && !this.dragItem) {
|
28
65
|
const item = document.createElement("li");
|
29
66
|
item.dataset.dragging = "";
|
30
67
|
item.dataset.newItem = "";
|
31
|
-
this.element.
|
68
|
+
this.element.appendChild(item);
|
32
69
|
}
|
33
70
|
}
|
34
71
|
|
72
|
+
/**
|
73
|
+
* When the user drags the item out of the list, remove the placeholder.
|
74
|
+
* This allows users to cancel the drag by dragging the item out of the list.
|
75
|
+
*
|
76
|
+
* @param event {DragEvent}
|
77
|
+
*/
|
35
78
|
dragleave(event) {
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
if (
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
79
|
+
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
80
|
+
// https://bugs.webkit.org/show_bug.cgi?id=66547
|
81
|
+
this.enterCount--;
|
82
|
+
|
83
|
+
if (
|
84
|
+
this.enterCount <= 0 &&
|
85
|
+
this.dragItem.dataset.hasOwnProperty("newItem")
|
86
|
+
) {
|
87
|
+
this.cancelDrag(event);
|
45
88
|
}
|
46
89
|
}
|
47
90
|
|
91
|
+
/**
|
92
|
+
* When the user drops an item into the list, end the drag and reindex the list.
|
93
|
+
*
|
94
|
+
* If the item is a new item, we replace the placeholder with the template
|
95
|
+
* item data from the dataTransfer API.
|
96
|
+
*
|
97
|
+
* @param event {DragEvent}
|
98
|
+
*/
|
48
99
|
drop(event) {
|
49
|
-
let item = this.dragItem
|
100
|
+
let item = this.dragItem;
|
50
101
|
|
51
102
|
if (!item) return;
|
52
103
|
|
53
104
|
event.preventDefault();
|
54
105
|
delete item.dataset.dragging;
|
55
|
-
swap(
|
106
|
+
swap(dropTarget(event.target), item);
|
56
107
|
|
57
108
|
if (item.dataset.hasOwnProperty("newItem")) {
|
58
109
|
const placeholder = item;
|
@@ -61,7 +112,7 @@ export default class ListController extends Controller {
|
|
61
112
|
item = template.content.querySelector("li");
|
62
113
|
|
63
114
|
this.element.replaceChild(item, placeholder);
|
64
|
-
|
115
|
+
requestAnimationFrame(() =>
|
65
116
|
item.querySelector("[role='button'][value='edit']").click()
|
66
117
|
);
|
67
118
|
}
|
@@ -73,23 +124,28 @@ export default class ListController extends Controller {
|
|
73
124
|
});
|
74
125
|
}
|
75
126
|
|
127
|
+
/**
|
128
|
+
* End an in-progress drag. If the item is a new item, remove it, otherwise
|
129
|
+
* reset the item's style and restore its original position in the list.
|
130
|
+
*/
|
76
131
|
dragend() {
|
77
|
-
const item = this.dragItem
|
78
|
-
if (!item) return;
|
132
|
+
const item = this.dragItem;
|
79
133
|
|
80
|
-
|
81
|
-
|
134
|
+
if (!item) {
|
135
|
+
} else if (item.dataset.hasOwnProperty("newItem")) {
|
136
|
+
item.remove();
|
137
|
+
} else {
|
138
|
+
delete item.dataset.dragging;
|
139
|
+
this.reset();
|
140
|
+
}
|
82
141
|
}
|
83
142
|
|
84
|
-
|
85
|
-
return this.
|
143
|
+
get isDragging() {
|
144
|
+
return !!this.dragItem;
|
86
145
|
}
|
87
146
|
|
88
|
-
|
89
|
-
return (
|
90
|
-
e.closest("[data-controller='navigation--editor--list'] > *") ||
|
91
|
-
e.closest("[data-controller='navigation--editor--list']")
|
92
|
-
);
|
147
|
+
get dragItem() {
|
148
|
+
return this.element.querySelector("[data-dragging]");
|
93
149
|
}
|
94
150
|
|
95
151
|
reindex() {
|
@@ -101,6 +157,12 @@ export default class ListController extends Controller {
|
|
101
157
|
}
|
102
158
|
}
|
103
159
|
|
160
|
+
/**
|
161
|
+
* Swaps two list items. If target is a list, the item is appended.
|
162
|
+
*
|
163
|
+
* @param target the target element to swap with
|
164
|
+
* @param item the item the user is dragging
|
165
|
+
*/
|
104
166
|
function swap(target, item) {
|
105
167
|
if (!target) return;
|
106
168
|
if (target === item) return;
|
@@ -118,3 +180,26 @@ function swap(target, item) {
|
|
118
180
|
target.appendChild(item);
|
119
181
|
}
|
120
182
|
}
|
183
|
+
|
184
|
+
/**
|
185
|
+
* Returns true if the event supports copy or copy move.
|
186
|
+
*
|
187
|
+
* Chrome and Firefox use copy, but Safari only supports copyMove.
|
188
|
+
*/
|
189
|
+
function copyAllowed(event) {
|
190
|
+
return (
|
191
|
+
event.dataTransfer.effectAllowed === "copy" ||
|
192
|
+
event.dataTransfer.effectAllowed === "copyMove"
|
193
|
+
);
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Given an event target, return the closest drop target, if any.
|
198
|
+
*/
|
199
|
+
function dropTarget(e) {
|
200
|
+
return (
|
201
|
+
e &&
|
202
|
+
(e.closest("[data-controller='navigation--editor--list'] > *") ||
|
203
|
+
e.closest("[data-controller='navigation--editor--list']"))
|
204
|
+
);
|
205
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katalyst-navigation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katalyst Interactive
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: katalyst-html-attributes
|
@@ -134,7 +134,6 @@ files:
|
|
134
134
|
- app/views/katalyst/navigation/items/_link.html.erb
|
135
135
|
- app/views/katalyst/navigation/items/edit.html.erb
|
136
136
|
- app/views/katalyst/navigation/items/edit.turbo_stream.erb
|
137
|
-
- app/views/katalyst/navigation/items/new.html.erb
|
138
137
|
- app/views/katalyst/navigation/items/update.turbo_stream.erb
|
139
138
|
- app/views/katalyst/navigation/menus/_menu.html+row.erb
|
140
139
|
- app/views/katalyst/navigation/menus/edit.html.erb
|