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.
- checksums.yaml +4 -4
- data/README.md +11 -2
- data/app/assets/builds/katalyst/navigation.esm.js +215 -109
- data/app/assets/builds/katalyst/navigation.js +215 -109
- data/app/assets/builds/katalyst/navigation.min.js +1 -1
- data/app/assets/builds/katalyst/navigation.min.js.map +1 -1
- data/app/assets/images/katalyst/navigation/icons/button.svg +1 -0
- data/app/assets/images/katalyst/navigation/icons/collapse.svg +3 -0
- data/app/assets/images/katalyst/navigation/icons/edit.svg +1 -0
- data/app/assets/images/katalyst/navigation/icons/expand.svg +3 -0
- data/app/assets/images/katalyst/navigation/icons/heading.svg +1 -0
- data/app/assets/images/katalyst/navigation/icons/hidden.svg +1 -0
- data/app/assets/images/katalyst/navigation/icons/indent.svg +3 -0
- data/app/assets/images/katalyst/navigation/icons/link.svg +1 -0
- data/app/assets/images/katalyst/navigation/icons/outdent.svg +4 -0
- data/app/assets/images/katalyst/navigation/icons/remove.svg +1 -0
- data/app/assets/stylesheets/katalyst/_navigation.scss +3 -0
- data/app/assets/stylesheets/katalyst/navigation/editor.css +175 -0
- data/app/assets/stylesheets/katalyst/navigation/icons.css +54 -0
- data/app/assets/stylesheets/katalyst/navigation/{editor/_status-bar.scss → status-bar.css} +30 -34
- data/app/assets/stylesheets/katalyst/navigation.css +3 -0
- data/app/components/katalyst/navigation/editor/base_component.rb +0 -6
- data/app/components/katalyst/navigation/editor/item_component.html.erb +26 -16
- data/app/components/katalyst/navigation/editor/item_component.rb +2 -3
- data/app/components/katalyst/navigation/editor/item_editor_component.rb +16 -19
- data/app/components/katalyst/navigation/editor/new_item_component.html.erb +15 -14
- data/app/components/katalyst/navigation/editor/new_item_component.rb +7 -10
- data/app/components/katalyst/navigation/editor/new_items_component.html.erb +41 -2
- data/app/components/katalyst/navigation/editor/new_items_component.rb +0 -2
- data/app/components/katalyst/navigation/editor/row_component.html.erb +2 -1
- data/app/components/katalyst/navigation/editor/status_bar_component.rb +9 -11
- data/app/components/katalyst/navigation/editor/table_component.html.erb +0 -6
- data/app/components/katalyst/navigation/editor/table_component.rb +12 -14
- data/app/components/katalyst/navigation/editor_component.html.erb +7 -0
- data/app/components/katalyst/navigation/editor_component.rb +13 -16
- data/app/controllers/katalyst/navigation/items_controller.rb +1 -1
- data/app/controllers/katalyst/navigation/menus_controller.rb +1 -1
- data/app/javascript/navigation/application.js +8 -3
- data/app/javascript/navigation/editor/item.js +1 -1
- data/app/javascript/navigation/editor/item_editor_controller.js +59 -0
- data/app/javascript/navigation/editor/list_controller.js +11 -103
- data/app/javascript/navigation/editor/new_items_controller.js +150 -0
- data/app/views/katalyst/navigation/items/_button.html.erb +24 -28
- data/app/views/katalyst/navigation/items/_form.html.erb +6 -0
- data/app/views/katalyst/navigation/items/_form_errors.html.erb +1 -1
- data/app/views/katalyst/navigation/items/_heading.html.erb +12 -14
- data/app/views/katalyst/navigation/items/_link.html.erb +24 -26
- data/app/views/katalyst/navigation/items/edit.html.erb +39 -2
- data/app/views/katalyst/navigation/items/edit.turbo_stream.erb +2 -0
- data/app/views/katalyst/navigation/items/update.turbo_stream.erb +0 -1
- data/app/views/katalyst/navigation/menus/show.html.erb +0 -1
- data/lib/katalyst/navigation/engine.rb +0 -1
- metadata +21 -28
- data/app/assets/stylesheets/katalyst/navigation/editor/_icon.scss +0 -17
- data/app/assets/stylesheets/katalyst/navigation/editor/_index.scss +0 -145
- data/app/assets/stylesheets/katalyst/navigation/editor/_item-actions.scss +0 -93
- data/app/assets/stylesheets/katalyst/navigation/editor/_item-rules.scss +0 -19
- data/app/assets/stylesheets/katalyst/navigation/editor/_new-items.scss +0 -67
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7673f31e3890ef3021ee1e1b5c1ba29f84708a6bcf84f316b9598c5f87fdc675
|
4
|
+
data.tar.gz: 18a0af06707489433eb2313c9dc5d2e568827746a007be03444598f65d65e5ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b6c0279c73bae208e257ae7b35f961a36f975a04b2994805a5286ce3ff92187caaf3174d0f36045947b2adc37a5e2bde3c0af5f6bcb20e91340f6f2e0b71de2
|
7
|
+
data.tar.gz: 491585bcfc806c6d52e76a4f959a82304af56bbf1111d022b3a822fb55d8f504bef6fb204d9429cfbb14597cbe975496a0588893e09719ac6e0a9ccbc72b86b8
|
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 `
|
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
|
-
|
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.
|
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
|
747
|
+
class ItemEditorController extends Controller {
|
748
|
+
static targets = ["dialog"];
|
749
|
+
|
748
750
|
connect() {
|
749
|
-
this.
|
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
|
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
|
873
|
-
*
|
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 (
|
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.
|
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
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
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
|
-
|
951
|
-
static targets = ["template"];
|
916
|
+
const EDGE_AREA = 24;
|
952
917
|
|
953
|
-
|
954
|
-
|
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
|
-
|
957
|
-
|
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-
|
1004
|
-
controllerConstructor:
|
1109
|
+
identifier: "navigation--editor--new-items",
|
1110
|
+
controllerConstructor: NewItemsController,
|
1005
1111
|
},
|
1006
1112
|
{
|
1007
1113
|
identifier: "navigation--editor--status-bar",
|