@geotab/zenith 3.15.0 → 3.16.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +21 -0
  2. package/dist/absolute/absolute.js +1 -1
  3. package/dist/betaPill/betaPill.js +9 -9
  4. package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
  5. package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.js +5 -1
  6. package/dist/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
  7. package/dist/commonHelpers/hooks/useTrapFocus.js +66 -14
  8. package/dist/controlledPopup/controlledPopup.d.ts +1 -1
  9. package/dist/controlledPopup/controlledPopup.js +1 -1
  10. package/dist/dropdownRaw/dropdownSearchableTrigger.js +1 -1
  11. package/dist/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
  12. package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
  13. package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
  14. package/dist/filtersBar/filtersContainer/filtersContainer.js +7 -4
  15. package/dist/header/header.js +9 -2
  16. package/dist/header/headerHelpers.js +8 -5
  17. package/dist/index.css +142 -11
  18. package/dist/index.d.ts +6 -1
  19. package/dist/index.js +12 -3
  20. package/dist/modal/modal.d.ts +48 -0
  21. package/dist/modal/modal.js +37 -7
  22. package/dist/nav/navHeader/navHeader.d.ts +1 -1
  23. package/dist/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
  24. package/dist/nav/navItem/navItem.d.ts +15 -2
  25. package/dist/nav/navItem/navItem.js +49 -7
  26. package/dist/nav/utils/navUtils.d.ts +2 -2
  27. package/dist/pillExpandable/pillExpandable.js +6 -3
  28. package/dist/rangeRaw/components/rangeField.d.ts +2 -1
  29. package/dist/rangeRaw/components/rangeField.js +4 -3
  30. package/dist/rangeRaw/rangeRaw.js +8 -5
  31. package/dist/rangeRaw/types.d.ts +1 -0
  32. package/dist/sidePanel/sidePanel.js +3 -2
  33. package/dist/table/selectable/interfaces.d.ts +13 -0
  34. package/dist/table/selectable/useSelectableRows.js +46 -12
  35. package/dist/table/selectable/utils/calculateSelectionState.js +35 -0
  36. package/dist/table/selectable/utils/isRowChecked.js +12 -1
  37. package/dist/table/table.d.ts +2 -0
  38. package/dist/table/table.js +3 -1
  39. package/dist/tabs/tabs.js +14 -5
  40. package/dist/textInputRaw/textInputRaw.js +1 -1
  41. package/dist/textInputRaw/types.d.ts +1 -0
  42. package/dist/tooltip/components/inlineTrigger.d.ts +43 -0
  43. package/dist/tooltip/components/inlineTrigger.js +42 -0
  44. package/dist/tooltip/tooltip.d.ts +5 -2
  45. package/dist/tooltip/tooltip.js +15 -4
  46. package/dist/trendIndicator/trendIndicator.d.ts +47 -0
  47. package/dist/trendIndicator/trendIndicator.js +47 -0
  48. package/dist/trendIndicator/trendIndicatorUtils.d.ts +9 -0
  49. package/dist/trendIndicator/trendIndicatorUtils.js +18 -0
  50. package/dist/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
  51. package/dist/utils/experimentalFeatures/experimentalContext.js +29 -0
  52. package/dist/utils/experimentalFeatures/index.d.ts +4 -0
  53. package/dist/utils/experimentalFeatures/index.js +9 -0
  54. package/dist/utils/experimentalFeatures/registry.d.ts +13 -0
  55. package/dist/utils/experimentalFeatures/registry.js +11 -0
  56. package/dist/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
  57. package/dist/utils/experimentalFeatures/useExperimentalFeature.js +25 -0
  58. package/dist/utils/localization/translations/ar.json +1 -1
  59. package/dist/utils/localization/translations/bg-BG.json +1 -1
  60. package/dist/utils/localization/translations/cs.json +1 -1
  61. package/dist/utils/localization/translations/da-DK.json +1 -1
  62. package/dist/utils/localization/translations/de.json +1 -1
  63. package/dist/utils/localization/translations/el-GR.json +1 -1
  64. package/dist/utils/localization/translations/en.json +1 -1
  65. package/dist/utils/localization/translations/es.json +1 -1
  66. package/dist/utils/localization/translations/et-EE.json +1 -1
  67. package/dist/utils/localization/translations/fi-FI.json +1 -1
  68. package/dist/utils/localization/translations/fr-FR.json +1 -1
  69. package/dist/utils/localization/translations/fr.json +1 -1
  70. package/dist/utils/localization/translations/hu-HU.json +1 -1
  71. package/dist/utils/localization/translations/id.json +1 -1
  72. package/dist/utils/localization/translations/it.json +1 -1
  73. package/dist/utils/localization/translations/ja.json +1 -1
  74. package/dist/utils/localization/translations/ka-GE.json +1 -1
  75. package/dist/utils/localization/translations/ko-KR.json +1 -1
  76. package/dist/utils/localization/translations/lt-LT.json +1 -1
  77. package/dist/utils/localization/translations/lv-LV.json +1 -1
  78. package/dist/utils/localization/translations/ms.json +1 -1
  79. package/dist/utils/localization/translations/nb-NO.json +1 -1
  80. package/dist/utils/localization/translations/nl.json +1 -1
  81. package/dist/utils/localization/translations/pl.json +1 -1
  82. package/dist/utils/localization/translations/pt-BR.json +1 -1
  83. package/dist/utils/localization/translations/pt-PT.json +1 -1
  84. package/dist/utils/localization/translations/ro-RO.json +1 -1
  85. package/dist/utils/localization/translations/sk-SK.json +1 -1
  86. package/dist/utils/localization/translations/sv.json +1 -1
  87. package/dist/utils/localization/translations/th.json +1 -1
  88. package/dist/utils/localization/translations/tr.json +1 -1
  89. package/dist/utils/localization/translations/zh-Hans.json +1 -1
  90. package/dist/utils/localization/translations/zh-TW.json +1 -1
  91. package/esm/absolute/absolute.js +1 -1
  92. package/esm/betaPill/betaPill.js +9 -9
  93. package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
  94. package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.js +3 -0
  95. package/esm/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
  96. package/esm/commonHelpers/hooks/useTrapFocus.js +67 -15
  97. package/esm/controlledPopup/controlledPopup.d.ts +1 -1
  98. package/esm/controlledPopup/controlledPopup.js +1 -1
  99. package/esm/dropdownRaw/dropdownSearchableTrigger.js +1 -1
  100. package/esm/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
  101. package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
  102. package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
  103. package/esm/filtersBar/filtersContainer/filtersContainer.js +7 -4
  104. package/esm/header/header.js +9 -2
  105. package/esm/header/headerHelpers.js +8 -5
  106. package/esm/index.d.ts +6 -1
  107. package/esm/index.js +5 -1
  108. package/esm/modal/modal.d.ts +48 -0
  109. package/esm/modal/modal.js +37 -7
  110. package/esm/nav/navHeader/navHeader.d.ts +1 -1
  111. package/esm/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
  112. package/esm/nav/navItem/navItem.d.ts +15 -2
  113. package/esm/nav/navItem/navItem.js +49 -7
  114. package/esm/nav/utils/navUtils.d.ts +2 -2
  115. package/esm/pillExpandable/pillExpandable.js +6 -3
  116. package/esm/rangeRaw/components/rangeField.d.ts +2 -1
  117. package/esm/rangeRaw/components/rangeField.js +4 -3
  118. package/esm/rangeRaw/rangeRaw.js +8 -5
  119. package/esm/rangeRaw/types.d.ts +1 -0
  120. package/esm/sidePanel/sidePanel.js +3 -2
  121. package/esm/table/selectable/interfaces.d.ts +13 -0
  122. package/esm/table/selectable/useSelectableRows.js +46 -12
  123. package/esm/table/selectable/utils/calculateSelectionState.js +35 -0
  124. package/esm/table/selectable/utils/isRowChecked.js +12 -1
  125. package/esm/table/table.d.ts +2 -0
  126. package/esm/table/table.js +2 -0
  127. package/esm/tabs/tabs.js +14 -5
  128. package/esm/testHelpers/renderWithFeatures.d.ts +4 -0
  129. package/esm/testHelpers/renderWithFeatures.js +6 -0
  130. package/esm/textInputRaw/textInputRaw.js +1 -1
  131. package/esm/textInputRaw/types.d.ts +1 -0
  132. package/esm/tooltip/components/inlineTrigger.d.ts +43 -0
  133. package/esm/tooltip/components/inlineTrigger.js +38 -0
  134. package/esm/tooltip/tooltip.d.ts +5 -2
  135. package/esm/tooltip/tooltip.js +15 -4
  136. package/esm/trendIndicator/trendIndicator.d.ts +47 -0
  137. package/esm/trendIndicator/trendIndicator.js +43 -0
  138. package/esm/trendIndicator/trendIndicatorUtils.d.ts +9 -0
  139. package/esm/trendIndicator/trendIndicatorUtils.js +14 -0
  140. package/esm/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
  141. package/esm/utils/experimentalFeatures/experimentalContext.js +25 -0
  142. package/esm/utils/experimentalFeatures/index.d.ts +4 -0
  143. package/esm/utils/experimentalFeatures/index.js +3 -0
  144. package/esm/utils/experimentalFeatures/registry.d.ts +13 -0
  145. package/esm/utils/experimentalFeatures/registry.js +8 -0
  146. package/esm/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
  147. package/esm/utils/experimentalFeatures/useExperimentalFeature.js +21 -0
  148. package/esm/utils/localization/translations/ar.json +1 -1
  149. package/esm/utils/localization/translations/bg-BG.json +1 -1
  150. package/esm/utils/localization/translations/cs.json +1 -1
  151. package/esm/utils/localization/translations/da-DK.json +1 -1
  152. package/esm/utils/localization/translations/de.json +1 -1
  153. package/esm/utils/localization/translations/el-GR.json +1 -1
  154. package/esm/utils/localization/translations/en.json +1 -1
  155. package/esm/utils/localization/translations/es.json +1 -1
  156. package/esm/utils/localization/translations/et-EE.json +1 -1
  157. package/esm/utils/localization/translations/fi-FI.json +1 -1
  158. package/esm/utils/localization/translations/fr-FR.json +1 -1
  159. package/esm/utils/localization/translations/fr.json +1 -1
  160. package/esm/utils/localization/translations/hu-HU.json +1 -1
  161. package/esm/utils/localization/translations/id.json +1 -1
  162. package/esm/utils/localization/translations/it.json +1 -1
  163. package/esm/utils/localization/translations/ja.json +1 -1
  164. package/esm/utils/localization/translations/ka-GE.json +1 -1
  165. package/esm/utils/localization/translations/ko-KR.json +1 -1
  166. package/esm/utils/localization/translations/lt-LT.json +1 -1
  167. package/esm/utils/localization/translations/lv-LV.json +1 -1
  168. package/esm/utils/localization/translations/ms.json +1 -1
  169. package/esm/utils/localization/translations/nb-NO.json +1 -1
  170. package/esm/utils/localization/translations/nl.json +1 -1
  171. package/esm/utils/localization/translations/pl.json +1 -1
  172. package/esm/utils/localization/translations/pt-BR.json +1 -1
  173. package/esm/utils/localization/translations/pt-PT.json +1 -1
  174. package/esm/utils/localization/translations/ro-RO.json +1 -1
  175. package/esm/utils/localization/translations/sk-SK.json +1 -1
  176. package/esm/utils/localization/translations/sv.json +1 -1
  177. package/esm/utils/localization/translations/th.json +1 -1
  178. package/esm/utils/localization/translations/tr.json +1 -1
  179. package/esm/utils/localization/translations/zh-Hans.json +1 -1
  180. package/esm/utils/localization/translations/zh-TW.json +1 -1
  181. package/package.json +36 -16
  182. package/dist/banner/utils/isBannerTarget.d.ts +0 -1
  183. package/dist/banner/utils/isBannerTarget.js +0 -11
  184. package/esm/banner/utils/isBannerTarget.d.ts +0 -1
  185. package/esm/banner/utils/isBannerTarget.js +0 -7
