@geotab/zenith 3.15.0 → 3.16.0-beta.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 (184) hide show
  1. package/dist/absolute/absolute.js +1 -1
  2. package/dist/betaPill/betaPill.js +9 -9
  3. package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
  4. package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.js +5 -1
  5. package/dist/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
  6. package/dist/commonHelpers/hooks/useTrapFocus.js +66 -14
  7. package/dist/controlledPopup/controlledPopup.d.ts +1 -1
  8. package/dist/controlledPopup/controlledPopup.js +1 -1
  9. package/dist/dropdownRaw/dropdownSearchableTrigger.js +1 -1
  10. package/dist/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
  11. package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
  12. package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
  13. package/dist/filtersBar/filtersContainer/filtersContainer.js +7 -4
  14. package/dist/header/header.js +9 -2
  15. package/dist/header/headerHelpers.js +8 -5
  16. package/dist/index.css +142 -11
  17. package/dist/index.d.ts +6 -1
  18. package/dist/index.js +12 -3
  19. package/dist/modal/modal.d.ts +48 -0
  20. package/dist/modal/modal.js +37 -7
  21. package/dist/nav/navHeader/navHeader.d.ts +1 -1
  22. package/dist/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
  23. package/dist/nav/navItem/navItem.d.ts +15 -2
  24. package/dist/nav/navItem/navItem.js +49 -7
  25. package/dist/nav/utils/navUtils.d.ts +2 -2
  26. package/dist/pillExpandable/pillExpandable.js +6 -3
  27. package/dist/rangeRaw/components/rangeField.d.ts +2 -1
  28. package/dist/rangeRaw/components/rangeField.js +4 -3
  29. package/dist/rangeRaw/rangeRaw.js +8 -5
  30. package/dist/rangeRaw/types.d.ts +1 -0
  31. package/dist/sidePanel/sidePanel.js +3 -2
  32. package/dist/table/selectable/interfaces.d.ts +13 -0
  33. package/dist/table/selectable/useSelectableRows.js +46 -12
  34. package/dist/table/selectable/utils/calculateSelectionState.js +35 -0
  35. package/dist/table/selectable/utils/isRowChecked.js +12 -1
  36. package/dist/table/table.d.ts +2 -0
  37. package/dist/table/table.js +3 -1
  38. package/dist/tabs/tabs.js +14 -5
  39. package/dist/textInputRaw/textInputRaw.js +1 -1
  40. package/dist/textInputRaw/types.d.ts +1 -0
  41. package/dist/tooltip/components/inlineTrigger.d.ts +43 -0
  42. package/dist/tooltip/components/inlineTrigger.js +42 -0
  43. package/dist/tooltip/tooltip.d.ts +5 -2
  44. package/dist/tooltip/tooltip.js +15 -4
  45. package/dist/trendIndicator/trendIndicator.d.ts +47 -0
  46. package/dist/trendIndicator/trendIndicator.js +47 -0
  47. package/dist/trendIndicator/trendIndicatorUtils.d.ts +9 -0
  48. package/dist/trendIndicator/trendIndicatorUtils.js +18 -0
  49. package/dist/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
  50. package/dist/utils/experimentalFeatures/experimentalContext.js +29 -0
  51. package/dist/utils/experimentalFeatures/index.d.ts +4 -0
  52. package/dist/utils/experimentalFeatures/index.js +9 -0
  53. package/dist/utils/experimentalFeatures/registry.d.ts +13 -0
  54. package/dist/utils/experimentalFeatures/registry.js +11 -0
  55. package/dist/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
  56. package/dist/utils/experimentalFeatures/useExperimentalFeature.js +25 -0
  57. package/dist/utils/localization/translations/ar.json +1 -1
  58. package/dist/utils/localization/translations/bg-BG.json +1 -1
  59. package/dist/utils/localization/translations/cs.json +1 -1
  60. package/dist/utils/localization/translations/da-DK.json +1 -1
  61. package/dist/utils/localization/translations/de.json +1 -1
  62. package/dist/utils/localization/translations/el-GR.json +1 -1
  63. package/dist/utils/localization/translations/en.json +1 -1
  64. package/dist/utils/localization/translations/es.json +1 -1
  65. package/dist/utils/localization/translations/et-EE.json +1 -1
  66. package/dist/utils/localization/translations/fi-FI.json +1 -1
  67. package/dist/utils/localization/translations/fr-FR.json +1 -1
  68. package/dist/utils/localization/translations/fr.json +1 -1
  69. package/dist/utils/localization/translations/hu-HU.json +1 -1
  70. package/dist/utils/localization/translations/id.json +1 -1
  71. package/dist/utils/localization/translations/it.json +1 -1
  72. package/dist/utils/localization/translations/ja.json +1 -1
  73. package/dist/utils/localization/translations/ka-GE.json +1 -1
  74. package/dist/utils/localization/translations/ko-KR.json +1 -1
  75. package/dist/utils/localization/translations/lt-LT.json +1 -1
  76. package/dist/utils/localization/translations/lv-LV.json +1 -1
  77. package/dist/utils/localization/translations/ms.json +1 -1
  78. package/dist/utils/localization/translations/nb-NO.json +1 -1
  79. package/dist/utils/localization/translations/nl.json +1 -1
  80. package/dist/utils/localization/translations/pl.json +1 -1
  81. package/dist/utils/localization/translations/pt-BR.json +1 -1
  82. package/dist/utils/localization/translations/pt-PT.json +1 -1
  83. package/dist/utils/localization/translations/ro-RO.json +1 -1
  84. package/dist/utils/localization/translations/sk-SK.json +1 -1
  85. package/dist/utils/localization/translations/sv.json +1 -1
  86. package/dist/utils/localization/translations/th.json +1 -1
  87. package/dist/utils/localization/translations/tr.json +1 -1
  88. package/dist/utils/localization/translations/zh-Hans.json +1 -1
  89. package/dist/utils/localization/translations/zh-TW.json +1 -1
  90. package/esm/absolute/absolute.js +1 -1
  91. package/esm/betaPill/betaPill.js +9 -9
  92. package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
  93. package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.js +3 -0
  94. package/esm/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
  95. package/esm/commonHelpers/hooks/useTrapFocus.js +67 -15
  96. package/esm/controlledPopup/controlledPopup.d.ts +1 -1
  97. package/esm/controlledPopup/controlledPopup.js +1 -1
  98. package/esm/dropdownRaw/dropdownSearchableTrigger.js +1 -1
  99. package/esm/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
  100. package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
  101. package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
  102. package/esm/filtersBar/filtersContainer/filtersContainer.js +7 -4
  103. package/esm/header/header.js +9 -2
  104. package/esm/header/headerHelpers.js +8 -5
  105. package/esm/index.d.ts +6 -1
  106. package/esm/index.js +5 -1
  107. package/esm/modal/modal.d.ts +48 -0
  108. package/esm/modal/modal.js +37 -7
  109. package/esm/nav/navHeader/navHeader.d.ts +1 -1
  110. package/esm/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
  111. package/esm/nav/navItem/navItem.d.ts +15 -2
  112. package/esm/nav/navItem/navItem.js +49 -7
  113. package/esm/nav/utils/navUtils.d.ts +2 -2
  114. package/esm/pillExpandable/pillExpandable.js +6 -3
  115. package/esm/rangeRaw/components/rangeField.d.ts +2 -1
  116. package/esm/rangeRaw/components/rangeField.js +4 -3
  117. package/esm/rangeRaw/rangeRaw.js +8 -5
  118. package/esm/rangeRaw/types.d.ts +1 -0
  119. package/esm/sidePanel/sidePanel.js +3 -2
  120. package/esm/table/selectable/interfaces.d.ts +13 -0
  121. package/esm/table/selectable/useSelectableRows.js +46 -12
  122. package/esm/table/selectable/utils/calculateSelectionState.js +35 -0
  123. package/esm/table/selectable/utils/isRowChecked.js +12 -1
  124. package/esm/table/table.d.ts +2 -0
  125. package/esm/table/table.js +2 -0
  126. package/esm/tabs/tabs.js +14 -5
  127. package/esm/testHelpers/renderWithFeatures.d.ts +4 -0
  128. package/esm/testHelpers/renderWithFeatures.js +6 -0
  129. package/esm/textInputRaw/textInputRaw.js +1 -1
  130. package/esm/textInputRaw/types.d.ts +1 -0
  131. package/esm/tooltip/components/inlineTrigger.d.ts +43 -0
  132. package/esm/tooltip/components/inlineTrigger.js +38 -0
  133. package/esm/tooltip/tooltip.d.ts +5 -2
  134. package/esm/tooltip/tooltip.js +15 -4
  135. package/esm/trendIndicator/trendIndicator.d.ts +47 -0
  136. package/esm/trendIndicator/trendIndicator.js +43 -0
  137. package/esm/trendIndicator/trendIndicatorUtils.d.ts +9 -0
  138. package/esm/trendIndicator/trendIndicatorUtils.js +14 -0
  139. package/esm/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
  140. package/esm/utils/experimentalFeatures/experimentalContext.js +25 -0
  141. package/esm/utils/experimentalFeatures/index.d.ts +4 -0
  142. package/esm/utils/experimentalFeatures/index.js +3 -0
  143. package/esm/utils/experimentalFeatures/registry.d.ts +13 -0
  144. package/esm/utils/experimentalFeatures/registry.js +8 -0
  145. package/esm/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
  146. package/esm/utils/experimentalFeatures/useExperimentalFeature.js +21 -0
  147. package/esm/utils/localization/translations/ar.json +1 -1
  148. package/esm/utils/localization/translations/bg-BG.json +1 -1
  149. package/esm/utils/localization/translations/cs.json +1 -1
  150. package/esm/utils/localization/translations/da-DK.json +1 -1
  151. package/esm/utils/localization/translations/de.json +1 -1
  152. package/esm/utils/localization/translations/el-GR.json +1 -1
  153. package/esm/utils/localization/translations/en.json +1 -1
  154. package/esm/utils/localization/translations/es.json +1 -1
  155. package/esm/utils/localization/translations/et-EE.json +1 -1
  156. package/esm/utils/localization/translations/fi-FI.json +1 -1
  157. package/esm/utils/localization/translations/fr-FR.json +1 -1
  158. package/esm/utils/localization/translations/fr.json +1 -1
  159. package/esm/utils/localization/translations/hu-HU.json +1 -1
  160. package/esm/utils/localization/translations/id.json +1 -1
  161. package/esm/utils/localization/translations/it.json +1 -1
  162. package/esm/utils/localization/translations/ja.json +1 -1
  163. package/esm/utils/localization/translations/ka-GE.json +1 -1
  164. package/esm/utils/localization/translations/ko-KR.json +1 -1
  165. package/esm/utils/localization/translations/lt-LT.json +1 -1
  166. package/esm/utils/localization/translations/lv-LV.json +1 -1
  167. package/esm/utils/localization/translations/ms.json +1 -1
  168. package/esm/utils/localization/translations/nb-NO.json +1 -1
  169. package/esm/utils/localization/translations/nl.json +1 -1
  170. package/esm/utils/localization/translations/pl.json +1 -1
  171. package/esm/utils/localization/translations/pt-BR.json +1 -1
  172. package/esm/utils/localization/translations/pt-PT.json +1 -1
  173. package/esm/utils/localization/translations/ro-RO.json +1 -1
  174. package/esm/utils/localization/translations/sk-SK.json +1 -1
  175. package/esm/utils/localization/translations/sv.json +1 -1
  176. package/esm/utils/localization/translations/th.json +1 -1
  177. package/esm/utils/localization/translations/tr.json +1 -1
  178. package/esm/utils/localization/translations/zh-Hans.json +1 -1
  179. package/esm/utils/localization/translations/zh-TW.json +1 -1
  180. package/package.json +36 -16
  181. package/dist/banner/utils/isBannerTarget.d.ts +0 -1
  182. package/dist/banner/utils/isBannerTarget.js +0 -11
  183. package/esm/banner/utils/isBannerTarget.d.ts +0 -1
  184. package/esm/banner/utils/isBannerTarget.js +0 -7
