@geotab/zenith 3.5.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +4 -0
  2. package/dist/index.css +57 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +5 -3
  5. package/dist/list/hooks/useDragAndDrop.d.ts +19 -1
  6. package/dist/list/hooks/useDragAndDrop.js +65 -26
  7. package/dist/list/utils/findElement.d.ts +1 -0
  8. package/dist/list/utils/findElement.js +13 -0
  9. package/dist/list/utils/findItemPosition.d.ts +1 -1
  10. package/dist/list/utils/findItemPosition.js +2 -2
  11. package/dist/list/utils/isDragNotClick.d.ts +6 -0
  12. package/dist/list/utils/isDragNotClick.js +9 -0
  13. package/dist/nav/context/nav.context.d.ts +2 -0
  14. package/dist/nav/context/nav.context.js +3 -1
  15. package/dist/nav/nav.js +20 -8
  16. package/dist/nav/navEditList/navEditList.d.ts +7 -0
  17. package/dist/nav/navEditList/navEditList.js +22 -0
  18. package/dist/nav/navEditList/navEditListUtils.d.ts +3 -0
  19. package/dist/nav/navEditList/navEditListUtils.js +14 -0
  20. package/dist/nav/navEditSection/navEditSection.d.ts +3 -0
  21. package/dist/nav/navEditSection/navEditSection.js +7 -0
  22. package/dist/nav/navFooter/navEditFooter/navEditFooter.d.ts +11 -0
  23. package/dist/nav/navFooter/navEditFooter/navEditFooter.js +110 -0
  24. package/dist/nav/navFooter/navFooter.d.ts +2 -7
  25. package/dist/nav/navFooter/navFooter.js +4 -4
  26. package/dist/nav/navItem/navActionItem.d.ts +14 -0
  27. package/dist/nav/navItem/navActionItem.js +15 -0
  28. package/esm/index.d.ts +1 -0
  29. package/esm/index.js +1 -0
  30. package/esm/list/hooks/useDragAndDrop.d.ts +19 -1
  31. package/esm/list/hooks/useDragAndDrop.js +65 -26
  32. package/esm/list/utils/findElement.d.ts +1 -0
  33. package/esm/list/utils/findElement.js +9 -0
  34. package/esm/list/utils/findItemPosition.d.ts +1 -1
  35. package/esm/list/utils/findItemPosition.js +2 -2
  36. package/esm/list/utils/isDragNotClick.d.ts +6 -0
  37. package/esm/list/utils/isDragNotClick.js +5 -0
  38. package/esm/nav/context/nav.context.d.ts +2 -0
  39. package/esm/nav/context/nav.context.js +3 -1
  40. package/esm/nav/nav.js +21 -9
  41. package/esm/nav/navEditList/navEditList.d.ts +7 -0
  42. package/esm/nav/navEditList/navEditList.js +18 -0
  43. package/esm/nav/navEditList/navEditListUtils.d.ts +3 -0
  44. package/esm/nav/navEditList/navEditListUtils.js +10 -0
  45. package/esm/nav/navEditSection/navEditSection.d.ts +3 -0
  46. package/esm/nav/navEditSection/navEditSection.js +3 -0
  47. package/esm/nav/navFooter/navEditFooter/navEditFooter.d.ts +11 -0
  48. package/esm/nav/navFooter/navEditFooter/navEditFooter.js +101 -0
  49. package/esm/nav/navFooter/navFooter.d.ts +2 -7
  50. package/esm/nav/navFooter/navFooter.js +4 -4
  51. package/esm/nav/navItem/navActionItem.d.ts +14 -0
  52. package/esm/nav/navItem/navActionItem.js +11 -0
  53. package/package.json +4 -4
  54. package/dist/list/utils/findListElement.d.ts +0 -1
  55. package/dist/list/utils/findListElement.js +0 -13
  56. package/esm/list/utils/findListElement.d.ts +0 -1
  57. package/esm/list/utils/findListElement.js +0 -9
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+
3
+ const {
4
+ injectString
5
+ } = require("../../../utils/localization/translationsDictionary");
6
+ Object.defineProperty(exports, "__esModule", {
7
+ value: true
8
+ });
9
+ exports.NavEditFooter = void 0;
10
+ const jsx_runtime_1 = require("react/jsx-runtime");
11
+ injectString("cs", "Save", "Ulo\u017Eit");
12
+ injectString("da-DK", "Save", "Spare");
13
+ injectString("de", "Save", "Speichern");
14
+ injectString("en", "Save", "Save");
15
+ injectString("es", "Save", "Guardar");
16
+ injectString("fi-FI", "Save", "S\xE4\xE4st\xE4\xE4");
17
+ injectString("fr", "Save", "Enregistrer");
18
+ injectString("fr-FR", "Save", "Enregistrer");
19
+ injectString("hu-HU", "Save", "Ment\xE9s");
20
+ injectString("id", "Save", "Simpan");
21
+ injectString("it", "Save", "Salvare");
22
+ injectString("ja", "Save", "\u4FDD\u5B58");
23
+ injectString("ko-KR", "Save", "\uC800\uC7A5");
24
+ injectString("ms", "Save", "Simpan");
25
+ injectString("nb-NO", "Save", "Lagre");
26
+ injectString("nl", "Save", "Opslaan");
27
+ injectString("pl", "Save", "Zapisz");
28
+ injectString("pt-BR", "Save", "Salvar");
29
+ injectString("pt-PT", "Save", "Guardar");
30
+ injectString("sk-SK", "Save", "Ulo\u017Ei\u0165");
31
+ injectString("sv", "Save", "Spara");
32
+ injectString("th", "Save", "\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01");
33
+ injectString("tr", "Save", "Kaydet");
34
+ injectString("zh-Hans", "Save", "\u4FDD\u5B58");
35
+ injectString("zh-TW", "Save", "\u5132\u5B58");
36
+ injectString("ro-RO", "Save", "Salva\u021Bi");
37
+ injectString("cs", "Cancel", "Zru\u0161it");
38
+ injectString("da-DK", "Cancel", "Annuller");
39
+ injectString("de", "Cancel", "Abbrechen");
40
+ injectString("en", "Cancel", "Cancel");
41
+ injectString("es", "Cancel", "Cancelar");
42
+ injectString("fi-FI", "Cancel", "Peruuta");
43
+ injectString("fr", "Cancel", "Annuler");
44
+ injectString("fr-FR", "Cancel", "Annuler");
45
+ injectString("hu-HU", "Cancel", "M\xE9gse");
46
+ injectString("id", "Cancel", "Batalkan");
47
+ injectString("it", "Cancel", "Annullare");
48
+ injectString("ja", "Cancel", "\u30AD\u30E3\u30F3\u30BB\u30EB");
49
+ injectString("ko-KR", "Cancel", "\uCDE8\uC18C");
50
+ injectString("ms", "Cancel", "Batal");
51
+ injectString("nb-NO", "Cancel", "Avbryt");
52
+ injectString("nl", "Cancel", "Annuleren");
53
+ injectString("pl", "Cancel", "Anuluj");
54
+ injectString("pt-BR", "Cancel", "Cancelar");
55
+ injectString("pt-PT", "Cancel", "Cancelar");
56
+ injectString("sk-SK", "Cancel", "Zru\u0161.");
57
+ injectString("sv", "Cancel", "Avbryt");
58
+ injectString("th", "Cancel", "\u0E22\u0E01\u0E40\u0E25\u0E34\u0E01");
59
+ injectString("tr", "Cancel", "\u0130ptal");
60
+ injectString("zh-Hans", "Cancel", "\u53D6\u6D88");
61
+ injectString("zh-TW", "Cancel", "\u53D6\u6D88");
62
+ injectString("ro-RO", "Cancel", "Anula\u021Bi");
63
+ const button_1 = require("../../../button/button");
64
+ const classNames_1 = require("../../../commonHelpers/classNames/classNames");
65
+ const useLanguage_1 = require("../../../utils/localization/useLanguage");
66
+ const nav_context_1 = require("../../context/nav.context");
67
+ const react_1 = require("react");
68
+ const useMobile_1 = require("../../../commonHelpers/hooks/useMobile");
69
+ const NavEditFooter = ({
70
+ className,
71
+ handleSaveClick,
72
+ handleCancelClick
73
+ }) => {
74
+ const {
75
+ onEditStateToggle
76
+ } = (0, nav_context_1.useNavContext)();
77
+ const isMobile = (0, useMobile_1.useMobile)();
78
+ const {
79
+ translate
80
+ } = (0, useLanguage_1.useLanguage)();
81
+ const onSaveClick = (0, react_1.useCallback)(() => {
82
+ onEditStateToggle === null || onEditStateToggle === void 0 ? void 0 : onEditStateToggle(false);
83
+ handleSaveClick();
84
+ }, [onEditStateToggle, handleSaveClick]);
85
+ const onCancelClick = (0, react_1.useCallback)(() => {
86
+ onEditStateToggle === null || onEditStateToggle === void 0 ? void 0 : onEditStateToggle(false);
87
+ handleCancelClick();
88
+ }, [onEditStateToggle, handleCancelClick]);
89
+ const navEditFooterClassNames = (0, classNames_1.classNames)(["zen-nav-edit-footer", isMobile ? "zen-nav-edit-footer--mobile" : "", className !== null && className !== void 0 ? className : ""]);
90
+ // needed to change buttons order between mobile and desktop modes
91
+ const saveButtonClassNames = (0, classNames_1.classNames)(["zen-nav-edit-footer__button", isMobile ? "zen-nav-edit-footer__button--with-margin" : ""]);
92
+ const saveButton = (0, jsx_runtime_1.jsx)(button_1.Button, {
93
+ onClick: onSaveClick,
94
+ className: saveButtonClassNames,
95
+ type: "primary",
96
+ children: translate("Save")
97
+ });
98
+ const cancelButton = (0, jsx_runtime_1.jsx)(button_1.Button, {
99
+ onClick: onCancelClick,
100
+ className: "zen-nav-edit-footer__button",
101
+ type: "secondary",
102
+ children: translate("Cancel")
103
+ });
104
+ return (0, jsx_runtime_1.jsxs)("div", {
105
+ className: navEditFooterClassNames,
106
+ children: [isMobile ? saveButton : cancelButton, isMobile ? cancelButton : saveButton]
107
+ });
108
+ };
109
+ exports.NavEditFooter = NavEditFooter;
110
+ exports.NavEditFooter.displayName = "NavEditFooter";
@@ -3,11 +3,6 @@ import { IZenComponentProps } from "../../commonHelpers/zenComponent";
3
3
  import { FC } from "react";
