@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.
- package/dist/absolute/absolute.js +1 -1
- package/dist/betaPill/betaPill.js +9 -9
- package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
- package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.js +5 -1
- package/dist/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
- package/dist/commonHelpers/hooks/useTrapFocus.js +66 -14
- package/dist/controlledPopup/controlledPopup.d.ts +1 -1
- package/dist/controlledPopup/controlledPopup.js +1 -1
- package/dist/dropdownRaw/dropdownSearchableTrigger.js +1 -1
- package/dist/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
- package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
- package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
- package/dist/filtersBar/filtersContainer/filtersContainer.js +7 -4
- package/dist/header/header.js +9 -2
- package/dist/header/headerHelpers.js +8 -5
- package/dist/index.css +142 -11
- package/dist/index.d.ts +6 -1
- package/dist/index.js +12 -3
- package/dist/modal/modal.d.ts +48 -0
- package/dist/modal/modal.js +37 -7
- package/dist/nav/navHeader/navHeader.d.ts +1 -1
- package/dist/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
- package/dist/nav/navItem/navItem.d.ts +15 -2
- package/dist/nav/navItem/navItem.js +49 -7
- package/dist/nav/utils/navUtils.d.ts +2 -2
- package/dist/pillExpandable/pillExpandable.js +6 -3
- package/dist/rangeRaw/components/rangeField.d.ts +2 -1
- package/dist/rangeRaw/components/rangeField.js +4 -3
- package/dist/rangeRaw/rangeRaw.js +8 -5
- package/dist/rangeRaw/types.d.ts +1 -0
- package/dist/sidePanel/sidePanel.js +3 -2
- package/dist/table/selectable/interfaces.d.ts +13 -0
- package/dist/table/selectable/useSelectableRows.js +46 -12
- package/dist/table/selectable/utils/calculateSelectionState.js +35 -0
- package/dist/table/selectable/utils/isRowChecked.js +12 -1
- package/dist/table/table.d.ts +2 -0
- package/dist/table/table.js +3 -1
- package/dist/tabs/tabs.js +14 -5
- package/dist/textInputRaw/textInputRaw.js +1 -1
- package/dist/textInputRaw/types.d.ts +1 -0
- package/dist/tooltip/components/inlineTrigger.d.ts +43 -0
- package/dist/tooltip/components/inlineTrigger.js +42 -0
- package/dist/tooltip/tooltip.d.ts +5 -2
- package/dist/tooltip/tooltip.js +15 -4
- package/dist/trendIndicator/trendIndicator.d.ts +47 -0
- package/dist/trendIndicator/trendIndicator.js +47 -0
- package/dist/trendIndicator/trendIndicatorUtils.d.ts +9 -0
- package/dist/trendIndicator/trendIndicatorUtils.js +18 -0
- package/dist/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
- package/dist/utils/experimentalFeatures/experimentalContext.js +29 -0
- package/dist/utils/experimentalFeatures/index.d.ts +4 -0
- package/dist/utils/experimentalFeatures/index.js +9 -0
- package/dist/utils/experimentalFeatures/registry.d.ts +13 -0
- package/dist/utils/experimentalFeatures/registry.js +11 -0
- package/dist/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
- package/dist/utils/experimentalFeatures/useExperimentalFeature.js +25 -0
- package/dist/utils/localization/translations/ar.json +1 -1
- package/dist/utils/localization/translations/bg-BG.json +1 -1
- package/dist/utils/localization/translations/cs.json +1 -1
- package/dist/utils/localization/translations/da-DK.json +1 -1
- package/dist/utils/localization/translations/de.json +1 -1
- package/dist/utils/localization/translations/el-GR.json +1 -1
- package/dist/utils/localization/translations/en.json +1 -1
- package/dist/utils/localization/translations/es.json +1 -1
- package/dist/utils/localization/translations/et-EE.json +1 -1
- package/dist/utils/localization/translations/fi-FI.json +1 -1
- package/dist/utils/localization/translations/fr-FR.json +1 -1
- package/dist/utils/localization/translations/fr.json +1 -1
- package/dist/utils/localization/translations/hu-HU.json +1 -1
- package/dist/utils/localization/translations/id.json +1 -1
- package/dist/utils/localization/translations/it.json +1 -1
- package/dist/utils/localization/translations/ja.json +1 -1
- package/dist/utils/localization/translations/ka-GE.json +1 -1
- package/dist/utils/localization/translations/ko-KR.json +1 -1
- package/dist/utils/localization/translations/lt-LT.json +1 -1
- package/dist/utils/localization/translations/lv-LV.json +1 -1
- package/dist/utils/localization/translations/ms.json +1 -1
- package/dist/utils/localization/translations/nb-NO.json +1 -1
- package/dist/utils/localization/translations/nl.json +1 -1
- package/dist/utils/localization/translations/pl.json +1 -1
- package/dist/utils/localization/translations/pt-BR.json +1 -1
- package/dist/utils/localization/translations/pt-PT.json +1 -1
- package/dist/utils/localization/translations/ro-RO.json +1 -1
- package/dist/utils/localization/translations/sk-SK.json +1 -1
- package/dist/utils/localization/translations/sv.json +1 -1
- package/dist/utils/localization/translations/th.json +1 -1
- package/dist/utils/localization/translations/tr.json +1 -1
- package/dist/utils/localization/translations/zh-Hans.json +1 -1
- package/dist/utils/localization/translations/zh-TW.json +1 -1
- package/esm/absolute/absolute.js +1 -1
- package/esm/betaPill/betaPill.js +9 -9
- package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
- package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.js +3 -0
- package/esm/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
- package/esm/commonHelpers/hooks/useTrapFocus.js +67 -15
- package/esm/controlledPopup/controlledPopup.d.ts +1 -1
- package/esm/controlledPopup/controlledPopup.js +1 -1
- package/esm/dropdownRaw/dropdownSearchableTrigger.js +1 -1
- package/esm/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
- package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
- package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
- package/esm/filtersBar/filtersContainer/filtersContainer.js +7 -4
- package/esm/header/header.js +9 -2
- package/esm/header/headerHelpers.js +8 -5
- package/esm/index.d.ts +6 -1
- package/esm/index.js +5 -1
- package/esm/modal/modal.d.ts +48 -0
- package/esm/modal/modal.js +37 -7
- package/esm/nav/navHeader/navHeader.d.ts +1 -1
- package/esm/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
- package/esm/nav/navItem/navItem.d.ts +15 -2
- package/esm/nav/navItem/navItem.js +49 -7
- package/esm/nav/utils/navUtils.d.ts +2 -2
- package/esm/pillExpandable/pillExpandable.js +6 -3
- package/esm/rangeRaw/components/rangeField.d.ts +2 -1
- package/esm/rangeRaw/components/rangeField.js +4 -3
- package/esm/rangeRaw/rangeRaw.js +8 -5
- package/esm/rangeRaw/types.d.ts +1 -0
- package/esm/sidePanel/sidePanel.js +3 -2
- package/esm/table/selectable/interfaces.d.ts +13 -0
- package/esm/table/selectable/useSelectableRows.js +46 -12
- package/esm/table/selectable/utils/calculateSelectionState.js +35 -0
- package/esm/table/selectable/utils/isRowChecked.js +12 -1
- package/esm/table/table.d.ts +2 -0
- package/esm/table/table.js +2 -0
- package/esm/tabs/tabs.js +14 -5
- package/esm/testHelpers/renderWithFeatures.d.ts +4 -0
- package/esm/testHelpers/renderWithFeatures.js +6 -0
- package/esm/textInputRaw/textInputRaw.js +1 -1
- package/esm/textInputRaw/types.d.ts +1 -0
- package/esm/tooltip/components/inlineTrigger.d.ts +43 -0
- package/esm/tooltip/components/inlineTrigger.js +38 -0
- package/esm/tooltip/tooltip.d.ts +5 -2
- package/esm/tooltip/tooltip.js +15 -4
- package/esm/trendIndicator/trendIndicator.d.ts +47 -0
- package/esm/trendIndicator/trendIndicator.js +43 -0
- package/esm/trendIndicator/trendIndicatorUtils.d.ts +9 -0
- package/esm/trendIndicator/trendIndicatorUtils.js +14 -0
- package/esm/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
- package/esm/utils/experimentalFeatures/experimentalContext.js +25 -0
- package/esm/utils/experimentalFeatures/index.d.ts +4 -0
- package/esm/utils/experimentalFeatures/index.js +3 -0
- package/esm/utils/experimentalFeatures/registry.d.ts +13 -0
- package/esm/utils/experimentalFeatures/registry.js +8 -0
- package/esm/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
- package/esm/utils/experimentalFeatures/useExperimentalFeature.js +21 -0
- package/esm/utils/localization/translations/ar.json +1 -1
- package/esm/utils/localization/translations/bg-BG.json +1 -1
- package/esm/utils/localization/translations/cs.json +1 -1
- package/esm/utils/localization/translations/da-DK.json +1 -1
- package/esm/utils/localization/translations/de.json +1 -1
- package/esm/utils/localization/translations/el-GR.json +1 -1
- package/esm/utils/localization/translations/en.json +1 -1
- package/esm/utils/localization/translations/es.json +1 -1
- package/esm/utils/localization/translations/et-EE.json +1 -1
- package/esm/utils/localization/translations/fi-FI.json +1 -1
- package/esm/utils/localization/translations/fr-FR.json +1 -1
- package/esm/utils/localization/translations/fr.json +1 -1
- package/esm/utils/localization/translations/hu-HU.json +1 -1
- package/esm/utils/localization/translations/id.json +1 -1
- package/esm/utils/localization/translations/it.json +1 -1
- package/esm/utils/localization/translations/ja.json +1 -1
- package/esm/utils/localization/translations/ka-GE.json +1 -1
- package/esm/utils/localization/translations/ko-KR.json +1 -1
- package/esm/utils/localization/translations/lt-LT.json +1 -1
- package/esm/utils/localization/translations/lv-LV.json +1 -1
- package/esm/utils/localization/translations/ms.json +1 -1
- package/esm/utils/localization/translations/nb-NO.json +1 -1
- package/esm/utils/localization/translations/nl.json +1 -1
- package/esm/utils/localization/translations/pl.json +1 -1
- package/esm/utils/localization/translations/pt-BR.json +1 -1
- package/esm/utils/localization/translations/pt-PT.json +1 -1
- package/esm/utils/localization/translations/ro-RO.json +1 -1
- package/esm/utils/localization/translations/sk-SK.json +1 -1
- package/esm/utils/localization/translations/sv.json +1 -1
- package/esm/utils/localization/translations/th.json +1 -1
- package/esm/utils/localization/translations/tr.json +1 -1
- package/esm/utils/localization/translations/zh-Hans.json +1 -1
- package/esm/utils/localization/translations/zh-TW.json +1 -1
- package/package.json +36 -16
- package/dist/banner/utils/isBannerTarget.d.ts +0 -1
- package/dist/banner/utils/isBannerTarget.js +0 -11
- package/esm/banner/utils/isBannerTarget.d.ts +0 -1
- package/esm/banner/utils/isBannerTarget.js +0 -7
package/esm/modal/modal.d.ts
CHANGED
|
@@ -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>;
|
package/esm/modal/modal.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
120
|
+
(_a = zen.document) === null || _a === void 0 ? void 0 : _a.body.classList.remove(MODAL_SCROLL_LOCK_CLASS);
|
|
101
121
|
if (isOpen) {
|
|
102
|
-
|
|
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(
|
|
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").
|
|
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:
|
|
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
|
-
|
|
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 |
|
|
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({
|
|
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 = (
|
|
81
|
-
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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 {
|
|
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<
|
|
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
|
|
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
|
};
|
package/esm/rangeRaw/rangeRaw.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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",
|
package/esm/rangeRaw/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
}, [
|
|
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,
|
|
148
|
-
|
|
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
|
-
|
|
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(
|
|
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 = ((
|
|
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({
|