package/README.md CHANGED
@@ -47,6 +47,27 @@ Zenith library provides components defined in Zenith Design System. It includes
47
47
 
48
48
  ## Change log
49
49
 
50
+ ### 3.16.0
51
+
52
+ - Add `Tooltip.Inline` subcomponent for inline tooltip triggers
53
+ - Add popup button variant for `Nav` items
54
+ - Add integer-only mode prop to `Range` / `FilterBar.Range`
55
+ - Add experimental feature-flag infrastructure (`ZenithExperimentalProvider`, `useExperimentalFeature`)
56
+ - Export `getTrendIndicatorDirection` helper
57
+ - Add support for six additional languages
58
+ - Fix cross-page selection in selectable tables clearing when a row is unchecked after select-all
59
+ - Fix multiline `Banner` header spacing when there is no close button
60
+ - Fix scrolling in `Tabs` and `FiltersBar`
61
+ - Fix `Tabs` fading and side panel slide direction in RTL layouts
62
+ - Fix pills and chips scrolling the page to a blank area in RTL
63
+ - Fix icons ignoring their component's colour
64
+ - Fix mobile header overflow and `ReportsPopup` missing from the kebab menu in non-English languages
65
+ - Move `Tabs` :focus-visible outline outside the tab to distinguish it from the active state
66
+ - Fix `BetaPill` synthesising a Send feedback button when `feedbackAction` is undefined
67
+ - Migrate component alignment defaults to logical direction values
68
+ - Fix Storybook brand URL to resolve based on the environment
69
+ - Resolve 20 npm audit vulnerabilities in Zenith dependencies
70
+
50
71
  ### 3.15.0