4
4
  export interface INavFooter extends IZenComponentProps {
5
5
  }
6
- export interface INavFooterBottom extends IZenComponentProps {
7
- }
8
- export interface INavFooterEditButton extends IZenComponentProps {
9
- onClick?: () => void;
10
- }
11
6
  /**
12
7
  * @beta This component is not fully ready yet and may change in future releases.
13
8
  */
@@ -16,6 +11,6 @@ export declare const NavFooter: FC<INavFooter> & {
16
11
  ({ children }: import("./navFooterAction/navFooterAction").INavFooterActionProps): import("react/jsx-runtime").JSX.Element | null;
17
12
  displayName: string;
18
13
  };
19
- Bottom: FC<INavFooterBottom>;
20
- EditButton: FC<INavFooterEditButton>;
14
+ Bottom: FC<IZenComponentProps>;
15
+ EditButton: FC<IZenComponentProps>;
21
16
  };
@@ -73,14 +73,14 @@ const NavFooterBottom = ({
73
73
  };
74
74
  NavFooterBottom.displayName = "NavFooterBottom";
75
75
  const NavFooterEditButton = ({
76
- className,
77
- onClick
76
+ className
78
77
  }) => {
79
78
  const {
80
79
  translate
81
80
  } = (0, useLanguage_1.useLanguage)();
82
81
  const {
83
- collapsed
82
+ collapsed,
83
+ onEditStateToggle
84
84
  } = (0, nav_context_1.useNavContext)();
85
85
  const isMobile = (0, useMobile_1.useMobile)();
86
86
  return (0, jsx_runtime_1.jsx)(navItem_1.NavItem, {
@@ -88,7 +88,7 @@ const NavFooterEditButton = ({
88
88
  title: translate("Edit Navigation"),
89
89
  collapsed: !isMobile,
90
90
  primaryIcon: iconEdit_1.IconEdit,
91
- onClick: () => onClick && onClick(),
91
+ onClick: () => onEditStateToggle === null || onEditStateToggle === void 0 ? void 0 : onEditStateToggle(true),
92
92
  menuAlignment: "right-bottom",
93
93
  tooltipAlignment: collapsed ? "right" : "top"
94
94
  }, "edit-button");
@@ -0,0 +1,14 @@
1
+ import { FC, MouseEvent, ReactNode } from "react";
2
+ import { IZenComponentProps } from "../../commonHelpers/zenComponent";
3
+ import { IIcon } from "../../icons/icon";
4
+ import "./navItem.less";
5
+ export interface INavActionItem extends IZenComponentProps {
6
+ title: string;
7
+ id?: string;
8
+ icon?: FC<IIcon>;
9
+ startNode?: ReactNode;
10
+ endButtonIcon?: FC<IIcon>;
11
+ endButtonTitle?: string;
12
+ onEndButtonClick?: (e: MouseEvent) => void;
13
+ }
14
+ export declare const NavActionItem: FC<INavActionItem>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NavActionItem = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const classNames_1 = require("../../commonHelpers/classNames/classNames");
6
+ const NavActionItem = ({ title, id, icon, className, startNode, endButtonIcon, endButtonTitle, onEndButtonClick }) => {
7
+ const PrimaryIconComponent = typeof icon === "function" ? icon : null;
8
+ const EndButtonIconComponent = typeof endButtonIcon === "function" ? endButtonIcon : null;
9
+ const itemClasses = (0, classNames_1.classNames)(["zen-nav-item", className || ""]);
10
+ return ((0, jsx_runtime_1.jsxs)("div", { className: itemClasses, id: id, children: [(0, jsx_runtime_1.jsx)("div", { className: "zen-nav-item__main", children: (0, jsx_runtime_1.jsxs)("div", { className: "zen-nav-item__content-left", children: [startNode ? (0, jsx_runtime_1.jsx)("span", { className: "zen-nav-item__icon", children: startNode }) : null, PrimaryIconComponent ? (0, jsx_runtime_1.jsx)(PrimaryIconComponent, { size: "huge" }) : null, (0, jsx_runtime_1.jsx)("span", { className: "zen-nav-item__title", children: (0, jsx_runtime_1.jsx)("span", { className: "zen-nav-item__title-text", children: title }) })] }) }), EndButtonIconComponent && endButtonTitle && onEndButtonClick
11
+ ? (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zen-nav-item__action", "aria-label": endButtonTitle, title: endButtonTitle, onClick: onEndButtonClick, children: (0, jsx_runtime_1.jsx)(EndButtonIconComponent, { size: "huge" }) })
12
+ : null] }));
13
+ };
14
+ exports.NavActionItem = NavActionItem;
15
+ exports.NavActionItem.displayName = "NavActionItem";
package/esm/index.d.ts CHANGED
@@ -697,6 +697,7 @@ export { type IItemData, ItemData } from "./list/itemData/itemData";
697
697
  export { type IList, List } from "./list/list";
698
698
  export { type IListItem, ListItem } from "./list/listItem/listItem";
699
699
  export { changeOrder } from "./list/utils/changeOrder";
700
+ export { isDragNotClick } from "./list/utils/isDragNotClick";
700
701
  export { type IControlledMenu, ControlledMenu } from "./menu/controlledMenu";
701
702
  export { type IMenu, Menu } from "./menu/menu";
702
703
  export { findFirstFocusable } from "./menu/utils/findFirstFocusable";
package/esm/index.js CHANGED
@@ -684,6 +684,7 @@ export { ItemData } from "./list/itemData/itemData";
684
684
  export { List } from "./list/list";
685
685
  export { ListItem } from "./list/listItem/listItem";
686
686
  export { changeOrder } from "./list/utils/changeOrder";
687
+ export { isDragNotClick } from "./list/utils/isDragNotClick";
687
688
  export { ControlledMenu } from "./menu/controlledMenu";
688
689
  export { Menu } from "./menu/menu";
689
690
  export { findFirstFocusable } from "./menu/utils/findFirstFocusable";
@@ -1,4 +1,22 @@
1
1
  import { RefObject } from "react";
2
- export declare const useDragAndDrop: (containerRef: RefObject<HTMLElement | null>, onChangeOrder: (startPosition: number, endPosition: number) => void) => {
2
+ interface IMarkerClasses {
3
+ general: string;
4
+ placeholder: string;
5
+ dragging: string;
6
+ dragged?: string;
7
+ }
8
+ /**
9
+ * A custom React hook that provides drag-and-drop functionality for reorderable list items.
10
+ * @param containerRef - A React ref pointing to the container element that holds the draggable items
11
+ * @param onChangeOrder - Callback function invoked when an item is dropped at a new position.
12
+ * Receives the start position and end position of the dragged item
13
+ * @param marksClassNames - Optional custom CSS class names for marking different states of list items.
14
+ * If not provided, defaults to 'zen-list-item' based classes
15
+ * @param onBeforeDragOver - Optional callback function called before placing the placeholder at a new position.
16
+ * Receives the target position and should return false to prevent the drag operation.
17
+ * @returns An object containing the `onDragStart` event handler to be attached to draggable elements
18
+ */
19
+ export declare const useDragAndDrop: (containerRef: RefObject<HTMLElement | null>, onChangeOrder: (startPosition: number, endPosition: number) => void, marksClassNames?: IMarkerClasses, onBeforeDragOver?: (endPosition: number) => boolean) => {
3
20
  onDragStart: (e: React.PointerEvent<HTMLElement>) => void;
4
21
  };
22
+ export {};
@@ -1,32 +1,54 @@
1
1
  import { useCallback, useRef } from "react";
2
2
  import { getScrollableParent } from "../../utils/getScrollableParent";
3
- import { findListElement } from "../utils/findListElement";
3
+ import { findElement } from "../utils/findElement";
4
4
  import { useContainerScroll } from "./useContainerScroll";
5
5
  import { findItemPosition } from "../utils/findItemPosition";
6
- export const useDragAndDrop = (containerRef, onChangeOrder) => {
6
+ import { isDragNotClick } from "../utils/isDragNotClick";
7
+ /**
8
+ * A custom React hook that provides drag-and-drop functionality for reorderable list items.
9
+ * @param containerRef - A React ref pointing to the container element that holds the draggable items
10
+ * @param onChangeOrder - Callback function invoked when an item is dropped at a new position.
11
+ * Receives the start position and end position of the dragged item
12
+ * @param marksClassNames - Optional custom CSS class names for marking different states of list items.
13
+ * If not provided, defaults to 'zen-list-item' based classes
14
+ * @param onBeforeDragOver - Optional callback function called before placing the placeholder at a new position.
15
+ * Receives the target position and should return false to prevent the drag operation.
16
+ * @returns An object containing the `onDragStart` event handler to be attached to draggable elements
17
+ */
18
+ export const useDragAndDrop = (containerRef, onChangeOrder, marksClassNames, onBeforeDragOver) => {
7
19
  const dragImageRef = useRef(null);
8
20
  const dragMoveRef = useRef(undefined);
9
21
  const { moveScroll, cancelMoveScroll } = useContainerScroll();
22
+ const cssMarks = marksClassNames
23
+ ? marksClassNames
24
+ : {
25
+ general: "zen-list-item",
26
+ placeholder: "zen-list-item--placeholder",
27
+ dragging: "zen-list-item--dragging"
28
+ };
10
29
  const getPlaceholder = useCallback((height) => {
11
30
  const placeholder = document.createElement("li");
12
- placeholder.className = "zen-list-item zen-list-item--placeholder";
31
+ placeholder.className = `${cssMarks.general} ${cssMarks.placeholder}`;
13
32
  placeholder.style.height = height + "px";
14
33
  return placeholder;
15
- }, []);
34
+ }, [cssMarks.general, cssMarks.placeholder]);
16
35
  const removePlaceholders = useCallback((element) => {
17
- const placeholders = element.querySelectorAll(".zen-list-item--placeholder");
36
+ const placeholders = element.querySelectorAll(`.${cssMarks.placeholder}`);
18
37
  for (const placeholder of placeholders) {
19
38
  placeholder.remove();
20
39
  }
21
- }, []);
40
+ }, [cssMarks.placeholder]);
22
41
  const setPlaceholder = useCallback((element) => {
23
42
  if (!containerRef.current) {
24
43
  return;
25
44
  }
26
- const items = containerRef.current.querySelectorAll(".zen-list-item");
45
+ const items = containerRef.current.querySelectorAll(`.${cssMarks.general}`);
27
46
  const rect = element.getBoundingClientRect();
28
47
  const listRect = containerRef.current.getBoundingClientRect();
29
48
  if (rect.top < listRect.top) {
49
+ if (onBeforeDragOver && !onBeforeDragOver(0)) {
50
+ return;
51
+ }
30
52
  // If the dragged element is above the list, insert placeholder at the top
31
53
  const placeholder = getPlaceholder(rect.height);
32
54
  removePlaceholders(containerRef.current);
@@ -40,18 +62,25 @@ export const useDragAndDrop = (containerRef, onChangeOrder) => {
40
62
  return;
41
63
  }
42
64
  if (rect.top > listRect.bottom) {
65
+ if (onBeforeDragOver && !onBeforeDragOver(items.length)) {
66
+ return;
67
+ }
43
68
  const placeholder = getPlaceholder(rect.height);
44
69
  removePlaceholders(containerRef.current);
45
70
  containerRef.current.appendChild(placeholder);
46
71
  return;
47
72
  }
48
73
  const dragItemTopOffset = 4;
49
- for (const item of items) {
50
- if (item.classList.contains("zen-list-item--placeholder") || item.classList.contains("zen-list-item--dragging")) {
74
+ for (let i = 0; i < items.length; i++) {
75
+ const item = items[i];
76
+ if (item.classList.contains(cssMarks.placeholder) || item.classList.contains(cssMarks.dragging)) {
51
77
  continue;
52
78
  }
53
79
  const itemRect = item.getBoundingClientRect();
54
80
  if (item !== element && rect.top > itemRect.top + dragItemTopOffset && rect.top <= itemRect.bottom) {
81
+ if (onBeforeDragOver && !onBeforeDragOver(i + 1)) {
82
+ return;
83
+ }
55
84
  const placeholder = getPlaceholder(rect.height);
56
85
  removePlaceholders(containerRef.current);
57
86
  const next = item.nextSibling;
@@ -64,19 +93,24 @@ export const useDragAndDrop = (containerRef, onChangeOrder) => {
64
93
  return;
65
94
  }
66
95
  }
67
- }, [containerRef, getPlaceholder, removePlaceholders]);
96
+ }, [containerRef, getPlaceholder, removePlaceholders, onBeforeDragOver, cssMarks.general, cssMarks.placeholder, cssMarks.dragging]);
68
97
  const onDragStart = useCallback((e) => {
69
- const listItemElement = findListElement(e.currentTarget);
98
+ e.preventDefault();
99
+ const listItemElement = findElement(e.currentTarget, cssMarks.general);
70
100
  if (!listItemElement) {
71
101
  return;
72
102
  }
73
- const startPosition = findItemPosition(listItemElement);
103
+ const startPosition = findItemPosition(listItemElement, cssMarks.dragging);
74
104
  const rect = listItemElement.getBoundingClientRect();
75
105
  const offset = {
76
106
  x: e.clientX - rect.left,
77
107
  y: e.clientY - rect.top
78
108
  };
109
+ const initialCoords = { x: e.clientX, y: e.clientY };
79
110
  const div = listItemElement.cloneNode(true);
111
+ if (cssMarks.dragged) {
112
+ div.classList.add(cssMarks.dragged);
113
+ }
80
114
  document.body.appendChild(div);
81
115
  dragImageRef.current = div;
82
116
  div.style.position = "absolute";
@@ -84,16 +118,25 @@ export const useDragAndDrop = (containerRef, onChangeOrder) => {
84
118
  div.style.top = (e.clientY - offset.y) + "px";
85
119
  div.style.width = listItemElement.offsetWidth + "px";
86
120
  div.style.height = listItemElement.offsetHeight + "px";
121
+ let isDragStarted = false;
87
122
  dragMoveRef.current = (moveEvent) => {
88
123
  if (!dragImageRef.current) {
89
124
  return;
90
125
  }
91
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
92
- const pageScroll = window ? window.scrollY : 0;
93
- dragImageRef.current.style.left = (moveEvent.clientX - offset.x) + "px";
94
- dragImageRef.current.style.top = (pageScroll + moveEvent.clientY - offset.y) + "px";
95
- setPlaceholder(dragImageRef.current);
96
- moveEvent.preventDefault();
126
+ if (isDragStarted || isDragNotClick(initialCoords, { x: moveEvent.clientX, y: moveEvent.clientY })) {
127
+ if (!isDragStarted) {
128
+ isDragStarted = true;
129
+ listItemElement.classList.add(cssMarks.dragging);
130
+ const scrollableParent = getScrollableParent(containerRef.current);
131
+ moveScroll((scrollableParent || containerRef.current), dragImageRef.current, () => setPlaceholder(dragImageRef.current));
132
+ }
133
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
134
+ const pageScroll = window ? window.scrollY : 0;
135
+ dragImageRef.current.style.left = (moveEvent.clientX - offset.x) + "px";
136
+ dragImageRef.current.style.top = (pageScroll + moveEvent.clientY - offset.y) + "px";
137
+ setPlaceholder(dragImageRef.current);
138
+ moveEvent.preventDefault();
139
+ }
97
140
  };
98
141
  const onDragEnd = () => {
99
142
  var _a;
@@ -104,9 +147,9 @@ export const useDragAndDrop = (containerRef, onChangeOrder) => {
104
147
  if (dragMoveRef.current) {
105
148
  document.body.removeEventListener("pointermove", dragMoveRef.current, false);
106
149
  }
107
- const placeholderElement = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(".zen-list-item--placeholder");
108
- const endPosition = placeholderElement ? findItemPosition(placeholderElement) : null;
109
- listItemElement.classList.remove("zen-list-item--dragging");
150
+ const placeholderElement = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${cssMarks.placeholder}`);
151
+ const endPosition = placeholderElement ? findItemPosition(placeholderElement, cssMarks.dragging) : null;
152
+ listItemElement.classList.remove(cssMarks.dragging);
110
153
  if (containerRef.current) {
111
154
  removePlaceholders(containerRef.current);
112
155
  }
@@ -121,10 +164,6 @@ export const useDragAndDrop = (containerRef, onChangeOrder) => {
121
164
  document.body.addEventListener("pointermove", dragMoveRef.current, false);
122
165
  document.body.addEventListener("pointerup", onDragEnd, false);
123
166
  document.addEventListener("mouseleave", onDragEnd);
124
- listItemElement.classList.add("zen-list-item--dragging");
125
- setPlaceholder(dragImageRef.current);
126
- const scrollableParent = getScrollableParent(containerRef.current);
127
- moveScroll((scrollableParent || containerRef.current), dragImageRef.current, () => setPlaceholder(dragImageRef.current));
128
- }, [cancelMoveScroll, containerRef, moveScroll, onChangeOrder, removePlaceholders, setPlaceholder]);
167
+ }, [cancelMoveScroll, containerRef, moveScroll, onChangeOrder, removePlaceholders, setPlaceholder, cssMarks.general, cssMarks.placeholder, cssMarks.dragging, cssMarks.dragged]);
129
168
  return { onDragStart };
130
169
  };
@@ -0,0 +1 @@
1
+ export declare const findElement: (element: HTMLElement, className: string) => HTMLElement | null;
@@ -0,0 +1,9 @@
1
+ export const findElement = (element, className) => {
2
+ if (element.classList.contains(className)) {
3
+ return element;
4
+ }
5
+ if (element.parentElement) {
6
+ return findElement(element.parentElement, className);
7
+ }
8
+ return null;
9
+ };
@@ -1 +1 @@
1
- export declare const findItemPosition: (element: HTMLElement) => number;
1
+ export declare const findItemPosition: (element: HTMLElement, draggingClass?: string) => number;
@@ -1,4 +1,4 @@
1
- export const findItemPosition = (element) => {
1
+ export const findItemPosition = (element, draggingClass = "zen-list-item--dragging") => {
2
2
  const parent = element.parentElement;
3
3
  if (!parent) {
4
4
  return -1;
@@ -6,7 +6,7 @@ export const findItemPosition = (element) => {
6
6
  let ignoreOriginalElement = false;
7
7
  for (let i = 0; i < parent.children.length; i++) {
8
8
  const currentChild = parent.children[i];
9
- ignoreOriginalElement = ignoreOriginalElement || currentChild.classList.contains("zen-list-item--dragging");
9
+ ignoreOriginalElement = ignoreOriginalElement || currentChild.classList.contains(draggingClass);
10
10
  if (currentChild === element) {
11
11
  return i - (ignoreOriginalElement ? 1 : 0);
12
12
  }
@@ -0,0 +1,6 @@
1
+ interface ICoord {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ export declare const isDragNotClick: (initialCoords: ICoord, currentCoords: ICoord, threshold?: number) => boolean;
6
+ export {};
@@ -0,0 +1,5 @@
1
+ export const isDragNotClick = (initialCoords, currentCoords, threshold = 5) => {
2
+ const xShift = initialCoords.x - currentCoords.x;
3
+ const yShift = initialCoords.y - currentCoords.y;
4
+ return Math.sqrt(xShift * xShift + yShift * yShift) > threshold;
5
+ };
@@ -7,6 +7,8 @@ export interface INavContext {
7
7
  searchOpen?: boolean;
8
8
  onSearchToggle?: (isOpen: boolean) => void;
9
9
  onNavigate?: () => void;
10
+ isEditState?: boolean;
11
+ onEditStateToggle?: (isEdit: boolean) => void;
10
12
  }
11
13
  /**
12
14
  * @beta This component is not fully ready yet and may change in future releases.
@@ -9,7 +9,9 @@ export const NavContext = createContext({
9
9
  onSearch: undefined,
10
10
  searchOpen: false,
11
11
  onSearchToggle: undefined,
12
- onNavigate: undefined
12
+ onNavigate: undefined,
13
+ isEditState: false,
14
+ onEditStateToggle: undefined
13
15
  });
14
16
  /**
15
17
  * @beta This component is not fully ready yet and may change in future releases.
package/esm/nav/nav.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { classNames } from "../commonHelpers/classNames/classNames";
3
- import { NavContext } from "./context/nav.context";
3
+ import { NavContext, useNavContext } from "./context/nav.context";
4
4
  import { Children, cloneElement, Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
5
5
  import { NavDivider } from "./navDivider/navDivider";
6
6
  import { detectOverlayScrollbar, filterPrimaryNavItems, collectNavItems } from "./utils/navUtils";
@@ -10,6 +10,7 @@ import { NavMobileBar } from "./navMobileBar/navMobileBar";
10
10
  import { useMobile } from "../commonHelpers/hooks/useMobile";
11
11
  import { FOCUSABLE_SELECTOR } from "../utils/focusableSelector";
12
12
  const NavContent = ({ children }) => {
13
+ const { isEditState } = useNavContext();
13
14
  const contentRef = useRef(null);
14
15
  const [hasScrollbar, setHasScrollbar] = useState(false);
15
16
  const [showTopShadow, setShowTopShadow] = useState(false);
@@ -40,8 +41,8 @@ const NavContent = ({ children }) => {
40
41
  }, [handleResize]);
41
42
  return _jsx("div", { ref: contentRef, onScroll: handleScroll, className: classNames([
42
43
  "zen-nav__content",
43
- showTopShadow ? "zen-nav__content--show-top-shadow" : "",
44
- showBottomShadow ? "zen-nav__content--show-bottom-shadow" : "",
44
+ showTopShadow && !isEditState ? "zen-nav__content--show-top-shadow" : "",
45
+ showBottomShadow && !isEditState ? "zen-nav__content--show-bottom-shadow" : "",
45
46
  hasScrollbar ? "zen-nav__content--has-scrollbar" : "",
46
47
  hasOverlayScrollbar ? "zen-nav__content--overlay-scrollbar" : ""
47
48
  ]), children: _jsx("div", { className: "zen-nav__content-inner", children: children }) });
@@ -53,6 +54,7 @@ export const Nav = ({ children, className, collapsed = false, onCollapseToggle,
53
54
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
54
55
  const menuRef = useRef(null);
55
56
  const [isSearchOpen, setIsSearchOpen] = useState(false);
57
+ const [isEditState, setIsEditState] = useState(false);
56
58
  const [searchTerm, setSearchTerm] = useState("");
57
59
  const isMobile = useMobile();
58
60
  const prevIsMobileRef = useRef(false);
@@ -68,16 +70,18 @@ export const Nav = ({ children, className, collapsed = false, onCollapseToggle,
68
70
  }
69
71
  }, [isMobile, isMobileMenuOpen]);
70
72
  const childrenArray = Children.toArray(children);
71
- const header = childrenArray.find(child => { var _a; return ((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) === "NavHeader"; });
73
+ const header = isEditState ? undefined : childrenArray.find(child => { var _a; return ((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) === "NavHeader"; });
72
74
  const sections = childrenArray.filter(child => { var _a; return ((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) === "NavSection"; });
73
75
  const footer = childrenArray.find(child => { var _a; return ((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) === "NavFooter"; });
76
+ const editFooter = childrenArray.find(child => { var _a; return ((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) === "NavEditFooter"; });
74
77
  const secondarySections = sections.map(section => filterPrimaryNavItems(section, true)).filter(section => !!section && Children.count(section.props.children) > 0);
75
78
  const primarySections = sections.map(section => filterPrimaryNavItems(section, false)).filter(section => !!section && Children.count(section.props.children) > 0);
76
79
  const sectionsToRender = isMobile ? secondarySections : sections;
77
80
  const secondaryFooter = footer && filterPrimaryNavItems(footer, true);
78
81
  const primaryFooter = footer && filterPrimaryNavItems(footer, false);
79
- const footerToRender = isMobile ? secondaryFooter : footer;
82
+ const footerToRender = isEditState ? editFooter : (isMobile ? secondaryFooter : footer);
80
83
  const primaryItems = [...primarySections, ...(primaryFooter ? [primaryFooter] : [])].flatMap(e => collectNavItems(e));
84
+ const editListItems = childrenArray.find(child => { var _a; return ((_a = child.type) === null || _a === void 0 ? void 0 : _a.name) === "NavEditSection"; });
81
85
  const onSearchOpen = useCallback((isOpen) => {
82
86
  isOpen && onCollapseToggle && onCollapseToggle(false);
83
87
  setIsSearchOpen(isOpen || isMobile);
@@ -85,6 +89,10 @@ export const Nav = ({ children, className, collapsed = false, onCollapseToggle,
85
89
  const onMobileMenuToggle = useCallback(() => {
86
90
  setIsMobileMenuOpen(!isMobileMenuOpen);
87
91
  }, [isMobileMenuOpen, setIsMobileMenuOpen]);
92
+ const onEditStateToggle = useCallback((isEdit) => {
93
+ isEdit && onCollapseToggle && onCollapseToggle(false);
94
+ editListItems && editFooter && setIsEditState(isEdit);
95
+ }, [onCollapseToggle, setIsEditState, editListItems, editFooter]);
88
96
  useEffect(() => {
89
97
  const wasMobile = prevIsMobileRef.current;
90
98
  prevIsMobileRef.current = isMobile;
@@ -119,13 +127,17 @@ export const Nav = ({ children, className, collapsed = false, onCollapseToggle,
119
127
  collapsed,
120
128
  onCollapseToggle,
121
129
  searchOpen: isSearchOpen,
130
+ isEditState: isEditState,
131
+ onEditStateToggle,
122
132
  onSearchToggle: onSearchOpen,
123
133
  searchTerm,
124
134
  onSearch: setSearchTerm,
125
135
  onNavigate: () => setIsMobileMenuOpen(false)
126
136
  }, children: [(!isMobile || isMobileMenuOpen)
127
- ? _jsxs("div", { ref: menuRef, className: classNames(["zen-nav", isMobile ? "zen-nav--mobile" : "", collapsed ? "zen-nav--collapsed" : "", className || ""]), children: [header ? cloneElement(header, Object.assign(Object.assign({}, header.props), { className: classNames(["zen-nav__header", header.props.className || ""]) })) : null, _jsx(NavContent, { children: sectionsToRender.map((section, index) => (_jsxs(Fragment, { children: [cloneElement(section, {
128
- className: classNames(["zen-nav__section", section.props.className || ""])
129
- }), isMobile || index < sectionsToRender.length - 1 ? _jsx(NavDivider, {}) : null] }, index))) }), footerToRender] })
130
- : null, mobileBarContainer && isMobile && createPortal(_jsx(NavMobileBar, { onMenuToggle: onMobileMenuToggle, isMenuOpen: isMobileMenuOpen, children: primaryItems }), mobileBarContainer)] });
137
+ ? _jsxs("div", { ref: menuRef, className: classNames(["zen-nav", isMobile ? "zen-nav--mobile" : "", collapsed ? "zen-nav--collapsed" : "", className || ""]), children: [header ? cloneElement(header, Object.assign(Object.assign({}, header.props), { className: classNames(["zen-nav__header", header.props.className || ""]) })) : null, _jsx(NavContent, { children: isEditState
138
+ ? editListItems
139
+ : sectionsToRender.map((section, index) => (_jsxs(Fragment, { children: [cloneElement(section, {
140
+ className: classNames(["zen-nav__section", section.props.className || ""])
141
+ }), isMobile || index < sectionsToRender.length - 1 ? _jsx(NavDivider, {}) : null] }, index))) }), footerToRender] })
142
+ : null, mobileBarContainer && isMobile && createPortal(_jsx(NavMobileBar, { onMenuToggle: onMobileMenuToggle, isMenuOpen: isMobileMenuOpen, children: editListItems ? editListItems : primaryItems }), mobileBarContainer)] });
131
143
  };
@@ -0,0 +1,7 @@
1
+ import "./navEditList.less";
2
+ import { IZenComponentProps } from "../../commonHelpers/zenComponent";
3
+ export interface INavEditList extends IZenComponentProps {
4
+ onItemsChangeOrder?: (startPosition: number, endPosition: number) => void;
5
+ onBeforeItemDrag?: (position: number) => boolean;
6
+ }
7
+ export declare const NavEditList: ({ children, className, onItemsChangeOrder, onBeforeItemDrag }: INavEditList) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Children, useMemo, useRef } from "react";
3
+ import { useDragAndDrop } from "../../list/hooks/useDragAndDrop";
4
+ import { classNames } from "../../commonHelpers/classNames/classNames";
5
+ import { attachDndHandler } from "./navEditListUtils";
6
+ const marksClassNames = {
7
+ general: "zen-nav-edit-list__item",
8
+ placeholder: "zen-nav-edit-list__item--placeholder",
9
+ dragging: "zen-nav-edit-list__item--dragging",
10
+ dragged: "zen-nav-edit-list__item--dragged"
11
+ };
12
+ export const NavEditList = ({ children, className, onItemsChangeOrder, onBeforeItemDrag }) => {
13
+ const appsListRef = useRef(null);
14
+ const dragHandler = onItemsChangeOrder ? onItemsChangeOrder : () => { };
15
+ const { onDragStart } = useDragAndDrop(appsListRef, dragHandler, marksClassNames, onBeforeItemDrag);
16
+ const childrenArray = useMemo(() => onItemsChangeOrder ? Children.map(children, child => attachDndHandler(child, onDragStart)) : children, [children, onItemsChangeOrder, onDragStart]);
17
+ return _jsx("div", { className: classNames(["zen-nav-edit-list", className || ""]), ref: appsListRef, children: childrenArray });
18
+ };
@@ -0,0 +1,3 @@
1
+ import { PointerEvent, ReactElement } from "react";
2
+ import { INavActionItem } from "../navItem/navActionItem";
3
+ export declare const attachDndHandler: (item: ReactElement<INavActionItem>, onPointerDown: (e: PointerEvent<HTMLElement>) => void) => ReactElement<INavActionItem, string | import("react").JSXElementConstructor<any>>;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cloneElement } from "react";
3
+ import { NavActionItem } from "../navItem/navActionItem";
4
+ import { IconGrab } from "../../icons/iconGrab";
5
+ export const attachDndHandler = (item, onPointerDown) => {
6
+ if (item.type === NavActionItem) {
7
+ return cloneElement(item, Object.assign(Object.assign({}, item.props), { startNode: _jsx("div", { className: "zen-nav-item__dnd", onPointerDown: onPointerDown, children: _jsx(IconGrab, { size: "large" }) }) }));
8
+ }
9
+ return item;
10
+ };
@@ -0,0 +1,3 @@
1
+ import "./navEditSection.less";
2
+ import { IZenComponentProps } from "../../commonHelpers/zenComponent";
3
+ export declare const NavEditSection: ({ children, className }: IZenComponentProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { classNames } from "../../commonHelpers/classNames/classNames";
3
+ export const NavEditSection = ({ children, className }) => _jsx("div", { className: classNames(["zen-nav-edit-section", className || ""]), children: children });