katalyst-navigation 1.8.4 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -2
  3. data/app/assets/builds/katalyst/navigation.esm.js +215 -109
  4. data/app/assets/builds/katalyst/navigation.js +215 -109
  5. data/app/assets/builds/katalyst/navigation.min.js +1 -1
  6. data/app/assets/builds/katalyst/navigation.min.js.map +1 -1
  7. data/app/assets/images/katalyst/navigation/icons/button.svg +1 -0
  8. data/app/assets/images/katalyst/navigation/icons/collapse.svg +3 -0
  9. data/app/assets/images/katalyst/navigation/icons/edit.svg +1 -0
  10. data/app/assets/images/katalyst/navigation/icons/expand.svg +3 -0
  11. data/app/assets/images/katalyst/navigation/icons/heading.svg +1 -0
  12. data/app/assets/images/katalyst/navigation/icons/hidden.svg +1 -0
  13. data/app/assets/images/katalyst/navigation/icons/indent.svg +3 -0
  14. data/app/assets/images/katalyst/navigation/icons/link.svg +1 -0
  15. data/app/assets/images/katalyst/navigation/icons/outdent.svg +4 -0
  16. data/app/assets/images/katalyst/navigation/icons/remove.svg +1 -0
  17. data/app/assets/stylesheets/katalyst/_navigation.scss +3 -0
  18. data/app/assets/stylesheets/katalyst/navigation/editor.css +175 -0
  19. data/app/assets/stylesheets/katalyst/navigation/icons.css +54 -0
  20. data/app/assets/stylesheets/katalyst/navigation/{editor/_status-bar.scss → status-bar.css} +30 -34
  21. data/app/assets/stylesheets/katalyst/navigation.css +3 -0
  22. data/app/components/katalyst/navigation/editor/base_component.rb +0 -6
  23. data/app/components/katalyst/navigation/editor/item_component.html.erb +26 -16
  24. data/app/components/katalyst/navigation/editor/item_component.rb +2 -3
  25. data/app/components/katalyst/navigation/editor/item_editor_component.rb +16 -19
  26. data/app/components/katalyst/navigation/editor/new_item_component.html.erb +15 -14
  27. data/app/components/katalyst/navigation/editor/new_item_component.rb +7 -10
  28. data/app/components/katalyst/navigation/editor/new_items_component.html.erb +41 -2
  29. data/app/components/katalyst/navigation/editor/new_items_component.rb +0 -2
  30. data/app/components/katalyst/navigation/editor/row_component.html.erb +2 -1
  31. data/app/components/katalyst/navigation/editor/status_bar_component.rb +9 -11
  32. data/app/components/katalyst/navigation/editor/table_component.html.erb +0 -6
  33. data/app/components/katalyst/navigation/editor/table_component.rb +12 -14
  34. data/app/components/katalyst/navigation/editor_component.html.erb +7 -0
  35. data/app/components/katalyst/navigation/editor_component.rb +13 -16
  36. data/app/controllers/katalyst/navigation/items_controller.rb +1 -1
  37. data/app/controllers/katalyst/navigation/menus_controller.rb +1 -1
  38. data/app/javascript/navigation/application.js +8 -3
  39. data/app/javascript/navigation/editor/item.js +1 -1
  40. data/app/javascript/navigation/editor/item_editor_controller.js +59 -0
  41. data/app/javascript/navigation/editor/list_controller.js +11 -103
  42. data/app/javascript/navigation/editor/new_items_controller.js +150 -0
  43. data/app/views/katalyst/navigation/items/_button.html.erb +24 -28
  44. data/app/views/katalyst/navigation/items/_form.html.erb +6 -0
  45. data/app/views/katalyst/navigation/items/_form_errors.html.erb +1 -1
  46. data/app/views/katalyst/navigation/items/_heading.html.erb +12 -14
  47. data/app/views/katalyst/navigation/items/_link.html.erb +24 -26
  48. data/app/views/katalyst/navigation/items/edit.html.erb +39 -2
  49. data/app/views/katalyst/navigation/items/edit.turbo_stream.erb +2 -0
  50. data/app/views/katalyst/navigation/items/update.turbo_stream.erb +0 -1
  51. data/app/views/katalyst/navigation/menus/show.html.erb +0 -1
  52. data/lib/katalyst/navigation/engine.rb +0 -1
  53. metadata +21 -28
  54. data/app/assets/stylesheets/katalyst/navigation/editor/_icon.scss +0 -17
  55. data/app/assets/stylesheets/katalyst/navigation/editor/_index.scss +0 -145
  56. data/app/assets/stylesheets/katalyst/navigation/editor/_item-actions.scss +0 -93
  57. data/app/assets/stylesheets/katalyst/navigation/editor/_item-rules.scss +0 -19
  58. data/app/assets/stylesheets/katalyst/navigation/editor/_new-items.scss +0 -67
  59. data/app/javascript/navigation/editor/new_item_controller.js +0 -12