51
72
 
52
73
  - Add infinite scroll support for desktop `DataGrid`
@@ -111,7 +111,7 @@ const Absolute = ({ isOpen, id, paddingX, paddingY, className, children, trigger
111
111
  returnFocusToTrigger();
112
112
  }
113
113
  }, [isOpen, returnFocusToTrigger]);
114
- const condition = (0, react_1.useCallback)(() => (useTrapFocusWithTrigger === "off" ? false : true), [useTrapFocusWithTrigger]);
114
+ const condition = (0, react_1.useCallback)((subscriptionTrigger) => useTrapFocusWithTrigger !== "off" && !!subscriptionTrigger, [useTrapFocusWithTrigger]);
115
115
  (0, useTrapFocus_1.useTrapFocus)(popupRef, useTrapFocusWithTrigger === "withTrigger" ? triggerRef : undefined, isOpen, condition);
116
116
  (0, useEscape_1.useEscape)(popupRef, handleEscape, isOpen);
117
117
  (0, useArrowKey_1.useArrowKey)(popupRef, handleEscape, isOpen);
@@ -162,15 +162,15 @@ const BetaPill = props => {
162
162
  const isMobile = (0, useMobile_1.useMobile)();
163
163
  const pillClasses = (0, react_1.useMemo)(() => (0, classNames_1.classNames)(["zen-caption", "zen-beta-pill", isMobile ? "zen-beta-pill--mobile" : "", className !== null && className !== void 0 ? className : ""]), [isMobile, className]);
164
164
  const contextActions = (0, betaPillContext_1.useBetaPillActions)();
165
- let resolvedMainAction = feedbackAction || (contextActions === null || contextActions === void 0 ? void 0 : contextActions.feedbackAction);
166
- let resolvedSecondaryAction = betaOptionAction || (contextActions === null || contextActions === void 0 ? void 0 : contextActions.betaOptionAction);
167
- if (resolvedMainAction || resolvedSecondaryAction) {
168
- resolvedMainAction = Object.assign(Object.assign({}, resolvedMainAction), {
169
- text: (resolvedMainAction === null || resolvedMainAction === void 0 ? void 0 : resolvedMainAction.text) || translate("Send feedback")
170
- });
171
- resolvedSecondaryAction = Object.assign(Object.assign({}, resolvedSecondaryAction), {
172
- text: (resolvedSecondaryAction === null || resolvedSecondaryAction === void 0 ? void 0 : resolvedSecondaryAction.text) || translate("Manage beta option")
173
- });
165
+ const rawMainAction = feedbackAction || (contextActions === null || contextActions === void 0 ? void 0 : contextActions.feedbackAction);
166
+ const rawSecondaryAction = betaOptionAction || (contextActions === null || contextActions === void 0 ? void 0 : contextActions.betaOptionAction);
167
+ if (rawMainAction || rawSecondaryAction) {
168
+ const resolvedMainAction = rawMainAction ? Object.assign(Object.assign({}, rawMainAction), {
169
+ text: rawMainAction.text || translate("Send feedback")
170
+ }) : undefined;
171
+ const resolvedSecondaryAction = rawSecondaryAction ? Object.assign(Object.assign({}, rawSecondaryAction), {
172
+ text: rawSecondaryAction.text || translate("Manage beta option")
173
+ }) : undefined;
174
174
  return (0, jsx_runtime_1.jsx)(pillExpandable_1.PillExpandable, {
175
175
  isBeta: true,
176
176
  icon: iconLab_1.IconLab,
@@ -5,6 +5,7 @@ interface IScrollOptions {
5
5
  top: number;
6
6
  left: number;
7
7
  }
8
+ export declare const getDirectionalScrollDelta: (delta: number, isRTL: boolean) => number;
8
9
  export declare const getScrollOptions: (action: string, element: HTMLElement | null, isRTL?: boolean) => IScrollOptions;
9
10
  export declare const extractButtonAction: (event: React.MouseEvent<HTMLElement>) => TArrowsNavValue | null;
10
11
  export declare const scrollElement: (element: HTMLElement, options: IScrollOptions) => void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.scrollElement = exports.extractButtonAction = exports.getScrollOptions = exports.clearTimerTimeout = exports.clearTimerInterval = void 0;
3
+ exports.scrollElement = exports.extractButtonAction = exports.getScrollOptions = exports.getDirectionalScrollDelta = exports.clearTimerTimeout = exports.clearTimerInterval = void 0;
4
4
  const zen_1 = require("../../utils/zen");
5
5
  const clearTimerInterval = (timerId) => {
6
6
  var _a;
@@ -38,6 +38,10 @@ const getLTRScrollLeft = (action, scrollLeft, step, element) => {
38
38
  }
39
39
  return scrollLeft;
40
40
  };
41
+ // In RTL the horizontal scroll axis is inverted: scrollLeft is 0 at the right edge and goes negative
42
+ // toward the left edge, so a delta meant to move the viewport toward the arrow must flip sign.
43
+ const getDirectionalScrollDelta = (delta, isRTL) => (isRTL ? -delta : delta);
44
+ exports.getDirectionalScrollDelta = getDirectionalScrollDelta;
41
45
  const getScrollOptions = (action, element, isRTL = false) => {
42
46
  const step = (element === null || element === void 0 ? void 0 : element.offsetWidth) ? element.offsetWidth : 100;
43
47
  const scrollTop = (element === null || element === void 0 ? void 0 : element.scrollTop) || 0;
@@ -1,4 +1,13 @@
1
1
  import { RefObject } from "react";
2
2
  type TElementToBool = (element: HTMLElement) => boolean;
3
+ /**
4
+ * Traps keyboard (Tab) focus within `containerRef` (and optionally its `triggerRef`).
5
+ *
6
+ * Nested-popup contract: for correct nested-popup behaviour, a focused element that triggers an
7
+ * open child popup must expose a `data-popup-id` attribute whose value is the `id` of the child
8
+ * popup element rendered in the DOM. When present, focus is deferred to that child popup's trap.
9
+ * This contract is fulfilled automatically by `ControlledPopup`; callers using `useTrapFocus`
10
+ * directly must set `data-popup-id` themselves or nested popups will silently lose focus handling.
11
+ */
3
12
  export declare const useTrapFocus: (containerRef: RefObject<HTMLElement | null>, triggerRef: RefObject<HTMLElement | null> | undefined, subscriptionTrigger: unknown, condition?: (t: unknown) => boolean, isElementVisible?: TElementToBool) => void;
4
13
  export {};
@@ -9,18 +9,38 @@ const isChildOf_1 = require("../../utils/isChildOf");
9
9
  const isElementHidden_1 = require("../../utils/isElementHidden");
10
10
  const zen_1 = require("../../utils/zen");
11
11
  const findFirstVisible = (elems, isElementVisible) => elems.find(elem => isElementVisible(elem));
12
+ /**
13
+ * Traps keyboard (Tab) focus within `containerRef` (and optionally its `triggerRef`).
14
+ *
15
+ * Nested-popup contract: for correct nested-popup behaviour, a focused element that triggers an
16
+ * open child popup must expose a `data-popup-id` attribute whose value is the `id` of the child
17
+ * popup element rendered in the DOM. When present, focus is deferred to that child popup's trap.
18
+ * This contract is fulfilled automatically by `ControlledPopup`; callers using `useTrapFocus`
19
+ * directly must set `data-popup-id` themselves or nested popups will silently lose focus handling.
20
+ */
12
21
  const useTrapFocus = (containerRef, triggerRef, subscriptionTrigger, condition, isElementVisible) => {
13
22
  /* eslint-disable complexity */
14
23
  const handleKeyDown = (0, react_1.useCallback)(e => {
15
- var _a, _b, _c, _d, _e, _f;
24
+ var _a, _b, _c, _d, _e, _f, _g;
16
25
  /* eslint-enable complexity */
17
26
  const getChildCondition = () => {
18
- var _a, _b;
27
+ var _a, _b, _c;
28
+ const targetEl = e.target;
29
+ // If the target is a trigger for an open child popup, defer to that popup's
30
+ // focus trap. Key on data-popup-id + DOM presence so this works even when
31
+ // preventAttributesAutoSet suppresses aria-expanded.
32
+ const dataPopupId = targetEl.getAttribute("data-popup-id");
33
+ if (dataPopupId &&
34
+ ((_a = zen_1.zen.document) === null || _a === void 0 ? void 0 : _a.getElementById(dataPopupId)) &&
35
+ (0, isChildOf_1.isChildOf)(targetEl, containerRef.current) &&
36
+ targetEl !== (triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current)) {
37
+ return true;
38
+ }
19
39
  return triggerRef
20
- ? !(0, isChildOf_1.isChildOf)(e.target, containerRef.current) &&
21
- e.target !== ((_a = zen_1.zen.document) === null || _a === void 0 ? void 0 : _a.body) &&
22
- !(0, isChildOf_1.isChildOf)(e.target, triggerRef.current)
23
- : !(0, isChildOf_1.isChildOf)(e.target, containerRef.current) && e.target !== ((_b = zen_1.zen.document) === null || _b === void 0 ? void 0 : _b.body);
40
+ ? !(0, isChildOf_1.isChildOf)(targetEl, containerRef.current) &&
41
+ targetEl !== ((_b = zen_1.zen.document) === null || _b === void 0 ? void 0 : _b.body) &&
42
+ !(0, isChildOf_1.isChildOf)(targetEl, triggerRef.current)
43
+ : !(0, isChildOf_1.isChildOf)(targetEl, containerRef.current) && targetEl !== ((_c = zen_1.zen.document) === null || _c === void 0 ? void 0 : _c.body);
24
44
  };
25
45
  if (!containerRef.current || getChildCondition()) {
26
46
  return;
@@ -30,9 +50,14 @@ const useTrapFocus = (containerRef, triggerRef, subscriptionTrigger, condition,
30
50
  : !(0, dialogHelpers_1.isElementInsideContainer)(target, containerEl.current) &&
31
51
  !(0, dialogHelpers_1.isElementInsideContainer)(target, triggerEl.current);
32
52
  const isTargetOutside = e.target ? checkIsElementOutside(e.target, containerRef, triggerRef) : undefined;
33
- const triggerSelectors = ((_a = triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(focusableSelector_1.FOCUSABLE_SELECTOR)) || [];
53
+ const triggerElement = (_a = triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current) !== null && _a !== void 0 ? _a : null;
54
+ const triggerSelf = (triggerElement === null || triggerElement === void 0 ? void 0 : triggerElement.matches(focusableSelector_1.FOCUSABLE_SELECTOR)) ? [triggerElement] : [];
55
+ const triggerChildren = Array.from((_b = triggerElement === null || triggerElement === void 0 ? void 0 : triggerElement.querySelectorAll(focusableSelector_1.FOCUSABLE_SELECTOR)) !== null && _b !== void 0 ? _b : []);
56
+ // querySelectorAll only returns descendants (never the element itself), so triggerSelf
57
+ // and triggerChildren are disjoint by definition and need no deduplication.
58
+ const triggerSelectors = [...triggerSelf, ...triggerChildren];
34
59
  const containerSelectors = containerRef.current.querySelectorAll(focusableSelector_1.FOCUSABLE_SELECTOR);
35
- const focusable = triggerRef ? [...Array.from(triggerSelectors), ...Array.from(containerSelectors)] : [...Array.from(containerSelectors)];
60
+ const focusable = triggerRef ? [...triggerSelectors, ...Array.from(containerSelectors)] : [...Array.from(containerSelectors)];
36
61
  if (!focusable.length) {
37
62
  return;
38
63
  }
@@ -49,32 +74,59 @@ const useTrapFocus = (containerRef, triggerRef, subscriptionTrigger, condition,
49
74
  return;
50
75
  }
51
76
  if (e.shiftKey) {
52
- if (((_b = zen_1.zen.document) === null || _b === void 0 ? void 0 : _b.activeElement) === first) {
77
+ if (((_c = zen_1.zen.document) === null || _c === void 0 ? void 0 : _c.activeElement) === first) {
53
78
  e.preventDefault();
54
79
  last === null || last === void 0 ? void 0 : last.focus();
55
80
  return;
56
81
  }
57
- if (firstContainerFocusable && ((_c = zen_1.zen.document) === null || _c === void 0 ? void 0 : _c.activeElement) === firstContainerFocusable) {
82
+ if (firstContainerFocusable && ((_d = zen_1.zen.document) === null || _d === void 0 ? void 0 : _d.activeElement) === firstContainerFocusable) {
58
83
  e.preventDefault();
59
84
  lastTriggerFocusable === null || lastTriggerFocusable === void 0 ? void 0 : lastTriggerFocusable.focus();
60
85
  }
61
86
  return;
62
87
  }
63
- if (((_d = zen_1.zen.document) === null || _d === void 0 ? void 0 : _d.activeElement) === last) {
88
+ if (((_e = zen_1.zen.document) === null || _e === void 0 ? void 0 : _e.activeElement) === last) {
64
89
  e.preventDefault();
65
90
  first === null || first === void 0 ? void 0 : first.focus();
66
91
  return;
67
92
  }
68
- if (lastTriggerFocusable && ((_e = zen_1.zen.document) === null || _e === void 0 ? void 0 : _e.activeElement) === lastTriggerFocusable) {
93
+ if (lastTriggerFocusable && ((_f = zen_1.zen.document) === null || _f === void 0 ? void 0 : _f.activeElement) === lastTriggerFocusable) {
69
94
  e.preventDefault();
70
95
  firstContainerFocusable === null || firstContainerFocusable === void 0 ? void 0 : firstContainerFocusable.focus();
71
96
  }
72
- else if (isLastChildOfFirst && first && (0, isChildOf_1.isChildOf)((_f = zen_1.zen.document) === null || _f === void 0 ? void 0 : _f.activeElement, first)) {
97
+ else if (isLastChildOfFirst && first && (0, isChildOf_1.isChildOf)((_g = zen_1.zen.document) === null || _g === void 0 ? void 0 : _g.activeElement, first)) {
73
98
  e.preventDefault();
74
99
  }
75
100
  return;
76
101
  }
77
- }, [containerRef, triggerRef, isElementVisible]);
102
+ },
103
+ // condition is consumed by useKeydown below, not read by handleKeyDown directly; its absence from deps is intentional.
104
+ [containerRef, triggerRef, isElementVisible]);
78
105
  (0, useKeydown_1.useKeydown)(handleKeyDown, subscriptionTrigger, condition);
106
+ // When withTrigger mode closes (condition goes false), restore focus to the trigger
107
+ // if focus landed on body (which happens when the popup's focused element is unmounted).
108
+ // This restoration is intentionally scoped to the "focused element unmounted → body" case;
109
+ // the general close case (e.g. click-outside, item selection) is handled separately by the
110
+ // returnFocusToTrigger effect in absolute.tsx. Both are idempotent, so overlap is harmless.
111
+ // Initialized to false because popups mount closed; the guard below reconciles the first
112
+ // active/inactive edge, so a popup that mounts already open still resolves correctly.
113
+ const isActiveRef = (0, react_1.useRef)(false);
114
+ (0, react_1.useEffect)(() => {
115
+ var _a, _b;
116
+ const isActive = condition ? condition(subscriptionTrigger) : !!subscriptionTrigger;
117
+ // Guard: short-circuit when the raw open/close state is unchanged to avoid spurious
118
+ // focus restoration if an unstable condition reference re-fires this effect.
119
+ if (!!subscriptionTrigger === isActiveRef.current) {
120
+ isActiveRef.current = isActive;
121
+ return;
122
+ }
123
+ if (!isActive && isActiveRef.current && (triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current)) {
124
+ const active = (_a = zen_1.zen.document) === null || _a === void 0 ? void 0 : _a.activeElement;
125
+ if (!active || active === ((_b = zen_1.zen.document) === null || _b === void 0 ? void 0 : _b.body)) {
126
+ triggerRef.current.focus();
127
+ }
128
+ }
129
+ isActiveRef.current = isActive;
130
+ }, [condition, subscriptionTrigger, triggerRef]);
79
131
  };
80
132
  exports.useTrapFocus = useTrapFocus;
@@ -10,7 +10,7 @@ import { IAlignment } from "../utils/positioningUtils/alignments";
10
10
  export type TAlignment = "top" | "top-left" | "top-right" | "left" | "left-top" | "left-bottom" | "right" | "right-top" | "right-bottom" | "bottom" | "bottom-left" | "bottom-right";
11
11
  export interface IControlledPopup extends IZenComponentProps {
12
12
  triggerRef: RefObject<HTMLElement | null>;
13
- /** Alignment of the popup relative to its trigger. Defaults to `"bottom-left"` (flips to `"bottom-right"` in RTL). */
13
+ /** Alignment of the popup relative to its trigger. Defaults to `"bottom-start"`. */
14
14
  alignment?: TAlignmentAbsolute;
15
15
  id?: string;
16
16
  className?: string;
@@ -11,7 +11,7 @@ const ControlledPopup = ({ isOpen, id, paddingX, paddingY, triggerRef, className
11
11
  const autoId = (0, react_1.useId)();
12
12
  const popupId = id || autoId;
13
13
  const isClientReady = (0, useClientReady_1.useClientReady)();
14
- const rtlAlignment = (0, useRTLAlignment_1.useRTLAlignment)(alignment !== null && alignment !== void 0 ? alignment : "bottom-left");
14
+ const rtlAlignment = (0, useRTLAlignment_1.useRTLAlignment)(alignment !== null && alignment !== void 0 ? alignment : "bottom-start");
15
15
  (0, react_1.useEffect)(() => {
16
16
  if (!triggerRef.current || !isClientReady) {
17
17
  return;
@@ -183,7 +183,7 @@ const DropdownSearchableTrigger = _a => {
183
183
  } = _a,
184
184
  otherProps = __rest(_a, ["value", "className", "onChange", "placeholder", "isOpenPopup", "inputRef", "handleClick", "width", "title", "id", "disabled", "count", "currentSelection", "isError", "fullWidth", "ref", "name", "onBlur", "popupId", "handleFocusOnPopup", "dataAnalyticsId"])
185
185
  // eslint-disable-next-line complexity
186
- ;
186
+ ;
187
187
  const {
188
188
  isFormSection
189
189
  } = (0, react_1.useContext)(formSectionProvider_1.FormSectionContext);
@@ -3,7 +3,7 @@ import { IRange, IRangeValues } from "../../../range/range";
3
3
  export interface IFiltersBarRangeState {
4
4
  value: IRangeValues;
5
5
  }
6
- interface IFiltersBarRangeProps extends Pick<IRange, "id" | "defaultValue" | "unit" | "labelMin" | "labelMax" | "disabled" | "label" | "triggerAriaLabel" | "min" | "max" | "fullWidthTriggerButton" | "fullBounded" | "className" | "allowEqualMinMax"> {
6
+ interface IFiltersBarRangeProps extends Pick<IRange, "id" | "defaultValue" | "unit" | "labelMin" | "labelMax" | "disabled" | "label" | "triggerAriaLabel" | "min" | "max" | "fullWidthTriggerButton" | "fullBounded" | "className" | "allowEqualMinMax" | "integer"> {
7
7
  }
8
8
  export interface IFiltersBarRange extends IFiltersBarItem<IFiltersBarRangeState, IFiltersBarRangeProps> {
9
9
  className?: string;
@@ -2,4 +2,4 @@ import "./filtersBarSidePanelRange.less";
2
2
  import { IRange } from "../../../../range/range";
3
3
  export interface IFiltersBarSidePanelRange extends Omit<IRange, "label" | "fullWidthTriggerButton" | "defaultValue"> {
4
4
  }
5
- export declare const FiltersBarSidePanelRange: ({ className, id, onChange, value, unit, min, max, fullBounded, allowEqualMinMax, labelMin, labelMax, disabled }: IFiltersBarSidePanelRange) => import("react/jsx-runtime").JSX.Element;
5
+ export declare const FiltersBarSidePanelRange: ({ className, id, onChange, value, unit, min, max, fullBounded, allowEqualMinMax, labelMin, labelMax, disabled, integer }: IFiltersBarSidePanelRange) => import("react/jsx-runtime").JSX.Element;
@@ -94,7 +94,8 @@ const FiltersBarSidePanelRange = ({
94
94
  allowEqualMinMax,
95
95
  labelMin,
96
96
  labelMax,
97
- disabled
97
+ disabled,
98
+ integer
98
99
  }) => {
99
100
  const generatedId = (0, react_1.useId)();
100
101
  const intId = id || generatedId;
@@ -153,8 +154,9 @@ const FiltersBarSidePanelRange = ({
153
154
  min: min,
154
155
  isMinField: true,
155
156
  direction: directionState,
156
- disabled: disabled
157
- }), [rangeFieldClasses, labelMin, translate, unit, currentValue.min, errorType, min, max, handleChange, directionState, disabled]);
157
+ disabled: disabled,
158
+ integer: integer
159
+ }), [rangeFieldClasses, labelMin, translate, unit, currentValue.min, errorType, min, max, handleChange, directionState, disabled, integer]);
158
160
  const memoizedMaxRangeField = (0, react_1.useMemo)(() => (0, jsx_runtime_1.jsx)(rangeField_1.RangeField, {
159
161
  className: (0, classNames_1.classNames)([rangeFieldClasses, "zen-filters-bar-side-panel-range__max-field"]),
160
162
  label: labelMax || translate("Max"),
@@ -168,8 +170,9 @@ const FiltersBarSidePanelRange = ({
168
170
  min: min,
169
171
  isMinField: false,
170
172
  direction: directionState,
171
- disabled: disabled
172
- }), [rangeFieldClasses, labelMax, translate, unit, currentValue.max, errorType, min, max, handleChange, directionState, disabled]);
173
+ disabled: disabled,
174
+ integer: integer
175
+ }), [rangeFieldClasses, labelMax, translate, unit, currentValue.max, errorType, min, max, handleChange, directionState, disabled, integer]);
173
176
  return (0, jsx_runtime_1.jsxs)("div", {
174
177
  ref: setFieldsContainerRef,
175
178
  id: intId,
@@ -17,6 +17,7 @@ const useDirectionalIcon_1 = require("../../utils/localization/useDirectionalIco
17
17
  const useLanguage_1 = require("../../utils/localization/useLanguage");
18
18
  const useDriveClassName_1 = require("../../utils/theme/useDriveClassName");
19
19
  const useIsRTL_1 = require("../../utils/localization/useIsRTL");
20
+ const arrowNavigationHelper_1 = require("../../commonHelpers/arrowNavigation/arrowNavigationHelper");
20
21
  const zen_1 = require("../../utils/zen");
21
22
  injectString("cs", "Scroll to start", "P\u0159ej\xEDt na za\u010D\xE1tek");
22
23
  injectString("da-DK", "Scroll to start", "Rul til start");
@@ -92,13 +93,15 @@ const getScrollButtonType = (element, isRTL) => {
92
93
  return "";
93
94
  }
94
95
  if (isRTL) {
96
+ // scrollLeft is 0 at the start (right edge) and negative toward the end (left edge),
97
+ // so the button to offer is the mirror of the LTR case below.
95
98
  if (scrollLeft < -1 && scrollLeft > clientWidth - scrollWidth + 1) {
96
99
  return "both";
97
100
  }
98
101
  if (scrollLeft >= -1) {
99
- return "start";
102
+ return "end";
100
103
  }
101
- return "end";
104
+ return "start";
102
105
  }
103
106
  if (scrollLeft > 0 && scrollWidth > clientWidth + scrollLeft + 1) {
104
107
  return "both";
@@ -234,7 +237,7 @@ const FiltersContainer = ({
234
237
  className: (0, classNames_1.classNames)(["zen-filters-toolbar__scroller", driveClassNameScroller || "", "zen-filters-toolbar__start", showLeft ? `${scrollerClass}--visible` : "", isHiddenDisplay.left ? `${scrollerClass}--hidden` : ""]),
235
238
  icon: ScrollStartIcon,
236
239
  iconPosition: textIconButton_1.ButtonIconPosition.Start,
237
- onClick: handleScrollButtonClick(-scrollableStep)
240
+ onClick: handleScrollButtonClick((0, arrowNavigationHelper_1.getDirectionalScrollDelta)(-scrollableStep, isRTL))
238
241
  }), (0, jsx_runtime_1.jsx)("div", {
239
242
  ref: scrollableRef,
240
243
  className: (0, classNames_1.classNames)(["zen-filters-toolbar__scrollable", !isHiddenDisplay.left ? "has-left-scroller" : "", !isHiddenDisplay.right ? "has-right-scroller" : ""]),
@@ -248,7 +251,7 @@ const FiltersContainer = ({
248
251
  title: translate("Scroll to end"),
249
252
  className: (0, classNames_1.classNames)([scrollerClass, driveClassNameScroller || "", "zen-filters-toolbar__end", showRight ? `${scrollerClass}--visible` : "", isHiddenDisplay.right ? `${scrollerClass}--hidden` : ""]),
250
253
  icon: ScrollEndIcon,
251
- onClick: handleScrollButtonClick(scrollableStep),
254
+ onClick: handleScrollButtonClick((0, arrowNavigationHelper_1.getDirectionalScrollDelta)(scrollableStep, isRTL)),
252
255
  iconPosition: textIconButton_1.ButtonIconPosition.End
253
256
  })]
254
257
  }) : null;
@@ -187,11 +187,13 @@ const Header = ({ children, className, onFiltersBarOpen
187
187
  const headerRefWidth = ((_a = headerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
188
188
  const leftElementsRefWidth = ((_b = leftElementsRef.current) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect().width) || 0;
189
189
  const rightelementsRefWidth = ((_c = rightElementsRef.current) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect().width) || 0;
190
- const paddings = 48;
190
+ // Mobile padding: 8px * 2 = 16px (zen-main-header--mobile .zen-main-header__section--padding)
191
+ // Desktop padding: 20px * 2 = 40px + 8px gaps between sections ≈ 48px
192
+ const paddings = isMobile ? 16 : 48;
191
193
  const gaps = 8 + (rightelementsRefWidth ? 8 : 0);
192
194
  const calculatedMiddleWidth = headerRefWidth - gaps - paddings - rightelementsRefWidth - leftElementsRefWidth;
193
195
  setMiddleWidth(calculatedMiddleWidth);
194
- }, []);
196
+ }, [isMobile]);
195
197
  const resizeCallBack = (0, useThrottle_1.useThrottle)(() => {
196
198
  calculateMiddleWidth();
197
199
  if (!isMobile) {
@@ -248,6 +250,11 @@ const Header = ({ children, className, onFiltersBarOpen
248
250
  const prefixesForTitleSize = ["large", "medium", "small"];
249
251
  return prefixesForTitleSize[count - 1];
250
252
  }, [collapsedElements.length, mobileFilterCondition, mobileSearchCondition, rightElements.length, visibleElements.length]);
253
+ // On mobile, title shrink when items overflow is handled via CSS (flex-shrink: 1 and
254
+ // min-width: 0 are always set on .zen-header__section--left for mobile in header.less).
255
+ // Applying this via React state caused a measurement feedback loop: adding shrink-title
256
+ // increased middleWidth → items fit → condition became false → title expanded → items
257
+ // overflowed → condition became true again → oscillation.
251
258
  const titleShrinkCondition = (visibleElements.length === 0 && rightElements.length) || (mobileSearchCondition && mobileFilterCondition);
252
259
  const fullClassName = (0, classNames_1.classNames)([
253
260
  "zen-main-header zen-main-header--background-color",
@@ -87,11 +87,14 @@ const getItemsForRender = (items, isMobile) => {
87
87
  exports.getItemsForRender = getItemsForRender;
88
88
  const getItemsForMeasure = (items, isMobile) => (0, exports.getItemsForRender)(items, isMobile).map((el, ind) => {
89
89
  const elProps = el.props;
90
- return (0, react_1.cloneElement)(el, {
91
- key: `measure-key-${ind}-${items.length}`,
92
- id: undefined,
93
- "data-zen-measure-id": elProps.id
94
- });
90
+ // Only HeaderCustomMenuWrapper components receive their original id in the
91
+ // measurement clone. These components use id purely as a registration key inside
92
+ // useCustomMenu and do NOT forward it to a DOM attribute, so no duplicate HTML
93
+ // element id is created in the hidden measurement container (MYG-141810).
94
+ // All other items (HeaderButton, HeaderMenu, …) keep id: undefined, preserving
95
+ // the MYG-133482 protection against duplicate DOM ids.
96
+ const isCustomMenuWrapper = el.type.displayName === exports.HeaderCustomMenuWrapper;
97
+ return (0, react_1.cloneElement)(el, Object.assign(Object.assign({ key: `measure-key-${ind}-${items.length}` }, (isCustomMenuWrapper ? {} : { id: undefined })), { "data-zen-measure-id": elProps.id }));
95
98
  });
96
99
  exports.getItemsForMeasure = getItemsForMeasure;
97
100
  const filterItems = (items, isMobile) => {