katalyst-navigation 1.8.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -2
  3. data/app/assets/builds/katalyst/navigation.esm.js +207 -109
  4. data/app/assets/builds/katalyst/navigation.js +207 -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 +54 -0
  41. data/app/javascript/navigation/editor/list_controller.js +11 -103
  42. data/app/javascript/navigation/editor/new_items_controller.js +145 -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 +40 -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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba532b593b5abf0a722dccafede0ffb1c321080b82b9d1f0bd6294b4b2061bb1
4
- data.tar.gz: c21364fa3de9868564964cf23b90493e0250ef59e43c9469d2ed87095171d0df
3
+ metadata.gz: c156092dfddc81eae2c946543d921bc2da92d7a65b4ac1ce1186715104420d53
4
+ data.tar.gz: 29732312e198d827da22b4555f2f1a1ef86313cdc877ca0dc74e2761be24a032
5
5
  SHA512:
6
- metadata.gz: 9b2a8856c3283b2a1b2141eedfa29462b95efe8e1918a24c346a0278b4b6c4cdf423616d8c9a9156938d4344849b3fa251f921680cffffd0d8580018040698e2
7
- data.tar.gz: 51d6e8fb93d4550c3b29af9645d684ea376eae1d3fb283d32205845a453b77cd340101e7183f1c566db15b51d3a3e07d642cf26baeca4bd27c0745df2a1afaf9
6
+ metadata.gz: 3784055b26abc2a204eeed9089d14551b322ad8df9f1758f3525f7cf297878175c24376d680a95c9dcea0f2e841ad5421789e9f9fe7190257afbd7ed12799022
7
+ data.tar.gz: 3ee6d69f1e7aadcea6ce1c8dd9626378ff3abae3933b1adf6480cb029793062bd45b7c1b5a045015326cccbe3cc8ec306b7c66e0f98402853b450badc935bd5c
data/README.md CHANGED
@@ -23,7 +23,7 @@ rake katalyst_navigation:install:migrations
23
23
  ```
24
24
 
25
25
  Add the Gem's javascript and CSS to your build pipeline. This assumes that
26
- you're using `rails-dartsass` and `importmaps` to manage your assets.
26
+ you're using `propshaft` and `importmaps` to manage your assets.
27
27
 
28
28
  ```javascript
29
29
  // app/javascript/controllers/application.js
@@ -32,7 +32,16 @@ import navigation from "@katalyst/navigation";
32
32
  application.load(navigation);
33
33
  ```
34
34
 
35
- ```sass
35
+ Import the styles as css:
36
+
37
+ ```css
38
+ @import "/katalyst/navigation.css";
39
+ ```
40
+
41
+ Or, if you're using `dartsass-rails`:
42
+
43
+ ```scss
44
+ // app/assets/stylesheets/application.scss
36
45
  @use "katalyst/navigation";
37
46
  ```
38
47
 
@@ -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,59 @@ 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);
752
+ }
753
+
754
+ disconnect() {
755
+ this.element.removeEventListener("turbo:submit-end", this.onSubmit);
756
+ }
757
+
758
+ dismiss() {
759
+ if (!this.dialogTarget) return;
760
+ if (!this.dialogTarget.open) this.dialogTarget.close();
761
+
762
+ if (!("itemPersisted" in this.dialogTarget.dataset)) {
763
+ this.#removeTargetItem();
764
+ }
765
+
766
+ this.element.removeAttribute("src");
767
+ this.dialogTarget.remove();
768
+ }
769
+
770
+ dialogTargetConnected(dialog) {
771
+ dialog.showModal();
750
772
  }
751
773
 