@@ -4,6 +4,11 @@ import { IZenComponentProps } from "../commonHelpers/zenComponent";
4
4
  import { IIcon } from "../icons/icon";
5
5
  import { TButtonIconPosition } from "../textIconButton/textIconButton";
6
6
  import { TSvgIconSize } from "../svgIcon/svgIconSize";
7
+ /**
8
+ * The CSS class applied to `document.body` while a Modal is open to prevent page scrolling.
9
+ * Exported so consumers can reference it without coupling to internal implementation details.
10
+ */
11
+ export declare const MODAL_SCROLL_LOCK_CLASS = "zen-modal-content--hidden-overflow";
7
12
  interface IButtonIconProps {
8
13
  icon?: React.FC<IIcon>;
9
14
  iconPosition?: TButtonIconPosition;
@@ -38,6 +43,49 @@ export interface IModal {
38
43
  className?: string;
39
44
  modalContainerClassName?: string;
40
45
  focus?: TFocusElement;
46
+ /**
47
+ * Called once the body scroll-lock has been synchronously released after the modal closes.
48
+ * Use this to safely sequence scroll operations that depend on the modal being fully closed.
49
+ *
50
+ * **Triggers:** the callback fires in two scenarios:
51
+ * - `isOpen` transitions from `true` to `false` (normal close)
52
+ * - The component unmounts while `isOpen` is still `true` (e.g. conditional rendering with
53
+ * `{condition && <Modal isOpen={true} ... />}` where `condition` flips to `false`)
54
+ *
55
+ * It is **not** called when the modal mounts already closed, or when `isOpen` remains `false`
56
+ * across re-renders.
57
+ *
58
+ * **Layout-phase timing:** this callback fires inside a `useLayoutEffect` cleanup, which runs
59
+ * synchronously before the browser paints. This makes it safe for DOM reads and imperative
60
+ * scroll operations. However, calling `setState` inside this callback will trigger an
61
+ * additional synchronous re-render before the next paint — prefer keeping the callback
62
+ * side-effect-free or deferring state updates with `setTimeout`/`queueMicrotask` if needed.
63
+ *
64
+ * **React Strict Mode (development only):** React 18 Strict Mode intentionally
65
+ * double-invokes effects on mount (setup → cleanup → setup) to surface side-effect bugs.
66
+ * If `isOpen` is `true` on initial render, the cleanup from the first (discarded) mount
67
+ * fires with `wasOpen.current = true`, which triggers this callback once at mount even
68
+ * though the modal has not closed. This is a dev-mode artefact and does not occur in
69
+ * production builds.
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * // Safe: imperative scroll (no state update)
74
+ * <Modal
75
+ * isOpen={isOpen}
76
+ * onScrollLockReleased={() => window.scrollTo({ top: newSectionOffset, behavior: "smooth" })}
77
+ * ...
78
+ * />
79
+ *
80
+ * // Use caution: setState inside the callback causes a synchronous re-render before paint
81
+ * <Modal
82
+ * isOpen={isOpen}
83
+ * onScrollLockReleased={() => setScrollTarget(newSectionOffset)}
84
+ * ...
85
+ * />
86
+ * ```
87
+ */
88
+ onScrollLockReleased?: () => void;
41
89
  }
42
90
  export declare const Modal: FC<IModal> & {
43
91
  Content: FC<IDialogContent>;
@@ -1,7 +1,7 @@
1
1
  import { injectString } from "../utils/localization/translationsDictionary";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  /* eslint-disable @typescript-eslint/naming-convention */
4
- import React, { cloneElement, createElement, Fragment, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
4
+ import React, { cloneElement, createElement, Fragment, isValidElement, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
5
5
  import { classNames } from "../commonHelpers/classNames/classNames";
6
6
  import { Button } from "../button/button";
7
7
  import { FOCUSABLE_SELECTOR } from "../utils/focusableSelector";
@@ -22,6 +22,10 @@ import { TextIconButton } from "../textIconButton/textIconButton";
22
22
  import { useClientReady } from "../commonHelpers/hooks/useClientReady";
23
23
  import { zen } from "../utils/zen";
24
24
  import { createPortal } from "react-dom";
25
+ /**
26
+ * The CSS class applied to `document.body` while a Modal is open to prevent page scrolling.
27
+ * Exported so consumers can reference it without coupling to internal implementation details.
28
+ */
25
29
  injectString("cs", "Close", "Zav\u0159\xEDt");
26
30
  injectString("da-DK", "Close", "Luk");
27
31
  injectString("de", "Close", "Schlie\xDFen");
@@ -55,6 +59,7 @@ injectString("ka-GE", "Close", "\u10D3\u10D0\u10EE\u10E3\u10E0\u10D5\u10D0");
55
59
  injectString("el-GR", "Close", "\u039A\u03BB\u03B5\u03AF\u03C3\u03B9\u03BC\u03BF");
56
60
  injectString("et-EE", "Close", "Sulge");
57
61
  injectString("bg-BG", "Close", "\u0417\u0430\u0442\u0432\u0430\u0440\u044F\u043D\u0435");
62
+ export const MODAL_SCROLL_LOCK_CLASS = "zen-modal-content--hidden-overflow";
58
63
  export const Modal = ({
59
64
  isOpen,
60
65
  children,
@@ -65,7 +70,8 @@ export const Modal = ({
65
70
  type,
66
71
  className,
67
72
  focus,
68
- modalContainerClassName
73
+ modalContainerClassName,
74
+ onScrollLockReleased
69
75
  }) => {
70
76
  var _a, _b, _c;
71
77
  const lastActiveOutsideElement = useRef((_a = zen.document) === null || _a === void 0 ? void 0 : _a.activeElement);
@@ -74,6 +80,14 @@ export const Modal = ({
74
80
  dark
75
81
  } = useContext(themeContext);
76
82
  const isClientReady = useClientReady();
83
+ // Tracks whether the modal was open on the previous render so we can detect open→closed transitions.
84
+ const wasOpen = useRef(false);
85
+ // Keep the latest callback in a ref so it never needs to enter the useLayoutEffect deps array.
86
+ // Assigning directly in the render body (rather than via a sync effect) guarantees the ref is
87
+ // always current before any effects or cleanups run in the same commit — eliminating the stale
88
+ // callback edge case that occurred when isOpen and onScrollLockReleased changed simultaneously.
89
+ const onScrollLockReleasedRef = useRef(onScrollLockReleased);
90
+ onScrollLockReleasedRef.current = onScrollLockReleased;
77
91
  const onOutSideClick = useCallback(() => {
78
92
  if (!isClientReady) {
79
93
  return;
@@ -92,19 +106,35 @@ export const Modal = ({
92
106
  isFocusable(copyActive) && copyActive.focus();
93
107
  };
94
108
  }, [isClientReady]);
95
- useEffect(() => {
109
+ // useLayoutEffect runs synchronously after DOM mutations, before the browser paints.
110
+ // This guarantees the scroll-lock class is removed (and onScrollLockReleased fires)
111
+ // before the browser repaints — eliminating the timing gap that existed with useEffect.
112
+ // onScrollLockReleased is intentionally excluded from deps: it is read via ref so that
113
+ // inline arrow functions passed by consumers do not cause redundant DOM mutations on
114
+ // every parent re-render while the modal is open.
115
+ useLayoutEffect(() => {
96
116
  var _a, _b;
97
117
  if (!isClientReady) {
98
118
  return undefined;
99
119
  }
100
- (_a = zen.document) === null || _a === void 0 ? void 0 : _a.body.classList.remove("zen-modal-content--hidden-overflow");
120
+ (_a = zen.document) === null || _a === void 0 ? void 0 : _a.body.classList.remove(MODAL_SCROLL_LOCK_CLASS);
101
121
  if (isOpen) {
102
- (_b = zen.document) === null || _b === void 0 ? void 0 : _b.body.classList.add("zen-modal-content--hidden-overflow");
122
+ wasOpen.current = true;
123
+ (_b = zen.document) === null || _b === void 0 ? void 0 : _b.body.classList.add(MODAL_SCROLL_LOCK_CLASS);
103
124
  }
104
125
  return () => {
105
- var _a;
106
- (_a = zen.document) === null || _a === void 0 ? void 0 : _a.body.classList.remove("zen-modal-content--hidden-overflow");
126
+ var _a, _b;
127
+ (_a = zen.document) === null || _a === void 0 ? void 0 : _a.body.classList.remove(MODAL_SCROLL_LOCK_CLASS);
128
+ // Fire the callback in cleanup so it is triggered in both cases:
129
+ // 1. isOpen prop transitions true → false
130
+ // 2. component unmounts while isOpen is still true
131
+ // wasOpen guards against spurious calls (e.g. closing an already-closed modal).
132
+ if (wasOpen.current) {
133
+ wasOpen.current = false;
134
+ (_b = onScrollLockReleasedRef.current) === null || _b === void 0 ? void 0 : _b.call(onScrollLockReleasedRef);
135
+ }
107
136
  };
137
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
138
  }, [isClientReady, isOpen]);
109
139
  useEffect(() => {
110
140
  var _a, _b;
@@ -28,7 +28,7 @@ export declare const NavHeader: FC<INavHeader> & {
28
28
  * @deprecated Use NavItem instead. Will be removed in future releases.
29
29
  */
30
30
  Action: {
31
- (props: import("../navItem/navItem").ILinkNavItem | import("../navItem/navItem").IButtonNavItem): import("react/jsx-runtime").JSX.Element;
31
+ (props: import("../navItem/navItem").ILinkNavItem | import("../navItem/navItem").TButtonNavItem): import("react/jsx-runtime").JSX.Element;
32
32
  displayName: string;
33
33
  };
34
34
  Search: {
@@ -128,7 +128,7 @@ export const NavHeaderSearch = ({
128
128
  onClick: () => onSearchToggle === null || onSearchToggle === void 0 ? void 0 : onSearchToggle(true),
129
129
  tooltipAlignment: collapsed ? "end" : "bottom"
130
130
  }) : null, searchOpen || isMobile ? _jsx(SearchInput, {
131
- autoFocus: true,
131
+ autoFocus: searchOpen,
132
132
  ariaLabel: translate("Search"),
133
133
  value: searchTerm,
134
134
  placeholder: translate("Search"),
@@ -26,11 +26,24 @@ interface INavItemCommon extends IZenComponentProps {
26
26
  export interface ILinkNavItem extends INavItemCommon {
27
27
  link: string;
28
28
  }
29
- export interface IButtonNavItem extends INavItemCommon {
29
+ interface IButtonNavItemBase extends INavItemCommon {
30
30
  onClick?: () => void;
31
31
  menuAlignment?: TAlignment;
32
32
  onDropdownStateChange?: (isOpen: boolean) => void;
33
33
  }
34
+ export type TNavItemPopupType = "dialog" | "menu" | "listbox" | "tree" | "grid";
35
+ export interface IPopupButtonNavItem extends IButtonNavItemBase {
36
+ popupId: string;
37
+ isPopupOpen: boolean;
38
+ popupType?: TNavItemPopupType;
39
+ children?: never;
40
+ }
41
+ interface IPlainButtonNavItem extends IButtonNavItemBase {
42
+ popupId?: never;
43
+ isPopupOpen?: never;
44
+ popupType?: never;
45
+ }
46
+ export type TButtonNavItem = IPlainButtonNavItem | IPopupButtonNavItem;
34
47
  interface INavItemContext {
35
48
  parentLevel?: number;
36
49
  closeParentMenu?: () => void;
@@ -40,7 +53,7 @@ export declare const NavItemContext: import("react").Context<INavItemContext>;
40
53
  * @beta This component is not fully ready yet and may change in future releases.
41
54
  */
42
55
  export declare const NavItem: {
43
- (props: ILinkNavItem | IButtonNavItem): import("react/jsx-runtime").JSX.Element;
56
+ (props: ILinkNavItem | TButtonNavItem): import("react/jsx-runtime").JSX.Element;
44
57
  displayName: string;
45
58
  };
46
59
  export {};
@@ -22,6 +22,11 @@ import { NavItemType } from "./navItemType";
22
22
  export const NavItemContext = createContext({ parentLevel: 0 });
23
23
  const defaultTooltipAlignment = "end";
24
24
  const isLinkNavItem = (item) => !!item.link;
25
+ // An empty popupId would render aria-controls="", i.e. a trigger that advertises a popup but names no
26
+ // element. Nothing else catches that — TypeScript accepts "" as a string, and aria-controls is
27
+ // allowEmpty for validators — so reject it here and fall back to the plain button variant rather than
28
+ // emitting a relationship that resolves to nothing.
29
+ const isPopupButtonNavItem = (item) => "popupId" in item && typeof item.popupId === "string" && item.popupId !== "";
25
30
  const isNavItemElement = (child) => isValidElement(child) && child.type.displayName === "NavItem";
26
31
  const getWrapperClassName = ({ className, collapsed, active, isMobile, hasNested, isNested, isLink, type }) => classNames([
27
32
  "zen-nav-item",
@@ -74,11 +79,19 @@ const LinkNavItem = (_a) => {
74
79
  onNavigate && onNavigate();
75
80
  }, className: "zen-link--light zen-nav-item__main", children: _jsx(NavItemContent, Object.assign({ title: title, collapsed: collapsed, level: currentLevel, isExternalLink: isExternalLink }, rest)) }));
76
81
  const item = collapsed ? (_jsx(Tooltip, { trigger: linkElement, alignment: tooltipAlignment, children: title })) : (linkElement);
77
- return (_jsxs("div", { className: getWrapperClassName({ className, collapsed, active, isMobile, isLink: true, type }), children: [item, _jsx(NavItemAction, { actionIcon: actionIcon, actionIconTitle: actionIconTitle, onActionIconClick: onActionIconClick, collapsed: collapsed, level: currentLevel, isMenuItem: isMenuItem })] }));
82
+ return (_jsxs("div", { className: getWrapperClassName({
83
+ className,
84
+ collapsed,
85
+ active,
86
+ isMobile,
87
+ isLink: true,
88
+ type
89
+ }), children: [item, _jsx(NavItemAction, { actionIcon: actionIcon, actionIconTitle: actionIconTitle, onActionIconClick: onActionIconClick, collapsed: collapsed, level: currentLevel, isMenuItem: isMenuItem })] }));
78
90
  };
79
91
  LinkNavItem.displayName = "LinkNavItem";
80
- const ButtonNavItem = (_a) => {
81
- var { className, title, onClick, collapsed, active, children, tabIndex, menuAlignment = "end-top", isMenuItem = true, tooltipAlignment = defaultTooltipAlignment, actionIcon, actionIconTitle, onActionIconClick, onDropdownStateChange, type } = _a, rest = __rest(_a, ["className", "title", "onClick", "collapsed", "active", "children", "tabIndex", "menuAlignment", "isMenuItem", "tooltipAlignment", "actionIcon", "actionIconTitle", "onActionIconClick", "onDropdownStateChange", "type"]);
92
+ const ButtonNavItem = (props) => {
93
+ const isPopupTrigger = isPopupButtonNavItem(props);
94
+ const { className, title, onClick, collapsed, active, children, tabIndex, menuAlignment = "end-top", isMenuItem = true, tooltipAlignment = defaultTooltipAlignment, actionIcon, actionIconTitle, onActionIconClick, onDropdownStateChange, popupId, isPopupOpen, popupType, type } = props, rest = __rest(props, ["className", "title", "onClick", "collapsed", "active", "children", "tabIndex", "menuAlignment", "isMenuItem", "tooltipAlignment", "actionIcon", "actionIconTitle", "onActionIconClick", "onDropdownStateChange", "popupId", "isPopupOpen", "popupType", "type"]);
82
95
  const [menuOpen, setMenuOpen] = useState(false);
83
96
  const { parentLevel = 0, closeParentMenu } = useContext(NavItemContext);
84
97
  useEffect(() => {
@@ -113,7 +126,15 @@ const ButtonNavItem = (_a) => {
113
126
  const { onNavigate } = useNavContext();
114
127
  const isActive = active || (hasNestedItems && menuOpen);
115
128
  const triggerId = useId();
116
- const buttonElement = (_jsx("button", { id: triggerId, ref: triggerRef, "aria-label": title, title: title, tabIndex: tabIndex, role: isMenuItem ? "menuitem" : undefined, "aria-haspopup": hasNestedItems ? "menu" : undefined, "aria-expanded": hasNestedItems ? menuOpen : undefined, className: "zen-nav-item__main", onClick: handleClick, onKeyDown: handleKeyPress, children: _jsx(NavItemContent, Object.assign({ title: title, collapsed: collapsed, hasSubmenu: hasNestedItems, level: currentLevel }, rest)) }));
129
+ // All three attributes branch on hasNestedItems first so they always describe the same popup. A
130
+ // nested submenu wins: one button cannot honestly advertise both a submenu and an external popup.
131
+ // TypeScript already forbids the combination (IPopupButtonNavItem declares children?: never); the
132
+ // guard here keeps the triple consistent when that is bypassed at runtime — JS consumers, spread
133
+ // props, or `as any`.
134
+ const ariaHasPopup = hasNestedItems ? "menu" : isPopupTrigger ? (popupType !== null && popupType !== void 0 ? popupType : "dialog") : undefined;
135
+ const ariaExpanded = hasNestedItems ? menuOpen : isPopupTrigger ? isPopupOpen : undefined;
136
+ const ariaControls = hasNestedItems || !isPopupTrigger ? undefined : popupId;
137
+ const buttonElement = (_jsx("button", { id: triggerId, ref: triggerRef, "aria-label": title, title: title, tabIndex: tabIndex, role: isMenuItem ? "menuitem" : undefined, "aria-haspopup": ariaHasPopup, "aria-expanded": ariaExpanded, "aria-controls": ariaControls, className: "zen-nav-item__main", onClick: handleClick, onKeyDown: handleKeyPress, children: _jsx(NavItemContent, Object.assign({ title: title, collapsed: collapsed, hasSubmenu: hasNestedItems, level: currentLevel }, rest)) }));
117
138
  const trigger = collapsed ? (_jsx(Tooltip, { trigger: buttonElement, alignment: tooltipAlignment, children: title })) : (buttonElement);
118
139
  if (hasNestedItems) {
119
140
  const childLevel = currentLevel + 1;
@@ -127,7 +148,14 @@ const ButtonNavItem = (_a) => {
127
148
  };
128
149
  // For mobiles convert the entire subtree into a static NavMenuItem tree
129
150
  if (isMobile) {
130
- return (_jsxs("div", { className: getWrapperClassName({ className, collapsed, active: isActive, isMobile, hasNested: hasNestedItems, type }), children: [trigger, _jsx(ControlledMenu, { triggerRef: triggerRef, isOpen: menuOpen, setIsOpen: setMenuOpen, title: title, alignment: menuAlignment, className: classNames([popupClass, popupClassMobile]), listClassName: classNames([popupListClass, popupListClassMobile]), ariaLabelledby: triggerId, children: buildMobileMenuItems(children, triggerId, onNavigate) })] }));
151
+ return (_jsxs("div", { className: getWrapperClassName({
152
+ className,
153
+ collapsed,
154
+ active: isActive,
155
+ isMobile,
156
+ hasNested: hasNestedItems,
157
+ type
158
+ }), children: [trigger, _jsx(ControlledMenu, { triggerRef: triggerRef, isOpen: menuOpen, setIsOpen: setMenuOpen, title: title, alignment: menuAlignment, className: classNames([popupClass, popupClassMobile]), listClassName: classNames([popupListClass, popupListClassMobile]), ariaLabelledby: triggerId, children: buildMobileMenuItems(children, triggerId, onNavigate) })] }));
131
159
  }
132
160
  // Desktop: clone children with level classes and render nested ControlledMenu popups per level.
133
161
  const processedChildren = validChildren.map(child => {
@@ -136,9 +164,23 @@ const ButtonNavItem = (_a) => {
136
164
  const nestedClass = childLevel > 1 ? "zen-nav-item--nested" : "";
137
165
  return cloneElement(childElement, Object.assign(Object.assign({}, childElement.props), { className: classNames([levelClass, nestedClass, childElement.props.className || ""]), collapsed: false }));
138
166
  });
139
- return (_jsxs("div", { className: getWrapperClassName({ className, collapsed, active: isActive, isMobile, hasNested: hasNestedItems, type }), children: [trigger, _jsx(NavItemContext, { value: { parentLevel: currentLevel, closeParentMenu: closeLevelMenu }, children: _jsx(ControlledMenu, { triggerRef: triggerRef, isOpen: menuOpen, setIsOpen: setMenuOpen, title: title, alignment: menuAlignment, className: popupClass, listClassName: popupListClass, ariaLabelledby: triggerId, children: processedChildren }) })] }));
167
+ return (_jsxs("div", { className: getWrapperClassName({
168
+ className,
169
+ collapsed,
170
+ active: isActive,
171
+ isMobile,
172
+ hasNested: hasNestedItems,
173
+ type
174
+ }), children: [trigger, _jsx(NavItemContext, { value: { parentLevel: currentLevel, closeParentMenu: closeLevelMenu }, children: _jsx(ControlledMenu, { triggerRef: triggerRef, isOpen: menuOpen, setIsOpen: setMenuOpen, title: title, alignment: menuAlignment, className: popupClass, listClassName: popupListClass, ariaLabelledby: triggerId, children: processedChildren }) })] }));
140
175
  }
141
- return (_jsxs("div", { className: getWrapperClassName({ className, collapsed, active: isActive, isMobile, hasNested: hasNestedItems, type }), children: [trigger, _jsx(NavItemAction, { actionIcon: actionIcon, actionIconTitle: actionIconTitle, onActionIconClick: onActionIconClick, collapsed: collapsed, level: currentLevel, isMenuItem: isMenuItem })] }));
176
+ return (_jsxs("div", { className: getWrapperClassName({
177
+ className,
178
+ collapsed,
179
+ active: isActive,
180
+ isMobile,
181
+ hasNested: hasNestedItems,
182
+ type
183
+ }), children: [trigger, _jsx(NavItemAction, { actionIcon: actionIcon, actionIconTitle: actionIconTitle, onActionIconClick: onActionIconClick, collapsed: collapsed, level: currentLevel, isMenuItem: isMenuItem })] }));
142
184
  };
143
185
  ButtonNavItem.displayName = "ButtonNavItem";
144
186
  /**
@@ -1,8 +1,8 @@
1
1
  import { PropsWithChildren, ReactElement, ReactNode } from "react";
2
- import { IButtonNavItem, ILinkNavItem } from "../navItem/navItem";
2
+ import { ILinkNavItem, TButtonNavItem } from "../navItem/navItem";
3
3
  export declare const navHeaderImage = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIzIiBoZWlnaHQ9IjE1IiB2aWV3Qm94PSIwIDAgMTIzIDE1IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNDcuNDE3OCA1LjY2MDFIMzkuNTkxNVY2Ljc0NTI2SDQ1LjM1MjdDNDUuNDE5MiA3LjA2NjkzIDQ1LjQ1MjggNy4yNzc1IDQ1LjQ1MjggNy4zNzA5QzQ1LjQ1MjggOC41OTE3NyA0NS4xOTczIDkuMzU1MjUgNDQuNjk0IDkuNjM5OTdDNDQuMTg1NCA5LjkyODEzIDQyLjgyODkgMTAuMDczOSA0MC42NjIzIDEwLjA3MzlDMzcuODI1NyAxMC4wNzM5IDM2LjA1NzggOS45MTk3IDM1LjQwNzkgOS42MTUxNEMzNC43NjUzIDkuMzE0NDkgMzQuNDM5NyA4LjQ4ODM4IDM0LjQzOTcgNy4xNTkwM0wzNC40MjY2IDUuNjMwOThMMzQuNDUzIDQuMjExNTVDMzQuNDUzIDIuOTAyNCAzNC43NDYyIDIuMDc0MzkgMzUuMzI0MiAxLjc1MDMzQzM1LjkwNzcgMS40MjMxOSAzNy4zOTE1IDEuMjU3MzEgMzkuNzM0MiAxLjI1NzMxQzQyLjI3MjQgMS4yNTczMSA0My44NjMxIDEuMzYwOTMgNDQuNDYyIDEuNTY0ODRDNDUuMDQ5IDEuNzY1MTkgNDUuMzQ2NyAyLjI5OTU3IDQ1LjM0NjcgMy4xNTMwMVYzLjQ1MjQ2SDQ3LjQyMTlWMy4wMTE4NEM0Ny40MjE5IDEuODE2NCA0Ni45NjM3IDEuMDM0NjIgNDYuMDYwMyAwLjY4ODIyN0M0NS4xNjY1IDAuMzQ1NzU4IDQzLjE1ODUgMC4xNzIwMjggNDAuMDkyIDAuMTcyMDI4QzM3LjAzMjQgMC4xNzIwMjggMzQuOTY3NSAwLjQzNzI1NyAzMy45NTQxIDAuOTYwMjMxQzMyLjkzNDUgMS40ODcwMSAzMi40MTc2IDIuNTU5NTcgMzIuNDE3NiA0LjE0ODFMMzIuNDA0MyA1LjA5NzMyTDMyLjQxNzYgNi42NzMxM0MzMi40MTc2IDguNTYyODkgMzIuODgyNiA5Ljc5NzMgMzMuNzk5NyAxMC4zNDI0QzM0LjcxMjEgMTAuODg0MSAzNi43ODg3IDExLjE1OSAzOS45NzI3IDExLjE1OUM0My4zNDA5IDExLjE1OSA0NS40NTU0IDEwLjkyNjggNDYuMjU3NyAxMC40NjgyQzQ3LjA2NTMgMTAuMDA2NiA0Ny40NzQ4IDguNzkyODMgNDcuNDc0OCA2Ljg2MTM2QzQ3LjQ3NDggNi4zNjIzOSA0Ny40NTY5IDUuOTY4MTEgNDcuNDIxNCA1LjY4OTMzTDQ3LjQxNzggNS42NjAxWiIgZmlsbD0iIzI1NDc3QiIvPgo8cGF0aCBkPSJNNDkuNDEyMSAxMS4wNDk4SDYxLjExNDVWOS45NjQ2M0g1MS41MjcxVjYuMDMyMjlINjAuNzE2NlY0Ljk0NzI1SDUxLjUyNzFWMS4zNjc2SDYxLjExNDVWMC4yODI0NEg0OS40MTIxVjExLjA0OThaIiBmaWxsPSIjMjU0NzdCIi8+CjxwYXRoIGQ9Ik03Ni44NTk5IDEuMDY0OEM3NS45OTk4IDAuNDcyNTUgNzMuODMzIDAuMTcyMDI4IDcwLjQxOTcgMC4xNzIwMjhDNjcuNDY2NSAwLjE3MjAyOCA2NS40MTkyIDAuNDM1NzEzIDY0LjMzNjQgMC45NTU5NTNDNjMuMjQ1NSAxLjQ4IDYyLjY5MjUgMi40Njk3MyA2Mi42OTI1IDMuODk2ODlMNjIuNjY2IDQuOTA5MDlWNS42NjE2NEM2Mi42NjYgOC4xMjI2MiA2My4wOTIzIDkuNjcxOTQgNjMuOTMyNyAxMC4yNjYxQzY0Ljc3MTEgMTAuODU4NSA2Ni45NTM3IDExLjE1OSA3MC40MTk3IDExLjE1OUM3My45MDQgMTEuMTU5IDc2LjA4ODggMTAuODU2MSA3Ni45MTM5IDEwLjI1OEM3Ny43NDExIDkuNjU4NTEgNzguMTYwNiA4LjA2NDUyIDc4LjE2MDYgNS41MjA1OUM3OC4xNjA2IDMuMTU4NDggNzcuNzIzMSAxLjY1OTQzIDc2Ljg1OTkgMS4wNjQ4Wk02NS40MDQ1IDEuODEzNzlDNjUuODgxOCAxLjQ0NDU4IDY3LjU2NDUgMS4yNTcxOSA3MC40MDY3IDEuMjU3MTlDNzIuODgxNiAxLjI1NzE5IDc0LjQ3MzEgMS40MTY5IDc1LjEzNjkgMS43MzE4Qzc1Ljc5MzEgMi4wNDMxMyA3Ni4xMjUyIDIuNzkwMjIgNzYuMTI1MiAzLjk1Mjk4TDc2LjEzODYgNC45NjM4N1Y1LjY2MTc2Qzc2LjEzODYgNy44NTIyOCA3NS44OTc1IDkuMTQ5NTYgNzUuNDIyMSA5LjUxNzQ2Qzc0Ljk0NDggOS44ODY5IDczLjI1NzQgMTAuMDczOSA3MC40MDY3IDEwLjA3MzlDNjcuNTY0NSAxMC4wNzM5IDY1Ljg4MTYgOS44ODcwMiA2NS40MDQ1IDkuNTE3NDZDNjQuOTI5MiA5LjE0OTQ0IDY0LjY4ODEgNy44NTIxNiA2NC42ODgxIDUuNjYxNzZDNjQuNjg4MSAzLjQ3NjM1IDY0LjkyOTIgMi4xODE2OSA2NS40MDQ1IDEuODEzNzlaIiBmaWxsPSIjMjU0NzdCIi8+CjxwYXRoIGQ9Ik05Mi44NDYzIDAuMjgyNDRINzguOTgyNFYxLjQ2OTQ0SDg0Ljg4MzVWMTEuMDQ5OEg4Ni45OTg2VjEuNDY5NDRIOTIuODQ2M1YwLjI4MjQ0WiIgZmlsbD0iIzI1NDc3QiIvPgo8cGF0aCBkPSJNMTAwLjMxOCAwLjI4MjQ0SDk3LjQxMTVMOTAuNzQwMiAxMS4wNDk4SDkyLjk4NTRMOTQuMzc3NCA4Ljc4NDE3SDEwMy40OThMMTA0LjkwMyAxMS4wNDk4SDEwNy4xMjJMMTAwLjMyOSAwLjI5OTA3NkwxMDAuMzE4IDAuMjgyNDRaTTEwMi44NjEgNy44MDg4MUg5NS4wMDFMOTguOTA0OCAxLjM3MTE3TDEwMi44NjEgNy44MDg4MVoiIGZpbGw9IiMyNTQ3N0IiLz4KPHBhdGggZD0iTTEyMC4yMDcgNS40MDM2OEMxMjEuNjkxIDUuMDczOTMgMTIyLjQ0MiA0LjMxNDYgMTIyLjQ0MiAzLjE0NTU0QzEyMi40NDIgMS45NzI5MiAxMjIuMDkxIDEuMTkyOTIgMTIxLjM5NyAwLjgyNzc1NEMxMjAuNzA5IDAuNDY1Nzk2IDExOS4yMzcgMC4yODI0NCAxMTcuMDIyIDAuMjgyNDRIMTA4Ljk3N1YxMS4wNDk4SDExNy44ODRDMTE5Ljc1NyAxMS4wNDk4IDEyMS4wOTEgMTAuODE1OCAxMjEuODUgMTAuMzU0MkMxMjIuNjEzIDkuODkwMDEgMTIzIDkuMDc2MzggMTIzIDcuOTM1ODRDMTIzIDYuNTQ2MzYgMTIyLjA2IDUuNjk0ODIgMTIwLjIwNyA1LjQwMzY4Wk0xMTkuNzA0IDQuNjI5NUMxMTkuMjIgNC44NjYzMyAxMTguMTE4IDQuOTg2MjMgMTE2LjQyNiA0Ljk4NjIzSDExMS4wOTJWMS4zNjc0OEgxMTYuOTgzQzExOC40MjggMS4zNjc0OCAxMTkuMzc1IDEuNDczIDExOS43OTYgMS42ODA5NkMxMjAuMjExIDEuODg1NTggMTIwLjQyIDIuMzQ2NTMgMTIwLjQyIDMuMDUxNTVDMTIwLjQyIDMuODY1NjYgMTIwLjE4IDQuMzk2NTkgMTE5LjcwNCA0LjYyOTVaTTExMS4wOTIgNi4wNzEyN0gxMTYuMzJDMTE4LjQ2MSA2LjA3MTI3IDExOS43ODkgNi4xNzk3NiAxMjAuMjY5IDYuMzkzM0MxMjAuNzM5IDYuNjAzMzkgMTIwLjk3OCA3LjE4NTU1IDEyMC45NzggOC4xMjM3MUMxMjAuOTc4IDguODc1NDMgMTIwLjc0MSA5LjM3MzgxIDEyMC4yNzQgOS42MDM5OUMxMTkuOCA5LjgzODIgMTE4Ljc3NyA5Ljk1NzAzIDExNy4yMzUgOS45NTcwM0wxMTUuMzkxIDkuOTY0NTJIMTExLjA5MlY2LjA3MTI3WiIgZmlsbD0iIzI1NDc3QiIvPgo8cGF0aCBkPSJNMCAwLjI3NzM1MUgyLjk4NzI1VjEuNTE1OEMzLjQ5ODA3IDEuMDA3MjEgNC4wNjYzOCAwLjYyNzU0NCA0LjY4OTk1IDAuMzc2NDU1QzUuMzE0NyAwLjEyNTYwNCA1Ljk5NDg0IDAgNi43MzI3MiAwQzcuNDc2OTEgMCA4LjE0ODc4IDAuMTY1MjkzIDguNzQ3MTQgMC40OTUyODZDOS4zNDYxNyAwLjgyNTUxNiA5LjgyNzg2IDEuMzA3ODUgMTAuMTkzMyAxLjk0MTgxQzEwLjY2NzcgMS4zMDc4NSAxMS4yNDk2IDAuODI1NTE2IDExLjkzOTggMC40OTUyODZDMTIuNjMgMC4xNjUyOTMgMTMuMzgzNSAwIDE0LjIwMTQgMEMxNS4wNDc1IDAgMTUuNzkyNiAwLjE3ODI0NiAxNi40MzQ2IDAuNTM0OTc1QzE3LjA3NzYgMC44OTE3MDQgMTcuNTM5IDEuMzU3MjggMTcuODIwNyAxLjkzMjA3QzE4LjEwMTggMi41MDY2MSAxOC4yNDIgMy40NDEyMSAxOC4yNDIgNC43MzU3NVYxMS4wNTY1SDE1LjIzMjZWNS41ODc4OUMxNS4yMzI2IDQuMzY1OTUgMTUuMDY0OSAzLjUzODU0IDE0LjcyOTIgMy4xMDYxMUMxNC4zOTM4IDIuNjczMzMgMTMuODkwMyAyLjQ1NzE4IDEzLjIxOTEgMi40NTcxOEMxMi43MDg1IDIuNDU3MTggMTIuMjUwOSAyLjU4OTIgMTEuODQ1NyAyLjg1MzI0QzExLjQ0MSAzLjExNzUyIDExLjE0IDMuNDgyMzMgMTAuOTQzIDMuOTQ4MDNDMTAuNzQ2MSA0LjQxMzcyIDEwLjY0NzYgNS4xNjE2NCAxMC42NDc2IDYuMTkxOVYxMS4wNTY0SDcuNjM4MjJWNS44MzU1M0M3LjYzODIyIDQuODcxMjIgNy41NTk5OSA0LjE3MjczIDcuNDAyMSAzLjc0MDE5QzcuMjQ1MTIgMy4zMDc0MSA3LjAwOSAyLjk4NTUgNi42OTUzMiAyLjc3NDFDNi4zODA3MSAyLjU2MjU4IDYuMDAwNzQgMi40NTcxOCA1LjU1NDM2IDIuNDU3MThDNS4wNTY2NiAyLjQ1NzE4IDQuNjA1MDMgMi41OTA4NiA0LjE5OTQ3IDIuODU4MzVDMy43OTMzOCAzLjEyNTk2IDMuNDg5OTMgMy41MDA1MSAzLjI4OTI1IDMuOTgyODRDMy4wODc2NSA0LjQ2NDk0IDIuOTg3MjUgNS4yMjQ1IDIuOTg3MjUgNi4yNjE2NlYxMS4wNTY1SDBWMC4yNzczNTFaIiBmaWxsPSIjMDA3OEQzIi8+CjxwYXRoIGQ9Ik0xOC44MTg0IDAuMjc3MzY3SDIxLjg3MTJMMjQuOTYzOSA3LjA0MDg1TDI4LjM3NjkgMC4yNzczNjdIMzEuNDM4M0wyMy45ODE2IDE1SDIwLjg5NjZMMjMuMzQ0IDEwLjI1MjdMMTguODE4NCAwLjI3NzM2N1oiIGZpbGw9IiMwMDc4RDMiLz4KPC9zdmc+Cg==";
4
4
  export declare const navHeaderImageCollapsed = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOCIgaGVpZ2h0PSIxMyIgdmlld0JveD0iMCAwIDM4IDEzIiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTE1LjE5OTcgMC4yMjU0NTdIMTcuNjAyOEwyMC4wMzczIDYuMDk0TDIyLjcyNCAwLjIyNTQ1N0gyNS4xMzM5TDE5LjI2NDEgMTNIMTYuODM1NkwxOC43NjIyIDguODgwODhMMTUuMTk5NyAwLjIyNTQ1N1oiIGZpbGw9IiMwMDc4RDMiLz4KPHBhdGggZD0iTTAuMzg2NzE5IDAuMjQwNjUySDIuNzM4MjVWMS4zMTUyM0MzLjE0MDM2IDAuODczOTM1IDMuNTg3NzMgMC41NDQ1MDggNC4wNzg2IDAuMzI2NjQzQzQuNTcwMzkgMC4xMDg5ODQgNS4xMDU3OSAwIDUuNjg2NjUgMEM2LjI3MjQ2IDAgNi44MDEzNSAwLjE0MzQyMiA3LjI3MjM4IDAuNDI5NzVDNy43NDM5MiAwLjcxNjI4NCA4LjEyMzEgMS4xMzQ4IDguNDEwNzQgMS42ODQ4N0M4Ljc4NDI0IDEuMTM0OCA5LjI0MjI1IDAuNzE2Mjg0IDkuNzg1NiAwLjQyOTc1QzEwLjMyODkgMC4xNDM0MjIgMTAuOTIyMSAwIDExLjU2NTkgMEMxMi4yMzIgMCAxMi44MTg1IDAuMTU0NjYgMTMuMzIzOCAwLjQ2NDE4OEMxMy44MyAwLjc3MzcxNSAxNC4xOTMzIDEuMTc3NjkgMTQuNDE1IDEuNjc2NDJDMTQuNjM2MyAyLjE3NDk0IDE0Ljc0NjYgMi45ODU4NyAxNC43NDY2IDQuMTA5MTJWOS41OTM0OEgxMi4zNzc3VjQuODQ4NUMxMi4zNzc3IDMuNzg4MjUgMTIuMjQ1NiAzLjA3MDMyIDExLjk4MTQgMi42OTUxMUMxMS43MTczIDIuMzE5NiAxMS4zMjEgMi4xMzIwNSAxMC43OTI2IDIuMTMyMDVDMTAuMzkwNyAyLjEzMjA1IDEwLjAzMDUgMi4yNDY2IDkuNzExNTIgMi40NzU3QzkuMzkyOTkgMi43MDUwMSA5LjE1NTk4IDMuMDIxNTUgOS4wMDA4OSAzLjQyNTYzQzguODQ1OTIgMy44Mjk3IDguNzY4NDMgNC40Nzg2NiA4Ljc2ODQzIDUuMzcyNTlWOS41OTMzOEg2LjM5OTQ0VjUuMDYzMzhDNi4zOTk0NCA0LjIyNjY2IDYuMzM3ODYgMy42MjA2IDYuMjEzNTcgMy4yNDUyOUM2LjA5IDIuODY5NzggNS45MDQxMyAyLjU5MDQ2IDUuNjU3MiAyLjQwNzAzQzUuNDA5NTQgMi4yMjM1IDUuMTEwNDQgMi4xMzIwNSA0Ljc1OTA1IDIuMTMyMDVDNC4zNjcyNyAyLjEzMjA1IDQuMDExNzUgMi4yNDgwNCAzLjY5MjUgMi40ODAxNEMzLjM3MjgzIDIuNzEyMzMgMy4xMzM5NiAzLjAzNzMzIDIuOTc1OTggMy40NTU4NEMyLjgxNzI5IDMuODc0MTQgMi43MzgyNSA0LjUzMzIgMi43MzgyNSA1LjQzMzEyVjkuNTkzNDhIMC4zODY3MTlWMC4yNDA2NTJaIiBmaWxsPSIjMDA3OEQzIi8+CjxwYXRoIGQ9Ik0zNy41Njg1IDQuODc0NjhIMzEuNDA3N1Y1LjgxNjI2SDM1Ljk0MjlDMzUuOTk1MiA2LjA5NTM3IDM2LjAyMTYgNi4yNzgwNyAzNi4wMjE2IDYuMzU5MTJDMzYuMDIxNiA3LjQxODQ0IDM1LjgyMDYgOC4wODA5IDM1LjQyNDMgOC4zMjc5NEMzNS4wMjQgOC41Nzc5OCAzMy45NTYyIDguNzA0NDkgMzIuMjUwNiA4LjcwNDQ5QzMwLjAxNzcgOC43MDQ0OSAyOC42MjYgOC41NzA2NiAyOC4xMTQ0IDguMzA2MzlDMjcuNjA4NiA4LjA0NTUzIDI3LjM1MjMgNy4zMjg3MyAyNy4zNTIzIDYuMTc1MjhMMjcuMzQxOSA0Ljg0OTQyTDI3LjM2MjcgMy42MTc4MUMyNy4zNjI3IDIuNDgxODggMjcuNTkzNSAxLjc2MzQzIDI4LjA0ODUgMS40ODIyNkMyOC41MDc5IDEuMTk4NDEgMjkuNjc1OSAxLjA1NDQ3IDMxLjUyIDEuMDU0NDdDMzMuNTE4MSAxLjA1NDQ3IDM0Ljc3MDIgMS4xNDQzOCAzNS4yNDE3IDEuMzIxMzFDMzUuNzAzOCAxLjQ5NTE1IDM1LjkzODEgMS45NTg4MiAzNS45MzgxIDIuNjk5MzNWMi45NTkxNkgzNy41NzE3VjIuNTc2ODRDMzcuNTcxNyAxLjUzOTU5IDM3LjIxMSAwLjg2MTI0NiAzNi40OTk5IDAuNTYwNjlDMzUuNzk2MyAwLjI2MzUzNSAzNC4yMTU2IDAuMTEyNzkzIDMxLjgwMTcgMC4xMTI3OTNDMjkuMzkzMiAwLjExMjc5MyAyNy43Njc3IDAuMzQyOTI4IDI2Ljk3IDAuNzk2NzAxQzI2LjE2NzQgMS4yNTM3NyAyNS43NjA0IDIuMTg0NDIgMjUuNzYwNCAzLjU2Mjc1TDI1Ljc1IDQuMzg2MzdMMjUuNzYwNCA1Ljc1MzY3QzI1Ljc2MDQgNy4zOTMzOCAyNi4xMjY1IDguNDY0NDYgMjYuODQ4NSA4LjkzNzQxQzI3LjU2NjYgOS40MDc0NyAyOS4yMDE0IDkuNjQ1OTYgMzEuNzA3OCA5LjY0NTk2QzM0LjM1OTIgOS42NDU5NiAzNi4wMjM3IDkuNDQ0NDkgMzYuNjU1MyA5LjA0NjZDMzcuMjkxIDguNjQ2MDMgMzcuNjEzMyA3LjU5Mjg5IDM3LjYxMzMgNS45MTY5OUMzNy42MTMzIDUuNDg0MDUgMzcuNTk5MyA1LjE0MTk0IDM3LjU3MTMgNC45MDAwNUwzNy41Njg1IDQuODc0NjhaIiBmaWxsPSIjMjU0NzdCIi8+Cjwvc3ZnPg==";
5
5
  export declare const detectOverlayScrollbar: () => boolean;
6
6
  export declare function isImageUrl(str?: string | ReactNode): boolean;
7
7
  export declare const filterPrimaryNavItems: <T extends PropsWithChildren>(element: ReactElement<T>, isNegative: boolean) => ReactElement<T> | null;
8
- export declare const collectNavItems: (element: ReactElement) => (ReactElement<ILinkNavItem> | ReactElement<IButtonNavItem>)[];
8
+ export declare const collectNavItems: (element: ReactElement) => (ReactElement<ILinkNavItem> | ReactElement<TButtonNavItem>)[];
@@ -168,7 +168,8 @@ const PillExpandableChild = ({
168
168
  type,
169
169
  errorHandler,
170
170
  content,
171
- autoExpandChildren
171
+ autoExpandChildren,
172
+ dataAnalyticsId
172
173
  }) => {
173
174
  const pillId = useId();
174
175
  const descriptionId = useId();
@@ -226,7 +227,8 @@ const PillExpandableChild = ({
226
227
  onExpand: handleChildToggle,
227
228
  type: type,
228
229
  errorHandler: errorHandler,
229
- descriptionId: descriptionId
230
+ descriptionId: descriptionId,
231
+ dataAnalyticsId: dataAnalyticsId
230
232
  }), _jsx(PillContent, {
231
233
  onActionClick: handleChildToggle,
232
234
  descriptionText: content.description,
@@ -547,7 +549,8 @@ const PillExpandableBase = ({
547
549
  type: type,
548
550
  errorHandler: errorHandler,
549
551
  content: content,
550
- autoExpandChildren: autoExpandChildren
552
+ autoExpandChildren: autoExpandChildren,
553
+ dataAnalyticsId: dataAnalyticsId
551
554
  });
552
555
  }
553
556
  return _jsx(PillContext.Provider, {
@@ -16,5 +16,6 @@ export interface IRangeField {
16
16
  errorString?: string;
17
17
  direction?: TDirection;
18
18
  isMinField: boolean;
19
+ integer?: boolean;
19
20
  }
20
- export declare const RangeField: ({ value, onChange, onBlur, id, label, unit, disabled, min, max, inputRef, errorString, className, direction, isMinField }: IRangeField) => import("react/jsx-runtime").JSX.Element;
21
+ export declare const RangeField: ({ value, onChange, onBlur, id, label, unit, disabled, min, max, inputRef, errorString, className, direction, isMinField, integer }: IRangeField) => import("react/jsx-runtime").JSX.Element;
@@ -4,7 +4,7 @@ import { TextInput } from "../../textInput/textInput";
4
4
  import { classNames } from "../../commonHelpers/classNames/classNames";
5
5
  import { IconException } from "../../icons/iconException";
6
6
  import { useMobileClassName } from "../../utils/theme/useMobileClassName";
7
- export const RangeField = ({ value, onChange, onBlur, id, label, unit, disabled, min, max, inputRef, errorString, className, direction, isMinField }) => {
7
+ export const RangeField = ({ value, onChange, onBlur, id, label, unit, disabled, min, max, inputRef, errorString, className, direction, isMinField, integer }) => {
8
8
  const inputId = useId();
9
9
  const labelId = useId();
10
10
  const unitId = useId();
@@ -12,7 +12,8 @@ export const RangeField = ({ value, onChange, onBlur, id, label, unit, disabled,
12
12
  const mobileSenseClass = useMobileClassName("zen-range-field__sense");
13
13
  const mobileInputClass = useMobileClassName("zen-range-field__input");
14
14
  const handleChange = e => {
15
- const newValue = e.target.value ? Number(e.target.value) : null;
15
+ const rawValue = e.target.value ? Number(e.target.value) : null;
16
+ const newValue = integer && rawValue !== null ? Math.trunc(rawValue) : rawValue;
16
17
  onChange(id, newValue);
17
18
  };
18
19
  return (_jsxs("div", { className: classNames([
@@ -20,5 +21,5 @@ export const RangeField = ({ value, onChange, onBlur, id, label, unit, disabled,
20
21
  direction === "row" ? "zen-range-field--row" : "zen-range-field--column",
21
22
  isMinField ? "zen-range-field--min" : "zen-range-field--max",
22
23
  className || ""
23
- ]), children: [_jsxs("div", { className: "zen-range-field__content", children: [_jsx("label", { className: classNames(["zen-range-field__label", "zen-ellipsis", mobileLabelClass || ""]), id: labelId, htmlFor: inputId, children: label }), _jsx("div", { className: "zen-range-field__input-container", children: _jsx(TextInput, { inputClassName: classNames(["zen-range-field__input", mobileInputClass || "", errorString ? "zen-text-input--error" : ""]), value: value !== null ? value.toString() : "", placeholder: "", onChange: handleChange, onBlur: onBlur, disabled: disabled, type: "number", ref: inputRef, id: inputId, error: "", ariaLabelledby: `${labelId} ${unit ? unitId : ""}`, min: min, max: max }) }), unit ? (_jsx("div", { id: unitId, className: classNames(["zen-range-field__sense", "zen-ellipsis", mobileSenseClass || ""]), children: _jsx("div", { className: "zen-range-field__sense-text zen-ellipsis", children: unit }) })) : null] }), errorString && direction !== "row" ? (_jsxs("div", { className: "zen-range-field__error", children: [_jsx(IconException, { className: "zen-range-field__error-icon", size: "bigger" }), _jsx("div", { className: "zen-range-field__error-text", children: errorString })] })) : null] }));
24
+ ]), children: [_jsxs("div", { className: "zen-range-field__content", children: [_jsx("label", { className: classNames(["zen-range-field__label", "zen-ellipsis", mobileLabelClass || ""]), id: labelId, htmlFor: inputId, children: label }), _jsx("div", { className: "zen-range-field__input-container", children: _jsx(TextInput, { inputClassName: classNames(["zen-range-field__input", mobileInputClass || "", errorString ? "zen-text-input--error" : ""]), value: value !== null ? value.toString() : "", placeholder: "", onChange: handleChange, onBlur: onBlur, disabled: disabled, type: "number", ref: inputRef, id: inputId, error: "", ariaLabelledby: `${labelId} ${unit ? unitId : ""}`, min: min, max: max, step: integer ? 1 : undefined }) }), unit ? (_jsx("div", { id: unitId, className: classNames(["zen-range-field__sense", "zen-ellipsis", mobileSenseClass || ""]), children: _jsx("div", { className: "zen-range-field__sense-text zen-ellipsis", children: unit }) })) : null] }), errorString && direction !== "row" ? (_jsxs("div", { className: "zen-range-field__error", children: [_jsx(IconException, { className: "zen-range-field__error-icon", size: "bigger" }), _jsx("div", { className: "zen-range-field__error-text", children: errorString })] })) : null] }));
24
25
  };
@@ -273,7 +273,8 @@ export const RangeRaw = props => {
273
273
  chip,
274
274
  chipIcon,
275
275
  chipId,
276
- chipName
276
+ chipName,
277
+ integer
277
278
  } = props;
278
279
  const labelLocal = label && !title ? label : title;
279
280
  const accessibilityProps = getAriaAttributes(props);
@@ -424,8 +425,9 @@ export const RangeRaw = props => {
424
425
  max: max,
425
426
  min: min,
426
427
  isMinField: true,
427
- direction: directionState
428
- }), [labelMin, translate, unit, currentValue.min, errorType, min, max, handleChange, handleBlur, directionState]);
428
+ direction: directionState,
429
+ integer: integer
430
+ }), [labelMin, translate, unit, currentValue.min, errorType, min, max, handleChange, handleBlur, directionState, integer]);
429
431
  const memoizedMaxRangeField = useMemo(() => _jsx(RangeField, {
430
432
  className: "zen-range__max-field",
431
433
  label: labelMax || translate("Max"),
@@ -439,8 +441,9 @@ export const RangeRaw = props => {
439
441
  max: max,
440
442
  min: min,
441
443
  isMinField: false,
442
- direction: directionState
443
- }), [max, min, labelMax, handleChange, handleBlur, currentValue.max, translate, unit, errorType, directionState]);
444
+ direction: directionState,
445
+ integer: integer
446
+ }), [max, min, labelMax, handleChange, handleBlur, currentValue.max, translate, unit, errorType, directionState, integer]);
444
447
  const selection = useMemo(() => getSelection(), [getSelection]);
445
448
  const memoizedContent = useMemo(() => _jsxs("div", {
446
449
  className: "zen-range__fields-wrapper",
@@ -20,6 +20,7 @@ export interface IRangeRawBase extends IZenComponentProps, IChipsParent {
20
20
  allowEqualMinMax?: boolean;
21
21
  error?: string;
22
22
  triggerAriaLabel?: string;
23
+ integer?: boolean;
23
24
  }
24
25
  export interface IRangeRawDefault extends IRangeRawBase {
25
26
  onChange: (values: IRangeValuesRaw) => void;
@@ -15,12 +15,13 @@ import { useMobile } from "../commonHelpers/hooks/useMobile";
15
15
  import { isAlertTarget } from "../alert/utils/isAlertTarget";
16
16
  import { isToastTarget } from "../toast/utils/isToastTarget";
17
17
  import { isModalTarget } from "../modal/utils/isModalTarget";
18
- import { isBannerTarget } from "../banner/utils/isBannerTarget";
19
18
  import { useClientReady } from "../commonHelpers/hooks/useClientReady";
20
19
  import { zen } from "../utils/zen";
21
20
  import { usePortal } from "../commonHelpers/hooks/usePortal";
22
21
  /* eslint-enable @typescript-eslint/naming-convention */
23
- const OVERLAY_TARGET_CHECKS = [isAlertTarget, isToastTarget, isModalTarget, isBannerTarget];
22
+ // Banner is an inline/static element, not a floating overlay — clicks on it should close the panel.
23
+ // Only Modal, Toast and Alert are floating overlays that should suppress click-outside close.
24
+ const OVERLAY_TARGET_CHECKS = [isAlertTarget, isToastTarget, isModalTarget];
24
25
  export const isChildPopup = (target, stopElement) => {
25
26
  var _a;
26
27
  const parentPopup = getParentByClass(target, "zen-absolute");
@@ -5,6 +5,19 @@ export declare enum SelectionState {
5
5
  None = "none"
6
6
  }
7
7
  export interface ISelectionValue {
8
+ /**
9
+ * Whether every selectable row across every page is selected.
10
+ */
8
11
  all: boolean;
12
+ /**
13
+ * The meaning of this list depends on `all`:
14
+ * - when `all` is `false`, it is the ids explicitly *included* in the selection.
15
+ * - when `all` is `true`, it is the ids explicitly *excluded* from the otherwise
16
+ * fully-selected set (e.g. rows unchecked after a "select all" across paginated data).
17
+ *
18
+ * This lets `all: true` represent "all except these ids" without needing to know the
19
+ * full id universe (MYG-142234) - unchecking a row while `all` is true only ever adds
20
+ * to this exclusion list; it must never be rebuilt from the currently rendered page.
21
+ */
9
22
  selected: string[];
10
23
  }
@@ -133,19 +133,30 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
133
133
  }
134
134
  }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
135
135
  const getNewSelection = useCallback((id, checked) => {
136
- if (state === SelectionState.All) {
137
- const newSelection = getCurrentPageSelection(entities, selectable);
138
- checkRow(newSelection, selectable, entities, id, false);
139
- return newSelection;
136
+ var _a;
137
+ if ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) {
138
+ // MYG-142234: `selections` holds the ids excluded from the "all" selection.
139
+ // Toggling a row here must only ever add/remove that single id from the
140
+ // exclusion set - it must never be rebuilt from `entities` (the current page),
141
+ // otherwise every other page's rows would silently fall out of the selection.
142
+ const excluded = new Set(selections);
143
+ checkRow(excluded, selectable, entities, id, !checked);
144
+ return excluded;
140
145
  }
141
146
  checkRow(selections, selectable, entities, id, checked);
142
147
  return selections;
143
- }, [state, entities, selectable, selections]);
148
+ }, [entities, selectable, selections]);
144
149
  const onSelectListener = useCallback((id, checked) => {
145
150
  var _a;
151
+ const wasAllSelected = ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false;
146
152
  const newSelection = getNewSelection(id, checked);
147
- const newState = calculateSelectionState(entities, newSelection, ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
148
- const newValue = {
153
+ const newState = calculateSelectionState(entities, newSelection, wasAllSelected, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
154
+ // MYG-142234: once selection spans "all" pages, unchecking a row must keep `all: true`
155
+ // and only record the exclusion - it must not collapse to the current page's ids.
156
+ const newValue = wasAllSelected ? {
157
+ selected: [...newSelection],
158
+ all: true
159
+ } : {
149
160
  selected: [...newSelection],
150
161
  all: newState === SelectionState.All
151
162
  };
@@ -154,7 +165,7 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
154
165
  setSelectMode(newValue.all || newValue.selected.length > 0);
155
166
  }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, setState, getNewSelection]);
156
167
  const onChangeSelectionState = useCallback(newState => {
157
- var _a, _b;
168
+ var _a, _b, _c;
158
169
  if (newState === SelectionState.All) {
159
170
  (selectable === null || selectable === void 0 ? void 0 : selectable.onSelect) && selectable.onSelect({
160
171
  selected: [],
@@ -173,7 +184,11 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
173
184
  }
174
185
  const newSelections = getCurrentPageSelection(entities, selectable);
175
186
  setState(newState);
176
- const previousSelections = (_b = (_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.selected) !== null && _b !== void 0 ? _b : [];
187
+ // MYG-142234: previous `selected` ids mean something different depending on `all` -
188
+ // included ids when `all` is false, excluded ids when `all` is true. Switching to an
189
+ // explicit "select current page" here always leaves `all: false`, so a previous
190
+ // exclusion list must not be reinterpreted as an inclusion list.
191
+ const previousSelections = ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) ? [] : (_c = (_b = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _b === void 0 ? void 0 : _b.selected) !== null && _c !== void 0 ? _c : [];
177
192
  const mergedSelections = new Set([...previousSelections, ...newSelections]);
178
193
  const newValue = {
179
194
  selected: [...mergedSelections],
@@ -192,7 +207,12 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
192
207
  }
193
208
  return translate("Deselect all");
194
209
  }, [selectable, selectableState, state, translate]);
195
- const isEntityChecked = useCallback((entity, isNested) => isRowChecked(selections, state, selectable === null || selectable === void 0 ? void 0 : selectable.strategy, entity, isNested), [selections, state, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
210
+ const isEntityChecked = useCallback(
211
+ // MYG-142234: while `selection.all` is true, `selections` holds excluded ids, and every
212
+ // row must be evaluated through the SelectionState.All (exclusion-aware) branch of
213
+ // isRowChecked - not the per-page `state`, which can be Partial/None for a page that
214
+ // contains some of those exclusions.
215
+ (entity, isNested) => isRowChecked(selections, isAllSelected ? SelectionState.All : state, selectable === null || selectable === void 0 ? void 0 : selectable.strategy, entity, isNested), [selections, isAllSelected, state, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
196
216
  const hasSelectable = !!selectable;
197
217
  const selectableCheckboxSelectsCurrentPage = selectable === null || selectable === void 0 ? void 0 : selectable.checkboxSelectsCurrentPage;
198
218
  const selectableTurnOffSelectAll = selectable === null || selectable === void 0 ? void 0 : selectable.turnOffSelectAll;
@@ -244,16 +264,30 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
244
264
  checked: state === SelectionState.CurrentPage || state === SelectionState.All,
245
265
  indeterminate: state === SelectionState.Partial,
246
266
  onChange: newValue => {
247
- var _a, _b;
267
+ var _a, _b, _c;
248
268
  if (newValue) {
249
269
  const newState = checkboxEmitsCurrentPage ? SelectionState.CurrentPage : SelectionState.All;
250
270
  setState(newState);
251
271
  onChangeSelectionState(newState);
272
+ } else if ((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.all) {
273
+ // MYG-142234: in exclusion mode, `selected` holds excluded ids. Unchecking
274
+ // the header must add the current page's ids to that exclusion set and
275
+ // keep `all: true` - it must never be treated as an inclusion list, or
276
+ // every other page's selection would be silently wiped out.
277
+ const currentPageIds = getCurrentPageSelection(entities, selectable);
278
+ const newExcluded = new Set([...selectable.selection.selected, ...currentPageIds]);
279
+ const newState = calculateSelectionState(entities, newExcluded, true, checkboxVisible, checkboxDisabled, selectable.strategy);
280
+ setState(newState);
281
+ selectable.onSelect({
282
+ selected: [...newExcluded],
283
+ all: true
284
+ });
285
+ setSelectMode(true);
252
286
  } else {
253
287
  // Intentionally different from onSelect(None) ("Deselect all" menu item, which clears everything).
254
288
  // Unchecking the header checkbox only removes the current page from the selection, preserving other pages.
255
289
  const currentPageIds = getCurrentPageSelection(entities, selectable);
256
- const remaining = ((_b = (_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.selected) !== null && _b !== void 0 ? _b : []).filter(id => !currentPageIds.has(id));
290
+ const remaining = ((_c = (_b = selectable.selection) === null || _b === void 0 ? void 0 : _b.selected) !== null && _c !== void 0 ? _c : []).filter(id => !currentPageIds.has(id));
257
291
  const newState = remaining.length > 0 ? SelectionState.Partial : SelectionState.None;
258
292
  setState(newState);
259
293
  selectable.onSelect({