@@ -264,7 +264,7 @@ class Item {
264
264
  */
265
265
  function createChildrenList(node) {
266
266
  const childrenList = document.createElement("ol");
267
- childrenList.setAttribute("class", "hidden");
267
+ childrenList.toggleAttribute("hidden", true);
268
268
 
269
269
  childrenList.dataset[`navigationChildren`] = "";
270
270
 
@@ -744,11 +744,64 @@ class ItemController extends Controller {
744
744
  }
745
745
  }
746
746
 
747
- class ListController extends Controller {
747
+ class ItemEditorController extends Controller {
748
+ static targets = ["dialog"];
749
+
748
750
  connect() {
749
- this.enterCount = 0;
751
+ this.element.addEventListener("turbo:submit-end", this.onSubmit);
750
752
  }
751
753
 
754
+ disconnect() {
755
+ this.element.removeEventListener("turbo:submit-end", this.onSubmit);
756
+ }
757
+
758
+ click(e) {
759
+ if (e.target.tagName === "DIALOG") this.dismiss();
760
+ }
761
+
762
+ dismiss() {
763
+ if (!this.dialogTarget) return;
764
+ if (!this.dialogTarget.open) this.dialogTarget.close();
765
+
766
+ if (!("itemPersisted" in this.dialogTarget.dataset)) {
767
+ this.#removeTargetItem();
768
+ }
769
+
770
+ this.element.removeAttribute("src");
771
+ this.dialogTarget.remove();
772
+ }
773
+
774
+ dialogTargetConnected(dialog) {
775
+ dialog.showModal();
776
+ }
777
+
778
+ onSubmit = (event) => {
779
+ if (
780
+ event.detail.success &&
781
+ "closeDialog" in event.detail.formSubmission?.submitter?.dataset
782
+ ) {
783
+ this.dialogTarget.close();
784
+ this.element.removeAttribute("src");
785
+ this.dialogTarget.remove();
786
+ }
787
+ };
788
+
789
+ #removeTargetItem() {
790
+ const el = document.getElementById(this.dialogTarget.dataset.itemId);
791
+ const item = new Item(el.closest("[data-navigation-item]"));
792
+ const list = item.node.parentElement;
793
+
794
+ item.node.remove();
795
+
796
+ this.dispatch("reindex", {
797
+ target: list,
798
+ bubbles: true,
799
+ prefix: "navigation",
800
+ });
801
+ }
802
+ }
803
+
804
+ class ListController extends Controller {
752
805
  /**
753
806
  * When the user starts a drag within the list, set the item's dataTransfer
754
807
  * properties to indicate that it's being dragged and update its style.
@@ -772,11 +825,6 @@ class ListController extends Controller {
772
825
  * When the user drags an item over another item in the last, swap the
773
826
  * dragging item with the item under the cursor.
774
827
  *
775
- * As a special case, if the item is dragged over placeholder space at the end
776
- * of the list, move the item to the bottom of the list instead. This allows
777
- * users to hit the list element more easily when adding new items to an empty
778
- * list.
779
- *
780
828
  * @param event {DragEvent}
781
829
  */
782
830
  dragover(event) {
@@ -790,53 +838,7 @@ class ListController extends Controller {
790
838
  }
791
839
 
792
840
  /**
793
- * When the user drags an item into the list, create a placeholder item to
794
- * represent the new item. Note that we can't access the drag data
795
- * until drop, so we assume that this is our template item for now.
796
- *
797
- * Users can cancel the drag by dragging the item out of the list or by
798
- * pressing escape. Both are handled by `cancelDrag`.
799
- *
800
- * @param event {DragEvent}
801
- */
802
- dragenter(event) {
803
- event.preventDefault();
804
-
805
- // Safari doesn't support relatedTarget, so we count enter/leave pairs
806
- this.enterCount++;
807
-
808
- if (copyAllowed(event) && !this.dragItem) {
809
- const item = document.createElement("li");
810
- item.dataset.dragging = "";
811
- item.dataset.newItem = "";
812
- this.element.appendChild(item);
813
- }
814
- }
815
-
816
- /**
817
- * When the user drags the item out of the list, remove the placeholder.
818
- * This allows users to cancel the drag by dragging the item out of the list.
819
- *
820
- * @param event {DragEvent}
821
- */
822
- dragleave(event) {
823
- // Safari doesn't support relatedTarget, so we count enter/leave pairs
824
- // https://bugs.webkit.org/show_bug.cgi?id=66547
825
- this.enterCount--;
826
-
827
- if (
828
- this.enterCount <= 0 &&
829
- this.dragItem.dataset.hasOwnProperty("newItem")
830
- ) {
831
- this.cancelDrag(event);
832
- }
833
- }
834
-
835
- /**
836
- * When the user drops an item into the list, end the drag and reindex the list.
837
- *
838
- * If the item is a new item, we replace the placeholder with the template
839
- * item data from the dataTransfer API.
841
+ * When the user drops an item, end the drag and reindex the list.
840
842
  *
841
843
  * @param event {DragEvent}
842
844
  */
@@ -849,18 +851,6 @@ class ListController extends Controller {
849
851
  delete item.dataset.dragging;
850
852
  swap(dropTarget(event.target), item);
851
853
 
852
- if (item.dataset.hasOwnProperty("newItem")) {
853
- const placeholder = item;
854
- const template = document.createElement("template");
855
- template.innerHTML = event.dataTransfer.getData("text/html");
856
- item = template.content.querySelector("li");
857
-
858
- this.element.replaceChild(item, placeholder);
859
- requestAnimationFrame(() =>
860
- item.querySelector("[role='button'][value='edit']").click(),
861
- );
862
- }
863
-
864
854
  this.dispatch("drop", {
865
855
  target: item,
866
856
  bubbles: true,
@@ -869,15 +859,13 @@ class ListController extends Controller {
869
859
  }
870
860
 
871
861
  /**
872
- * End an in-progress drag. If the item is a new item, remove it, otherwise
873
- * reset the item's style and restore its original position in the list.
862
+ * End an in-progress drag by resetting the item's style and restoring its
863
+ * original position in the list.
874
864
  */
875
865
  dragend() {
876
866
  const item = this.dragItem;
877
867
 
878
- if (!item) ; else if (item.dataset.hasOwnProperty("newItem")) {
879
- item.remove();
880
- } else {
868
+ if (item) {
881
869
  delete item.dataset.dragging;
882
870
  this.reset();
883
871
  }
@@ -901,7 +889,7 @@ class ListController extends Controller {
901
889
  }
902
890
 
903
891
  /**
904
- * Swaps two list items. If target is a list, the item is appended.
892
+ * Swaps two list items.
905
893
  *
906
894
  * @param target the target element to swap with
907
895
  * @param item the item the user is dragging
@@ -910,51 +898,165 @@ function swap(target, item) {
910
898
  if (!target) return;
911
899
  if (target === item) return;
912
900
 
913
- if (target.nodeName === "LI") {
914
- const positionComparison = target.compareDocumentPosition(item);
915
- if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {
916
- target.insertAdjacentElement("beforebegin", item);
917
- } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {
918
- target.insertAdjacentElement("afterend", item);
919
- }
920
- }
921
-
922
- if (target.nodeName === "OL") {
923
- target.appendChild(item);
901
+ const positionComparison = target.compareDocumentPosition(item);
902
+ if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {
903
+ target.insertAdjacentElement("beforebegin", item);
904
+ } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {
905
+ target.insertAdjacentElement("afterend", item);
924
906
  }
925
907
  }
926
908
 
927
- /**
928
- * Returns true if the event supports copy or copy move.
929
- *
930
- * Chrome and Firefox use copy, but Safari only supports copyMove.
931
- */
932
- function copyAllowed(event) {
933
- return (
934
- event.dataTransfer.effectAllowed === "copy" ||
935
- event.dataTransfer.effectAllowed === "copyMove"
936
- );
937
- }
938
-
939
909
  /**
940
910
  * Given an event target, return the closest drop target, if any.
941
911
  */
942
912
  function dropTarget(e) {
943
- return (
944
- e &&
945
- (e.closest("[data-controller='navigation--editor--list'] > *") ||
946
- e.closest("[data-controller='navigation--editor--list']"))
947
- );
913
+ return e && e.closest("[data-controller='navigation--editor--list'] > *");
948
914
  }
949
915
 
950
- class NewItemController extends Controller {
951
- static targets = ["template"];
916
+ const EDGE_AREA = 24;
952
917
 
953
- dragstart(event) {
954
- if (this.element !== event.target) return;
918
+ class NewItemsController extends Controller {
919
+ static targets = ["inline"];
920
+
921
+ connect() {
922
+ this.form.addEventListener("mousemove", this.move);
923
+ }
924
+
925
+ disconnect() {
926
+ this.form?.removeEventListener("mousemove", this.move);
927
+ delete this.currentItem;
928
+ }
929
+
930
+ click(e) {
931
+ if (e.target.tagName === "DIALOG") this.close(e);
932
+ }
933
+
934
+ open(e) {
935
+ e.preventDefault();
936
+ this.dialog.showModal();
937
+ }
938
+
939
+ close(e) {
940
+ e.preventDefault();
941
+ this.dialog.close();
942
+ }
943
+
944
+ /**
945
+ * Add the selected item to the DOM at the current position or the end of the list.
946
+ */
947
+ add(e) {
948
+ e.preventDefault();
949
+
950
+ const template = e.target.closest("li").querySelector("template");
951
+ const item = template.content.querySelector("li").cloneNode(true);
952
+ const target = this.currentItem;
953
+
954
+ if (target) {
955
+ target.insertAdjacentElement("beforebegin", item);
956
+ new Item(item).depth = new Item(target).depth;
957
+ } else {
958
+ this.list.insertAdjacentElement("beforeend", item);
959
+ }
960
+
961
+ this.toggleInline(false);
962
+ this.dialog.close();
963
+
964
+ requestAnimationFrame(() => {
965
+ item.querySelector(`[value="edit"]`).click();
966
+ });
967
+ }
968
+
969
+ morph(e) {
970
+ e.preventDefault();
971
+ this.dialog.close();
972
+ }
973
+
974
+ move = (e) => {
975
+ if (this.isOverInlineTarget(e)) return;
976
+ if (this.dialog.open) return;
977
+
978
+ const target = this.getCurrentItem(e);
979
+
980
+ // return if we're already showing this item
981
+ if (this.currentItem === target) return;
982
+
983
+ // hide the button if it's already visible
984
+ if (this.currentItem) this.toggleInline(false);
985
+
986
+ this.currentItem = target;
955
987
 
956
- event.dataTransfer.setData("text/html", this.templateTarget.innerHTML);
957
- event.dataTransfer.effectAllowed = "copy";
988
+ // clear any previously set timer
989
+ if (this.timer) clearTimeout(this.timer);
990
+
991
+ // show the button after a debounce pause
992
+ this.timer = setTimeout(() => {
993
+ delete this.timer;
994
+ this.toggleInline();
995
+ }, 100);
996
+ };
997
+
998
+ toggleInline(show = !!this.currentItem) {
999
+ if (show) {
1000
+ this.inlineTarget.style.top = `${this.currentItem.offsetTop}px`;
1001
+ this.inlineTarget.toggleAttribute("hidden", false);
1002
+ } else {
1003
+ this.inlineTarget.toggleAttribute("hidden", true);
1004
+ }
1005
+ }
1006
+
1007
+ get dialog() {
1008
+ return this.element.querySelector("dialog");
1009
+ }
1010
+
1011
+ /**
1012
+ * @returns {HTMLFormElement}
1013
+ */
1014
+ get form() {
1015
+ return this.element.closest("form");
1016
+ }
1017
+
1018
+ /**
1019
+ * @returns {HTMLUListElement,null}
1020
+ */
1021
+ get list() {
1022
+ return this.form.querySelector(
1023
+ `[data-controller="navigation--editor--list"]`,
1024
+ );
1025
+ }
1026
+
1027
+ /**
1028
+ * @param {MouseEvent} e
1029
+ * @returns {HTMLLIElement,null}
1030
+ */
1031
+ getCurrentItem(e) {
1032
+ const item = document.elementFromPoint(e.clientX, e.clientY).closest("li");
1033
+ if (!item) return null;
1034
+
1035
+ const bounds = item.getBoundingClientRect();
1036
+
1037
+ // check X for center(ish) mouse position
1038
+ if (e.clientX < bounds.left + bounds.width / 2 - 2 * EDGE_AREA) return null;
1039
+ if (e.clientX > bounds.left + bounds.width / 2 + 2 * EDGE_AREA) return null;
1040
+
1041
+ // check Y for hits on this item or it's next sibling
1042
+ if (e.clientY - bounds.y <= EDGE_AREA) {
1043
+ return item;
1044
+ } else if (bounds.y + bounds.height - e.clientY <= EDGE_AREA) {
1045
+ return item.nextElementSibling;
1046
+ } else {
1047
+ return null;
1048
+ }
1049
+ }
1050
+
1051
+ /**
1052
+ * @param {MouseEvent} e
1053
+ * @returns {Boolean} true when the target of the event is the floating button
1054
+ */
1055
+ isOverInlineTarget(e) {
1056
+ return (
1057
+ this.inlineTarget ===
1058
+ document.elementFromPoint(e.clientX, e.clientY).closest("div")
1059
+ );
958
1060
  }
959
1061
  }
960
1062
 
@@ -995,13 +1097,17 @@ const Definitions = [
995
1097
  identifier: "navigation--editor--item",
996
1098
  controllerConstructor: ItemController,
997
1099
  },
1100
+ {
1101
+ identifier: "navigation--editor--item-editor",
1102
+ controllerConstructor: ItemEditorController,
1103
+ },
998
1104
  {
999
1105
  identifier: "navigation--editor--list",
1000
1106
  controllerConstructor: ListController,
1001
1107
  },
1002
1108
  {
1003
- identifier: "navigation--editor--new-item",
1004
- controllerConstructor: NewItemController,
1109
+ identifier: "navigation--editor--new-items",
1110
+ controllerConstructor: NewItemsController,
1005
1111
  },
1006
1112
  {
1007
1113
  identifier: "navigation--editor--status-bar",
@@ -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)}}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}morph(e){e.target===this.element&&(this.versionState=this.element.dataset.state,this.update({dirty:!1}))}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};
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.#i;return!!e&&e.children.length>0}hasExpandedDescendants(){let e=this.nextItem;return!!e&&e.depth>this.depth}traverse(e){const t=this.#s;e(this),this.#a(e),t.forEach((t=>t.#a(e)))}#a(e){this.hasCollapsedDescendants()&&this.#d.forEach((t=>{e(t),t.#a(e)}))}collapse(){let e=this.#i;e||(e=function(e){const t=document.createElement("ol");return t.toggleAttribute("hidden",!0),t.dataset.navigationChildren="",e.appendChild(t),t}(this.node)),this.#s.forEach((t=>e.appendChild(t.node)))}expand(){this.hasCollapsedDescendants()&&Array.from(this.#i.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#i(){return this.node.querySelector(":scope > [data-navigation-children]")}get#s(){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.#i.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 i{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),i.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 s(e){return new t(e.target.closest("[data-navigation-item]"))}function a(e,t){if(!e)return;if(e===t)return;const n=e.compareDocumentPosition(t);n&Node.DOCUMENT_POSITION_FOLLOWING?e.insertAdjacentElement("beforebegin",t):n&Node.DOCUMENT_POSITION_PRECEDING&&e.insertAdjacentElement("afterend",t)}function d(e){return e&&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.#o()}reset(){this.menu.reset()}drop(e){this.menu.reindex();const t=s(e),n=t.previousItem;let i=0;i=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+=i})),this.#o(),e.preventDefault()}remove(e){s(e).node.remove(),this.#o(),e.preventDefault()}nest(e){s(e).traverse((e=>{e.depth+=1})),this.#o(),e.preventDefault()}deNest(e){s(e).traverse((e=>{e.depth-=1})),this.#o(),e.preventDefault()}collapse(e){s(e).collapse(),this.#o(),e.preventDefault()}expand(e){s(e).expand(),this.#o(),e.preventDefault()}#o(){this.updateRequested=!0,setTimeout((()=>{if(!this.updateRequested)return;this.updateRequested=!1;const e=new i(this.maxDepthValue);this.menu.items.forEach((t=>e.normalize(t))),this.menu.items.forEach((t=>e.update(t))),this.#h()}),0)}#h(){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--item-editor",controllerConstructor:class extends e{static targets=["dialog"];connect(){this.element.addEventListener("turbo:submit-end",this.onSubmit)}disconnect(){this.element.removeEventListener("turbo:submit-end",this.onSubmit)}click(e){"DIALOG"===e.target.tagName&&this.dismiss()}dismiss(){this.dialogTarget&&(this.dialogTarget.open||this.dialogTarget.close(),"itemPersisted"in this.dialogTarget.dataset||this.#p(),this.element.removeAttribute("src"),this.dialogTarget.remove())}dialogTargetConnected(e){e.showModal()}onSubmit=e=>{e.detail.success&&"closeDialog"in e.detail.formSubmission?.submitter?.dataset&&(this.dialogTarget.close(),this.element.removeAttribute("src"),this.dialogTarget.remove())};#p(){const e=document.getElementById(this.dialogTarget.dataset.itemId),n=new t(e.closest("[data-navigation-item]")),i=n.node.parentElement;n.node.remove(),this.dispatch("reindex",{target:i,bubbles:!0,prefix:"navigation"})}}},{identifier:"navigation--editor--list",controllerConstructor:class extends e{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 a(d(e.target),t),e.preventDefault(),!0}drop(e){let t=this.dragItem;t&&(e.preventDefault(),delete t.dataset.dragging,a(d(e.target),t),this.dispatch("drop",{target:t,bubbles:!0,prefix:"navigation"}))}dragend(){const e=this.dragItem;e&&(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-items",controllerConstructor:class extends e{static targets=["inline"];connect(){this.form.addEventListener("mousemove",this.move)}disconnect(){this.form?.removeEventListener("mousemove",this.move),delete this.currentItem}click(e){"DIALOG"===e.target.tagName&&this.close(e)}open(e){e.preventDefault(),this.dialog.showModal()}close(e){e.preventDefault(),this.dialog.close()}add(e){e.preventDefault();const n=e.target.closest("li").querySelector("template").content.querySelector("li").cloneNode(!0),i=this.currentItem;i?(i.insertAdjacentElement("beforebegin",n),new t(n).depth=new t(i).depth):this.list.insertAdjacentElement("beforeend",n),this.toggleInline(!1),this.dialog.close(),requestAnimationFrame((()=>{n.querySelector('[value="edit"]').click()}))}morph(e){e.preventDefault(),this.dialog.close()}move=e=>{if(this.isOverInlineTarget(e))return;if(this.dialog.open)return;const t=this.getCurrentItem(e);this.currentItem!==t&&(this.currentItem&&this.toggleInline(!1),this.currentItem=t,this.timer&&clearTimeout(this.timer),this.timer=setTimeout((()=>{delete this.timer,this.toggleInline()}),100))};toggleInline(e=!!this.currentItem){e?(this.inlineTarget.style.top=`${this.currentItem.offsetTop}px`,this.inlineTarget.toggleAttribute("hidden",!1)):this.inlineTarget.toggleAttribute("hidden",!0)}get dialog(){return this.element.querySelector("dialog")}get form(){return this.element.closest("form")}get list(){return this.form.querySelector('[data-controller="navigation--editor--list"]')}getCurrentItem(e){const t=document.elementFromPoint(e.clientX,e.clientY).closest("li");if(!t)return null;const n=t.getBoundingClientRect();return e.clientX<n.left+n.width/2-48||e.clientX>n.left+n.width/2+48?null:e.clientY-n.y<=24?t:n.y+n.height-e.clientY<=24?t.nextElementSibling:null}isOverInlineTarget(e){return this.inlineTarget===document.elementFromPoint(e.clientX,e.clientY).closest("div")}}},{identifier:"navigation--editor--status-bar",controllerConstructor:class extends e{connect(){this.versionState=this.element.dataset.state}morph(e){e.target===this.element&&(this.versionState=this.element.dataset.state,this.update({dirty:!1}))}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