774
+ onSubmit = (event) => {
775
+ if (event.detail.success) {
776
+ this.dialogTarget.close();
777
+ this.element.removeAttribute("src");
778
+ this.dialogTarget.remove();
779
+ }
780
+ };
781
+
782
+ noop() {}
783
+
784
+ #removeTargetItem() {
785
+ const el = document.getElementById(this.dialogTarget.dataset.itemId);
786
+ const item = new Item(el.closest("[data-navigation-item]"));
787
+ const list = item.node.parentElement;
788
+
789
+ item.node.remove();
790
+
791
+ this.dispatch("reindex", {
792
+ target: list,
793
+ bubbles: true,
794
+ prefix: "navigation",
795
+ });
796
+ }
797
+ }
798
+
799
+ class ListController extends Controller {
752
800
  /**
753
801
  * When the user starts a drag within the list, set the item's dataTransfer
754
802
  * properties to indicate that it's being dragged and update its style.
@@ -772,11 +820,6 @@ class ListController extends Controller {
772
820
  * When the user drags an item over another item in the last, swap the
773
821
  * dragging item with the item under the cursor.
774
822
  *
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
823
  * @param event {DragEvent}
781
824
  */
782
825
  dragover(event) {
@@ -790,53 +833,7 @@ class ListController extends Controller {
790
833
  }
791
834
 
792
835
  /**
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.
836
+ * When the user drops an item, end the drag and reindex the list.
840
837
  *
841
838
  * @param event {DragEvent}
842
839
  */
@@ -849,18 +846,6 @@ class ListController extends Controller {
849
846
  delete item.dataset.dragging;
850
847
  swap(dropTarget(event.target), item);
851
848
 
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
849
  this.dispatch("drop", {
865
850
  target: item,
866
851
  bubbles: true,
@@ -869,15 +854,13 @@ class ListController extends Controller {
869
854
  }
870
855
 
871
856
  /**
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.
857
+ * End an in-progress drag by resetting the item's style and restoring its
858
+ * original position in the list.
874
859
  */
875
860
  dragend() {
876
861
  const item = this.dragItem;
877
862
 
878
- if (!item) ; else if (item.dataset.hasOwnProperty("newItem")) {
879
- item.remove();
880
- } else {
863
+ if (item) {
881
864
  delete item.dataset.dragging;
882
865
  this.reset();
883
866
  }
@@ -901,7 +884,7 @@ class ListController extends Controller {
901
884
  }
902
885
 
903
886
  /**
904
- * Swaps two list items. If target is a list, the item is appended.
887
+ * Swaps two list items.
905
888
  *
906
889
  * @param target the target element to swap with
907
890
  * @param item the item the user is dragging
@@ -910,51 +893,162 @@ function swap(target, item) {
910
893
  if (!target) return;
911
894
  if (target === item) return;
912
895
 
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);
896
+ const positionComparison = target.compareDocumentPosition(item);
897
+ if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {
898
+ target.insertAdjacentElement("beforebegin", item);
899
+ } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {
900
+ target.insertAdjacentElement("afterend", item);
924
901
  }
925
902
  }
926
903
 
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
904
  /**
940
905
  * Given an event target, return the closest drop target, if any.
941
906
  */
942
907
  function dropTarget(e) {
943
- return (
944
- e &&
945
- (e.closest("[data-controller='navigation--editor--list'] > *") ||
946
- e.closest("[data-controller='navigation--editor--list']"))
947
- );
908
+ return e && e.closest("[data-controller='navigation--editor--list'] > *");
948
909
  }
949
910
 
950
- class NewItemController extends Controller {
951
- static targets = ["template"];
911
+ const EDGE_AREA = 24;
952
912
 
953
- dragstart(event) {
954
- if (this.element !== event.target) return;
913
+ class NewItemsController extends Controller {
914
+ static targets = ["inline"];
955
915
 
956
- event.dataTransfer.setData("text/html", this.templateTarget.innerHTML);
957
- event.dataTransfer.effectAllowed = "copy";
916
+ connect() {
917
+ this.form.addEventListener("mousemove", this.move);
918
+ }
919
+
920
+ disconnect() {
921
+ this.form?.removeEventListener("mousemove", this.move);
922
+ delete this.currentItem;
923
+ }
924
+
925
+ open(e) {
926
+ e.preventDefault();
927
+ this.dialog.showModal();
928
+ }
929
+
930
+ close(e) {
931
+ e.preventDefault();
932
+ this.dialog.close();
933
+ }
934
+
935
+ noop(e) {}
936
+
937
+ /**
938
+ * Add the selected item to the DOM at the current position or the end of the list.
939
+ */
940
+ add(e) {
941
+ e.preventDefault();
942
+
943
+ const template = e.target.querySelector("template");
944
+ const item = template.content.querySelector("li").cloneNode(true);
945
+ const target = this.currentItem;
946
+
947
+ if (target) {
948
+ target.insertAdjacentElement("beforebegin", item);
949
+ } else {
950
+ this.list.insertAdjacentElement("beforeend", item);
951
+ }
952
+
953
+ this.toggleInline(false);
954
+ this.dialog.close();
955
+
956
+ requestAnimationFrame(() => {
957
+ item.querySelector(`[value="edit"]`).click();
958
+ });
959
+ }
960
+
961
+ morph(e) {
962
+ e.preventDefault();
963
+ this.dialog.close();
964
+ }
965
+
966
+ move = (e) => {
967
+ if (this.isOverInlineTarget(e)) return;
968
+ if (this.dialog.open) return;
969
+
970
+ const target = this.getCurrentItem(e);
971
+
972
+ // return if we're already showing this item
973
+ if (this.currentItem === target) return;
974
+
975
+ // hide the button if it's already visible
976
+ if (this.currentItem) this.toggleInline(false);
977
+
978
+ this.currentItem = target;
979
+
980
+ // clear any previously set timer
981
+ if (this.timer) clearTimeout(this.timer);
982
+
983
+ // show the button after a debounce pause
984
+ this.timer = setTimeout(() => {
985
+ delete this.timer;
986
+ this.toggleInline();
987
+ }, 100);
988
+ };
989
+
990
+ toggleInline(show = !!this.currentItem) {
991
+ if (show) {
992
+ this.inlineTarget.style.top = `${this.currentItem.offsetTop}px`;
993
+ this.inlineTarget.toggleAttribute("hidden", false);
994
+ } else {
995
+ this.inlineTarget.toggleAttribute("hidden", true);
996
+ }
997
+ }
998
+
999
+ get dialog() {
1000
+ return this.element.querySelector("dialog");
1001
+ }
1002
+
1003
+ /**
1004
+ * @returns {HTMLFormElement}
1005
+ */
1006
+ get form() {
1007
+ return this.element.closest("form");
1008
+ }
1009
+
1010
+ /**
1011
+ * @returns {HTMLUListElement,null}
1012
+ */
1013
+ get list() {
1014
+ return this.form.querySelector(
1015
+ `[data-controller="navigation--editor--list"]`,
1016
+ );
1017
+ }
1018
+
1019
+ /**
1020
+ * @param {MouseEvent} e
1021
+ * @returns {HTMLLIElement,null}
1022
+ */
1023
+ getCurrentItem(e) {
1024
+ const item = document.elementFromPoint(e.clientX, e.clientY).closest("li");
1025
+ if (!item) return null;
1026
+
1027
+ const bounds = item.getBoundingClientRect();
1028
+
1029
+ // check X for center(ish) mouse position
1030
+ if (e.clientX < bounds.left + bounds.width / 2 - 2 * EDGE_AREA) return null;
1031
+ if (e.clientX > bounds.left + bounds.width / 2 + 2 * EDGE_AREA) return null;
1032
+
1033
+ // check Y for hits on this item or it's next sibling
1034
+ if (e.clientY - bounds.y <= EDGE_AREA) {
1035
+ return item;
1036
+ } else if (bounds.y + bounds.height - e.clientY <= EDGE_AREA) {
1037
+ return item.nextElementSibling;
1038
+ } else {
1039
+ return null;
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * @param {MouseEvent} e
1045
+ * @returns {Boolean} true when the target of the event is the floating button
1046
+ */
1047
+ isOverInlineTarget(e) {
1048
+ return (
1049
+ this.inlineTarget ===
1050
+ document.elementFromPoint(e.clientX, e.clientY).closest("div")
1051
+ );
958
1052
  }
959
1053
  }
960
1054
 
@@ -995,13 +1089,17 @@ const Definitions = [
995
1089
  identifier: "navigation--editor--item",
996
1090
  controllerConstructor: ItemController,
997
1091
  },
1092
+ {
1093
+ identifier: "navigation--editor--item-editor",
1094
+ controllerConstructor: ItemEditorController,
1095
+ },
998
1096
  {
999
1097
  identifier: "navigation--editor--list",
1000
1098
  controllerConstructor: ListController,
1001
1099
  },
1002
1100
  {
1003
- identifier: "navigation--editor--new-item",
1004
- controllerConstructor: NewItemController,
1101
+ identifier: "navigation--editor--new-items",
1102
+ controllerConstructor: NewItemsController,
1005
1103
  },
1006
1104
  {
1007
1105
  identifier: "navigation--editor--status-bar",