@geotab/zenith 3.17.0-beta.1 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +26 -29
  2. package/dist/button/button.d.ts +1 -2
  3. package/dist/button/button.js +5 -22
  4. package/dist/button/buttonDisabledProps.d.ts +35 -0
  5. package/dist/button/buttonDisabledProps.js +33 -0
  6. package/dist/dataGrid/withSelectableRows/components/bulkActions/bulkActions.d.ts +1 -1
  7. package/dist/dropdownRaw/dropdownHelper.d.ts +20 -3
  8. package/dist/dropdownRaw/dropdownHelper.js +29 -6
  9. package/dist/dropdownRaw/dropdownRaw.js +22 -6
  10. package/dist/header/components/collapsedItemsControl/collapsedItemsControl.js +1 -1
  11. package/dist/header/headerButton.d.ts +15 -2
  12. package/dist/header/headerButton.js +11 -6
  13. package/dist/index.css +4 -8
  14. package/dist/menu/components/menuButton.js +40 -58
  15. package/dist/pageHeader/pageHeaderButton.d.ts +15 -1
  16. package/dist/pageHeader/pageHeaderButton.js +8 -6
  17. package/dist/pillExpandable/pillExpandable.js +3 -1
  18. package/dist/pillExpandable/pillExpandableSimple.d.ts +2 -1
  19. package/dist/pillExpandable/pillExpandableSimple.js +2 -2
  20. package/dist/table/selectable/interfaces.d.ts +6 -0
  21. package/dist/table/selectable/useDragSelection.d.ts +1 -1
  22. package/dist/table/selectable/useDragSelection.js +4 -4
  23. package/dist/table/selectable/useSelectableRows.d.ts +2 -0
  24. package/dist/table/selectable/useSelectableRows.js +88 -41
  25. package/dist/table/selectable/utils/calculateSelectionState.js +3 -2
  26. package/dist/table/selectable/utils/getCurrentPageSelection.js +9 -4
  27. package/dist/table/selectable/utils/groupExclusions.d.ts +7 -0
  28. package/dist/table/selectable/utils/groupExclusions.js +61 -0
  29. package/dist/table/selectable/utils/isRowChecked.js +1 -1
  30. package/dist/table/table.js +6 -5
  31. package/dist/tooltip/tooltip.d.ts +5 -3
  32. package/dist/tooltip/tooltip.js +11 -9
  33. package/esm/button/button.d.ts +1 -2
  34. package/esm/button/button.js +5 -22
  35. package/esm/button/buttonDisabledProps.d.ts +35 -0
  36. package/esm/button/buttonDisabledProps.js +29 -0
  37. package/esm/dataGrid/withSelectableRows/components/bulkActions/bulkActions.d.ts +1 -1
  38. package/esm/dropdownRaw/dropdownHelper.d.ts +20 -3
  39. package/esm/dropdownRaw/dropdownHelper.js +27 -5
  40. package/esm/dropdownRaw/dropdownRaw.js +23 -7
  41. package/esm/header/components/collapsedItemsControl/collapsedItemsControl.js +1 -1
  42. package/esm/header/headerButton.d.ts +15 -2
  43. package/esm/header/headerButton.js +11 -6
  44. package/esm/menu/components/menuButton.js +40 -58
  45. package/esm/pageHeader/pageHeaderButton.d.ts +15 -1
  46. package/esm/pageHeader/pageHeaderButton.js +8 -6
  47. package/esm/pillExpandable/pillExpandable.js +3 -1
  48. package/esm/pillExpandable/pillExpandableSimple.d.ts +2 -1
  49. package/esm/pillExpandable/pillExpandableSimple.js +2 -2
  50. package/esm/table/selectable/interfaces.d.ts +6 -0
  51. package/esm/table/selectable/useDragSelection.d.ts +1 -1
  52. package/esm/table/selectable/useDragSelection.js +4 -4
  53. package/esm/table/selectable/useSelectableRows.d.ts +2 -0
  54. package/esm/table/selectable/useSelectableRows.js +90 -43
  55. package/esm/table/selectable/utils/calculateSelectionState.js +3 -2
  56. package/esm/table/selectable/utils/getCurrentPageSelection.js +9 -4
  57. package/esm/table/selectable/utils/groupExclusions.d.ts +7 -0
  58. package/esm/table/selectable/utils/groupExclusions.js +55 -0
  59. package/esm/table/selectable/utils/isRowChecked.js +1 -1
  60. package/esm/table/table.js +6 -5
  61. package/esm/tooltip/tooltip.d.ts +5 -3
  62. package/esm/tooltip/tooltip.js +11 -9
  63. package/package.json +1 -1
  64. package/README.md.baseline +0 -1
@@ -148,6 +148,7 @@ const useTableChildren_1 = require("./children/useTableChildren");
148
148
  const deviceType_1 = require("../commonHelpers/hooks/deviceType");
149
149
  const useMobile_1 = require("../commonHelpers/hooks/useMobile");
150
150
  const getCurrentPageSelection_1 = require("./selectable/utils/getCurrentPageSelection");
151
+ const groupExclusions_1 = require("./selectable/utils/groupExclusions");
151
152
  const tableFullscreen_1 = require("./children/tableFullscreen");
152
153
  const tableBulkActions_1 = require("./children/tableBulkActions");
153
154
  const tablePagination_1 = require("./children/tablePagination");
@@ -235,7 +236,7 @@ const TableInner = ({
235
236
  active,
236
237
  onClick
237
238
  }) => {
238
- var _a, _b, _c, _d, _e;
239
+ var _a, _b, _c, _d, _e, _f;
239
240
  const isMobile = (0, useMobile_1.useMobile)();
240
241
  const {
241
242
  translate
@@ -329,15 +330,15 @@ const TableInner = ({
329
330
  var _a, _b;
330
331
  return (0, useSelectableRows_1.isAllSelection)((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all, (_b = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _b === void 0 ? void 0 : _b.selected.length, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions);
331
332
  }, [(_c = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _c === void 0 ? void 0 : _c.all, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions, (_d = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _d === void 0 ? void 0 : _d.selected.length]);
333
+ const resolvedSelection = (0, react_1.useMemo)(() => (0, groupExclusions_1.resolveGroupExclusions)(entities, selectable), [entities, selectable]);
332
334
  const selectionQty = (0, react_1.useMemo)(() => {
333
- var _a;
334
335
  if (!selectable) {
335
336
  return 0;
336
337
  }
337
- const ids = new Set((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.selected);
338
+ const ids = new Set(resolvedSelection === null || resolvedSelection === void 0 ? void 0 : resolvedSelection.selected);
338
339
  const base = entities.length > 0 ? entities : loadingEntities;
339
340
  return allSelected ? [...(0, getCurrentPageSelection_1.getCurrentPageSelection)(base, selectable)].filter(id => !selectable.allowExclusions || !ids.has(id)).length : ids.size;
340
- }, [selectable, entities, loadingEntities, allSelected]);
341
+ }, [selectable, resolvedSelection, entities, loadingEntities, allSelected]);
341
342
  const clearSelection = (0, react_1.useCallback)(() => {
342
343
  setSelectMode(false);
343
344
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect({
@@ -363,7 +364,7 @@ const TableInner = ({
363
364
  detailPanel,
364
365
  footer,
365
366
  other
366
- } = (0, useTableChildren_1.useTableChildren)(children, isMobile, selectionQty, allSelected, clearSelection, activeId, gridRef, onCloseDetailPanel, isInfinite, entities.length, (selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && ((_e = selectable.selection) === null || _e === void 0 ? void 0 : _e.all) ? new Set(selectable.selection.selected).size : 0, (selectable === null || selectable === void 0 ? void 0 : selectable.strategy) !== "nested" && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible), !!feedEndCallback || !!isLoading);
367
+ } = (0, useTableChildren_1.useTableChildren)(children, isMobile, selectionQty, allSelected, clearSelection, activeId, gridRef, onCloseDetailPanel, isInfinite, entities.length, (selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && (resolvedSelection === null || resolvedSelection === void 0 ? void 0 : resolvedSelection.all) ? new Set(resolvedSelection.selected).size + ((_f = (_e = resolvedSelection.excludedGroupIds) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) : 0, (selectable === null || selectable === void 0 ? void 0 : selectable.strategy) !== "nested" && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible), !!feedEndCallback || !!isLoading);
367
368
  const hasDetailPanel = !!detailPanel;
368
369
  const shouldEnableRowClick = hasDetailPanel || !!onClick;
369
370
  const activeIdForRow = shouldEnableRowClick ? activeId : undefined;
@@ -5,11 +5,13 @@ import { TAlignment } from "../absolute/absolute";
5
5
  import { IInlineTrigger } from "./components/inlineTrigger";
6
6
  export type TooltipSize = "large" | "small" | "auto";
7
7
  export type TMobileView = "default" | "mobileSheet";
8
+ interface ITriggerDisabledProps {
9
+ disabled?: boolean;
10
+ "aria-disabled"?: boolean | "true" | "false";
11
+ }
8
12
  interface ITooltip extends IZenComponentProps {
9
- trigger?: ReactElement<PropsWithChildren & {
13
+ trigger?: ReactElement<PropsWithChildren & ITriggerDisabledProps & {
10
14
  onClick?: (e: React.MouseEvent) => void;
11
- disabled?: boolean;
12
- focusableWhenDisabled?: boolean;
13
15
  }>;
14
16
  alignment?: TAlignment;
15
17
  triggerClassName?: string;
@@ -159,12 +159,14 @@ const getInitialDeviceType = () => {
159
159
  }
160
160
  return deviceType_1.DeviceType.Desktop;
161
161
  };
162
+ // A disabled trigger (native `disabled`, or `aria-disabled` when it must stay focusable) never runs its own
163
+ // `onClick`, so the tooltip has to react to the click capture phase instead.
164
+ const isTriggerDisabled = props => Boolean(props === null || props === void 0 ? void 0 : props.disabled) || (props === null || props === void 0 ? void 0 : props["aria-disabled"]) === true || (props === null || props === void 0 ? void 0 : props["aria-disabled"]) === "true";
162
165
  const EventWrapper = ({
163
166
  children,
164
167
  deviceType,
165
168
  onKeyDownHandler,
166
169
  handleUniversalClick,
167
- isTriggerDisabledFocusable,
168
170
  setTooltipVisible,
169
171
  timerRef,
170
172
  timerMouseLeaveRef,
@@ -173,8 +175,9 @@ const EventWrapper = ({
173
175
  const isCurrentDeviceDesktop = deviceType === deviceType_1.DeviceType.Desktop;
174
176
  const triggerProps = children.props;
175
177
  const originalOnClick = triggerProps.onClick;
176
- // A disabled-focusable trigger's own `onClick` is a no-op guard, so `composedOnClick` never runs on
177
- // mobile — see the `onClickCapture` handling in `mobileEvents` below.
178
+ // A disabled trigger doesn't run its own `onClick`, so on mobile the tap is handled in the capture phase
179
+ // instead — see `mobileEvents` below.
180
+ const isDisabledTrigger = isTriggerDisabled(triggerProps);
178
181
  const composedOnClick = (0, react_1.useCallback)(e => {
179
182
  // Call original onClick first if it exists
180
183
  if (originalOnClick) {
@@ -213,7 +216,7 @@ const EventWrapper = ({
213
216
  }, [handleUniversalClick]);
214
217
  const mobileEvents = Object.assign({
215
218
  onKeyDown: mobileOnKeyDown
216
- }, isTriggerDisabledFocusable ? {
219
+ }, isDisabledTrigger ? {
217
220
  onClickCapture: handleUniversalClick
218
221
  } : {
219
222
  onClick: composedOnClick
@@ -248,7 +251,7 @@ const Tooltip = ({
248
251
  const [deviceType, setDeviceType] = (0, react_1.useState)(getInitialDeviceType);
249
252
  const isMobile = deviceType === deviceType_1.DeviceType.Mobile;
250
253
  const triggerProps = trigger === null || trigger === void 0 ? void 0 : trigger.props;
251
- const isTriggerDisabledFocusable = Boolean(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.disabled) && (triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.focusableWhenDisabled) !== false;
254
+ const isDisabledTrigger = isTriggerDisabled(triggerProps);
252
255
  const tooltipId = (0, react_1.useId)();
253
256
  const {
254
257
  translate
@@ -279,7 +282,7 @@ const Tooltip = ({
279
282
  }
280
283
  };
281
284
  const handleUniversalClick = (0, react_1.useCallback)(e => {
282
- const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !isTriggerDisabledFocusable;
285
+ const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !isDisabledTrigger;
283
286
  if (deviceType === deviceType_1.DeviceType.Desktop) {
284
287
  setTooltipVisible(currentVisible => {
285
288
  if (currentVisible) {
@@ -288,14 +291,14 @@ const Tooltip = ({
288
291
  return currentVisible;
289
292
  });
290
293
  } else {
291
- if (!isTriggerDisabledFocusable) {
294
+ if (!isDisabledTrigger) {
292
295
  e === null || e === void 0 ? void 0 : e.stopPropagation();
293
296
  }
294
297
  if (!hasExternalClickHandler) {
295
298
  setTooltipVisible(isVisible => !isVisible);
296
299
  }
297
300
  }
298
- }, [deviceType, isTriggerDisabledFocusable, triggerProps]);
301
+ }, [deviceType, isDisabledTrigger, triggerProps]);
299
302
  // Apply `triggerAriaLabel` to a `Tooltip.InlineTrigger` trigger (it has no `ariaLabel` prop
300
303
  // of its own); the default info-icon trigger below handles `triggerAriaLabel` separately.
301
304
  // An `aria-label` set directly on the `Tooltip.InlineTrigger` element takes priority over
@@ -322,7 +325,6 @@ const Tooltip = ({
322
325
  isTooltipVisible: isTooltipVisible,
323
326
  onKeyDownHandler: onKeyDownHandler,
324
327
  handleUniversalClick: handleUniversalClick,
325
- isTriggerDisabledFocusable: isTriggerDisabledFocusable,
326
328
  setTooltipVisible: setTooltipVisible,
327
329
  timerRef: timerRef,
328
330
  timerMouseLeaveRef: timerMouseLeaveRef,
@@ -9,7 +9,6 @@ export interface IButton extends IZenComponentProps, IZenGridItem {
9
9
  title?: string;
10
10
  type?: TButton;
11
11
  disabled?: boolean;
12
- focusableWhenDisabled?: boolean;
13
12
  onClick?: React.MouseEventHandler;
14
13
  onBlur?: React.FocusEventHandler;
15
14
  onMouseOver?: (e: React.MouseEvent<HTMLElement>) => void;
@@ -28,6 +27,6 @@ export interface IButton extends IZenComponentProps, IZenGridItem {
28
27
  dataAnalyticsId?: string;
29
28
  }
30
29
  export declare const Button: {
31
- ({ className, type, disabled, focusableWhenDisabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId, ...other }: IButton): import("react/jsx-runtime").JSX.Element;
30
+ ({ className, type, disabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId, ...other }: IButton): import("react/jsx-runtime").JSX.Element;
32
31
  displayName: string;
33
32
  };
@@ -15,35 +15,18 @@ import { classNames } from "../commonHelpers/classNames/classNames";
15
15
  import { useDriveClassName } from "../utils/theme/useDriveClassName";
16
16
  import { filterGridProps } from "../gridLayout/utils/filterGridProps";
17
17
  import { useMobileClassName } from "../utils/theme/useMobileClassName";
18
- const blockActivation = (event) => {
19
- event.preventDefault();
20
- event.stopPropagation();
21
- };
22
- // Guards on `disabled` alone, not just `focusableWhenDisabled`, so clicks are blocked in JS regardless of
23
- // CSS. `onKeyDown`/`onKeyUp` stay unguarded — Tooltip needs them.
24
- const getDisabledGuardedHandlers = (disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp) => ({
25
- onClick: disabled ? blockActivation : onClick,
26
- onMouseDown: isFocusableWhenDisabled ? blockActivation : onMouseDown,
27
- onMouseUp: isFocusableWhenDisabled ? blockActivation : onMouseUp
28
- });
29
- const getButtonClassName = (type, disabled, isFocusableWhenDisabled, mobileOrDriveClasses, className) => {
18
+ export const Button = (_a) => {
19
+ var { className, type, disabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId } = _a, other = __rest(_a, ["className", "type", "disabled", "id", "title", "onClick", "onMouseOver", "onMouseOut", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "children", "link", "target", "htmlType", "rel", "ref", "name", "onBlur", "role", "ariaLabel", "dataAnalyticsId"]);
30
20
  const modifierClassName = type && type !== ButtonType.Secondary ? `zen-button--${type}` : "";
31
21
  const disabledClassName = disabled ? "zen-button--disabled" : "";
32
- const disabledFocusableClassName = isFocusableWhenDisabled ? "zen-button--disabled-focusable" : "";
33
- return classNames(["zen-button", mobileOrDriveClasses, modifierClassName, disabledClassName, disabledFocusableClassName, className !== null && className !== void 0 ? className : ""]);
34
- };
35
- export const Button = (_a) => {
36
- var { className, type, disabled, focusableWhenDisabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId } = _a, other = __rest(_a, ["className", "type", "disabled", "focusableWhenDisabled", "id", "title", "onClick", "onMouseOver", "onMouseOut", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "children", "link", "target", "htmlType", "rel", "ref", "name", "onBlur", "role", "ariaLabel", "dataAnalyticsId"]);
37
- const isFocusableWhenDisabled = Boolean(disabled) && Boolean(focusableWhenDisabled);
38
22
  const driveClasses = useDriveClassName("zen-button");
39
23
  const ariaLabelValue = ariaLabel || title;
40
24
  const mobileClasses = useMobileClassName("zen-button");
41
25
  const commonArgs = Object.assign(Object.assign(Object.assign({ id,
42
- title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className: getButtonClassName(type, disabled, isFocusableWhenDisabled, mobileClasses || driveClasses || "", className) });
43
- const { onClick: onClickWithDisabledGuard, onMouseDown: onMouseDownWithDisabledGuard, onMouseUp: onMouseUpWithDisabledGuard } = getDisabledGuardedHandlers(disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp);
26
+ title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className: classNames(["zen-button", mobileClasses || driveClasses || "", modifierClassName, disabledClassName, className !== null && className !== void 0 ? className : ""]) });
44
27
  if (link) {
45
- return (_jsx("a", Object.assign({ role: "link", rel: rel, target: target, href: disabled ? undefined : link, tabIndex: isFocusableWhenDisabled ? 0 : undefined, "aria-disabled": disabled ? true : undefined, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onClick: onClickWithDisabledGuard }, commonArgs, filterGridProps(other), { children: children })));
28
+ return (_jsx("a", Object.assign({ role: "link", rel: rel, target: target, href: disabled ? undefined : link, "aria-disabled": disabled, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onClick: onClick }, commonArgs, filterGridProps(other), { children: children })));
46
29
  }
47
- return (_jsx("button", Object.assign({ name: name, onBlur: onBlur, onClick: onClickWithDisabledGuard, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onMouseDown: onMouseDownWithDisabledGuard, onMouseUp: onMouseUpWithDisabledGuard, onKeyDown: onKeyDown, onKeyUp: onKeyUp, ref: ref, type: htmlType, disabled: isFocusableWhenDisabled ? undefined : disabled, "aria-disabled": isFocusableWhenDisabled ? true : undefined, role: role }, commonArgs, filterGridProps(other), { children: children })));
30
+ return (_jsx("button", Object.assign({ name: name, onBlur: onBlur, onClick: onClick, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onKeyDown: onKeyDown, onKeyUp: onKeyUp, ref: ref, type: htmlType, disabled: disabled, role: role }, commonArgs, filterGridProps(other), { children: children })));
48
31
  };
49
32
  Button.displayName = "Button";
@@ -0,0 +1,35 @@
1
+ import * as React from "react";
2
+ export declare const BUTTON_DISABLED_CLASS = "zen-button--disabled";
3
+ export declare const BUTTON_DISABLED_FOCUSABLE_CLASS = "zen-button--disabled-focusable";
4
+ export interface IButtonDisabledOptions {
5
+ disabled?: boolean;
6
+ /**
7
+ * Keeps a disabled button in the tab order and hoverable, using `aria-disabled` instead of the native
8
+ * `disabled` attribute. Has no effect while the button is enabled.
9
+ */
10
+ focusable?: boolean;
11
+ /** The `link` prop of the button, a link without `href` is not tabbable, so it needs an explicit `tabIndex`. */
12
+ link?: string;
13
+ }
14
+ export interface IButtonDisabledProps {
15
+ disabled?: boolean;
16
+ "aria-disabled"?: boolean;
17
+ tabIndex?: number;
18
+ className?: string;
19
+ onClick?: (event: React.SyntheticEvent) => void;
20
+ onMouseDown?: (event: React.SyntheticEvent) => void;
21
+ onMouseUp?: (event: React.SyntheticEvent) => void;
22
+ }
23
+ /**
24
+ * Props that make `Button` (or `TextIconButton`) disabled. `Button` itself only applies them.
25
+ *
26
+ * - enabled: `{ disabled }`
27
+ * - disabled: native `disabled`, the button leaves the tab order
28
+ * - disabled and `focusable`: `aria-disabled`, activation is blocked, the button stays focusable and hoverable
29
+ *
30
+ * `onKeyDown`/`onKeyUp` are left untouched: Tooltip needs them. Enter/Space on a focused button without the native
31
+ * `disabled` attribute fire a click, which is blocked here.
32
+ *
33
+ * The caller merges the returned `className` with its own and wraps the button in whatever explains the state.
34
+ */
35
+ export declare const getButtonDisabledProps: ({ disabled, focusable, link }: IButtonDisabledOptions) => IButtonDisabledProps;
@@ -0,0 +1,29 @@
1
+ import { classNames } from "../commonHelpers/classNames/classNames";
2
+ // `button.less` styles these classes: `--disabled-focusable` keeps pointer events so hover/tooltip still work.
3
+ export const BUTTON_DISABLED_CLASS = "zen-button--disabled";
4
+ export const BUTTON_DISABLED_FOCUSABLE_CLASS = "zen-button--disabled-focusable";
5
+ const blockActivation = (event) => {
6
+ event.preventDefault();
7
+ event.stopPropagation();
8
+ };
9
+ /**
10
+ * Props that make `Button` (or `TextIconButton`) disabled. `Button` itself only applies them.
11
+ *
12
+ * - enabled: `{ disabled }`
13
+ * - disabled: native `disabled`, the button leaves the tab order
14
+ * - disabled and `focusable`: `aria-disabled`, activation is blocked, the button stays focusable and hoverable
15
+ *
16
+ * `onKeyDown`/`onKeyUp` are left untouched: Tooltip needs them. Enter/Space on a focused button without the native
17
+ * `disabled` attribute fire a click, which is blocked here.
18
+ *
19
+ * The caller merges the returned `className` with its own and wraps the button in whatever explains the state.
20
+ */
21
+ export const getButtonDisabledProps = ({ disabled, focusable, link }) => {
22
+ if (!disabled || !focusable) {
23
+ return { disabled };
24
+ }
25
+ const hasLink = Boolean(link);
26
+ return Object.assign(Object.assign({}, (hasLink ? { disabled: true, tabIndex: 0 } : { "aria-disabled": true })), {
27
+ // Button adds `zen-button--disabled` to a disabled link itself.
28
+ className: classNames([hasLink ? "" : BUTTON_DISABLED_CLASS, BUTTON_DISABLED_FOCUSABLE_CLASS]), onClick: blockActivation, onMouseDown: blockActivation, onMouseUp: blockActivation });
29
+ };
@@ -12,7 +12,7 @@ export interface IBulkActionsProps extends IZenComponentProps {
12
12
  * even when `selected` is 0 (i.e., cross-page "select all" is active).
13
13
  */
14
14
  allSelected?: boolean;
15
- /** Show a selection label without a count when the selected total is unknown. */
15
+ /** The selection spans unloaded rows and its total is not available. */
16
16
  selectionCountUnknown?: boolean;
17
17
  onClearSelection: () => void;
18
18
  onSelectAll?: () => void;
@@ -12,12 +12,28 @@ export declare const isAllItemsHasOneParent: (listData: IDropdownRawItem[], chil
12
12
  export declare function prepareComboOptionsForList(actionListData: Set<string>, translate: (s: string) => string, options: IDropdownRawItem[], getSelectedFn: () => string[], getSelectedChildFn: (optionId: any) => string[], isFlatList: boolean, getTitle?: (itemId: string) => string, isAllSelected?: boolean): ICheckboxListWithActionOption[];
13
13
  export declare const createOptions: (stateObj: IDropdownState, translate: (s: string) => string, listItem: IDropdownRawItem[], isAllSelected?: boolean) => ICheckboxListWithActionOption[];
14
14
  export declare const sortDropdownItemArray: (arr: IDropdownRawItem[], sortFn?: (a: IDropdownRawItem, b: IDropdownRawItem) => number) => IDropdownRawItem[];
15
+ /**
16
+ * An item is half selected when at least one of its direct children is selected — the state its checkbox
17
+ * shows as indeterminate (see `prepareComboOptionsForList`). `selectedSet` is passed in so the sort can use
18
+ * its own snapshot of the selection instead of the live one.
19
+ */
20
+ export declare const hasSelectedChild: (groupsMap: IDropdownState["groupsMap"], itemId: string, selectedSet: Set<string>) => boolean;
21
+ interface ISortSelectedToTopOptions {
22
+ /** Items that count as selected although their own id is not, e.g. a half selected parent. */
23
+ isPartiallySelected?: (item: IDropdownRawItem) => boolean;
24
+ /** Disabled items cannot be toggled, so they follow the selectable ones. */
25
+ isDisabled?: (item: IDropdownRawItem) => boolean;
26
+ }
15
27
  /**
16
28
  * Floats selected items to the top of the list while preserving the relative order within each
17
- * group (selected and unselected). When `selectedIds` is empty the original array is returned
18
- * unchanged (no allocation). Uses a single-pass partition to avoid traversing `items` twice.
29
+ * group. The order is: selected items that can be toggled, then selected items that are disabled
30
+ * (so a newly selected item is never pushed down by disabled ones), then everything else. An item
31
+ * counts as selected when its own id is in `selectedIds` or when `isPartiallySelected` returns true
32
+ * for it, so half selected items sit with the selected ones. When `selectedIds` is empty the original
33
+ * array is returned unchanged (no allocation). Uses a single-pass partition to avoid traversing
34
+ * `items` more than once.
19
35
  */
20
- export declare const applySortSelectedToTop: (items: IDropdownRawItem[], selectedIds: string[]) => IDropdownRawItem[];
36
+ export declare const applySortSelectedToTop: (items: IDropdownRawItem[], selectedIds: string[], { isPartiallySelected, isDisabled }?: ISortSelectedToTopOptions) => IDropdownRawItem[];
21
37
  export declare const createSingleListOptions: (stateObj: IDropdownState, translate: (s: string) => string, listItem: IDropdownRawItem[]) => {
22
38
  destructive: boolean | undefined;
23
39
  divider?: boolean | undefined;
@@ -56,3 +72,4 @@ export declare const getAllyTriggerProps: (allyPattern: "selectOnlyCombobox" | "
56
72
  readonly role: "button";
57
73
  readonly "aria-haspopup": "menu";
58
74
  };
75
+ export {};
@@ -81,21 +81,43 @@ export const sortDropdownItemArray = (arr, sortFn) => {
81
81
  }
82
82
  return arr;
83
83
  };
84
+ /**
85
+ * An item is half selected when at least one of its direct children is selected — the state its checkbox
86
+ * shows as indeterminate (see `prepareComboOptionsForList`). `selectedSet` is passed in so the sort can use
87
+ * its own snapshot of the selection instead of the live one.
88
+ */
89
+ export const hasSelectedChild = (groupsMap, itemId, selectedSet) => {
90
+ const group = groupsMap[itemId];
91
+ return group ? getChildList(group.children).some(child => selectedSet.has(child.id)) : false;
92
+ };
84
93
  /**
85
94
  * Floats selected items to the top of the list while preserving the relative order within each
86
- * group (selected and unselected). When `selectedIds` is empty the original array is returned
87
- * unchanged (no allocation). Uses a single-pass partition to avoid traversing `items` twice.
95
+ * group. The order is: selected items that can be toggled, then selected items that are disabled
96
+ * (so a newly selected item is never pushed down by disabled ones), then everything else. An item
97
+ * counts as selected when its own id is in `selectedIds` or when `isPartiallySelected` returns true
98
+ * for it, so half selected items sit with the selected ones. When `selectedIds` is empty the original
99
+ * array is returned unchanged (no allocation). Uses a single-pass partition to avoid traversing
100
+ * `items` more than once.
88
101
  */
89
- export const applySortSelectedToTop = (items, selectedIds) => {
102
+ export const applySortSelectedToTop = (items, selectedIds, { isPartiallySelected, isDisabled } = {}) => {
90
103
  if (!selectedIds.length)
91
104
  return items;
92
105
  const selectedSet = new Set(selectedIds);
93
106
  const selected = [];
107
+ const selectedDisabled = [];
94
108
  const unselected = [];
95
109
  for (const item of items) {
96
- (selectedSet.has(item.id) ? selected : unselected).push(item);
110
+ if (!selectedSet.has(item.id) && !(isPartiallySelected === null || isPartiallySelected === void 0 ? void 0 : isPartiallySelected(item))) {
111
+ unselected.push(item);
112
+ }
113
+ else if (isDisabled === null || isDisabled === void 0 ? void 0 : isDisabled(item)) {
114
+ selectedDisabled.push(item);
115
+ }
116
+ else {
117
+ selected.push(item);
118
+ }
97
119
  }
98
- return [...selected, ...unselected];
120
+ return [...selected, ...selectedDisabled, ...unselected];
99
121
  };
100
122
  export const createSingleListOptions = (stateObj, translate, listItem) => {
101
123
  const preparedOptions = listItem && listItem.length
@@ -8,7 +8,7 @@ import { getInitialState, stateReducer, ROOT_GROUP_ID } from "./stateReducer/sta
8
8
  import { StateActionType } from "./stateReducer/stateActionType";
9
9
  import { FOCUSABLE_SELECTOR } from "../utils/focusableSelector";
10
10
  import { useEscape } from "../commonHelpers/hooks/useEscape";
11
- import { applySortSelectedToTop, checkIsDataProblem, compareObjectsArrays, compareStringsArrays, createListDataOptions, getDataForChange, getListDataWithDisabled, getSelectedCount, getStringFromAllSelected, getStringFromSelected, isAllChildrenSelected, isAllItemsHasOneParent, isEveryItemSelected, prepareSelectedIdsToItems, sortDropdownItemArray } from "./dropdownHelper";
11
+ import { applySortSelectedToTop, hasSelectedChild, checkIsDataProblem, compareObjectsArrays, compareStringsArrays, createListDataOptions, getDataForChange, getListDataWithDisabled, getSelectedCount, getStringFromAllSelected, getStringFromSelected, isAllChildrenSelected, isAllItemsHasOneParent, isEveryItemSelected, prepareSelectedIdsToItems, sortDropdownItemArray } from "./dropdownHelper";
12
12
  import { DropdownTrigger } from "./dropdownTrigger";
13
13
  import { isFocusable } from "../dialog/dialogHelpers";
14
14
  import { useLanguage } from "../utils/localization/useLanguage";
@@ -1147,20 +1147,36 @@ export const DropdownRaw = props => {
1147
1147
  // hasApplyButton=true: uses globalSelectedIds (committed selection) — reordering
1148
1148
  // only takes effect after the user clicks "Apply".
1149
1149
  const idsForSort = hasApplyButton ? state.globalSelectedIds : state.sortedTopSnapshot;
1150
- const floated = sortSelectedToTop ? applySortSelectedToTop(children, idsForSort) : children;
1151
- rawItems = floated.slice(0, listLimit);
1152
1150
  if (sortSelectedToTop) {
1151
+ // Half selected items (a direct child is selected, shown as indeterminate) float with the selected ones.
1152
+ // Disabled selected items follow the selectable ones; in a nested list `disabled` is not enforced (see createOptions).
1153
1153
  const selectedSet = new Set(idsForSort);
1154
- pinnedCount = rawItems.filter(item => selectedSet.has(item.id)).length;
1154
+ const isPartiallySelected = item => hasSelectedChild(state.groupsMap, item.id, selectedSet);
1155
+ const isDisabled = item => Boolean(item.disabled) && !state.isNestedList;
1156
+ rawItems = applySortSelectedToTop(children, idsForSort, {
1157
+ isPartiallySelected,
1158
+ isDisabled
1159
+ }).slice(0, listLimit);
1160
+ pinnedCount = rawItems.filter(item => selectedSet.has(item.id) || isPartiallySelected(item)).length;
1161
+ } else {
1162
+ rawItems = children.slice(0, listLimit);
1155
1163
  }
1156
1164
  }
1157
1165
  const listData = createListDataOptions(state, translate, rawItems, multiselect, currentAllSelected);
1158
1166
  // Explicit bounds check (rather than relying on CheckboxListWithAction's own
1159
1167
  // `ind < optionsArr.length - 1` guard) keeps the "don't draw a trailing divider"
1160
1168
  // intent local to where the boundary is computed.
1161
- const currentListData = multiselect && sortSelectedToTop && pinnedCount > 0 && pinnedCount < listData.length ? listData.map((opt, ind) => ind === pinnedCount - 1 ? Object.assign(Object.assign({}, opt), {
1162
- divider: true
1163
- }) : opt) : listData;
1169
+ // Floated items drop their own dividers, so the boundary divider is the only one drawn between them and the rest.
1170
+ const currentListData = multiselect && sortSelectedToTop && pinnedCount > 0 && pinnedCount < listData.length ? listData.map((opt, ind) => {
1171
+ if (ind === pinnedCount - 1) {
1172
+ return Object.assign(Object.assign({}, opt), {
1173
+ divider: true
1174
+ });
1175
+ }
1176
+ return ind < pinnedCount && opt.divider ? Object.assign(Object.assign({}, opt), {
1177
+ divider: false
1178
+ }) : opt;
1179
+ }) : listData;
1164
1180
  return _jsx(DropdownList, {
1165
1181
  onBackButtonClick: handleBackButtonClick,
1166
1182
  onSelectAllClick: isSearchMode ? handleSelectAllInSearch : handleSelectAllClick,
@@ -102,7 +102,7 @@ export const PageToolbarCollapsedItemsControl = ({
102
102
  rel: child.props.rel,
103
103
  icon: child.props.icon,
104
104
  iconOpen: child.props.iconOpen,
105
- destructive: child.props.destructive
105
+ destructive: child.props.type === ButtonType.Destructive || child.props.type === ButtonType.TertiaryDestructive
106
106
  });
107
107
  items.push(el);
108
108
  }
@@ -2,20 +2,33 @@ import { IButton } from "../button/button";
2
2
  import { TButtonIconPosition } from "../textIconButton/textIconButton";
3
3
  import { TSvgIconSize } from "../svgIcon/svgIconSize";
4
4
  import { IIcon } from "../icons/icon";
5
+ import { TAlignment } from "../absolute/absolute";
5
6
  export interface IHeaderButton extends Omit<IButton, "onClick"> {
6
7
  id: string;
7
8
  important?: boolean;
8
9
  inContextMenu?: boolean;
9
10
  icon?: React.FC<IIcon>;
10
11
  iconOpen?: boolean;
11
- destructive?: boolean;
12
12
  iconPosition?: TButtonIconPosition;
13
13
  iconClasses?: string;
14
14
  iconSize?: TSvgIconSize;
15
+ /**
16
+ * Tooltip content shown only while the button is `disabled`.
17
+ *
18
+ * When `disabled` and `disabledTooltip` are both set, the button uses `aria-disabled` instead of the native
19
+ * `disabled` attribute, so it stays focusable and hoverable and the tooltip can explain why it is unavailable.
20
+ * In every other case the standard `disabled` attribute is used, and `disabledTooltip` is ignored while the
21
+ * button is enabled.
22
+ */
23
+ disabledTooltip?: React.ReactNode;
15
24
  onClick?: (id: string, event: React.MouseEvent) => void;
16
25
  }
26
+ interface IHeaderButtonInternal {
27
+ tooltipAlignment?: TAlignment;
28
+ }
17
29
  export declare const HeaderButtonDisplayName = "HeaderButton";
18
30
  export declare const HeaderButton: {
19
- ({ id, children, icon, iconOpen, destructive, iconPosition, className, important, onClick, inContextMenu, disabled, focusableWhenDisabled, ...restProps }: IHeaderButton): import("react/jsx-runtime").JSX.Element;
31
+ ({ id, children, icon, iconOpen, iconPosition, className, important, onClick, inContextMenu, disabledTooltip, tooltipAlignment, disabled, link, ...restProps }: IHeaderButton & IHeaderButtonInternal): import("react/jsx-runtime").JSX.Element;
20
32
  displayName: string;
21
33
  };
34
+ export {};
@@ -13,13 +13,18 @@ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  import { Button } from "../button/button";
14
14
  import { ButtonIconPosition, TextIconButton } from "../textIconButton/textIconButton";
15
15
  import { classNames } from "../commonHelpers/classNames/classNames";
16
+ import { Tooltip } from "../tooltip/tooltip";
17
+ import { getButtonDisabledProps } from "../button/buttonDisabledProps";
16
18
  export const HeaderButtonDisplayName = "HeaderButton";
17
19
  export const HeaderButton = (_a) => {
18
- var { id, children, icon, iconOpen, destructive, iconPosition = ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabled, focusableWhenDisabled } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconOpen", "destructive", "iconPosition", "className", "important", "onClick", "inContextMenu", "disabled", "focusableWhenDisabled"]);
19
- const fullClassName = classNames(["zen-header-button", className]);
20
- const props = Object.assign(Object.assign({}, restProps), { disabled, "data-important": important ? "1" : "0",
21
- // Defaults on when disabled; pass `focusableWhenDisabled={false}` to opt back into native `disabled`.
22
- focusableWhenDisabled: focusableWhenDisabled !== null && focusableWhenDisabled !== void 0 ? focusableWhenDisabled : Boolean(disabled) });
23
- return icon ? (_jsx(TextIconButton, Object.assign({ id: id, icon: icon, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children }))) : (_jsx(Button, Object.assign({ id: id, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
20
+ var { id, children, icon, iconOpen, iconPosition = ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabledTooltip, tooltipAlignment = "bottom", disabled, link } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconOpen", "iconPosition", "className", "important", "onClick", "inContextMenu", "disabledTooltip", "tooltipAlignment", "disabled", "link"]);
21
+ const tooltip = disabled ? disabledTooltip : undefined;
22
+ // Native `disabled` takes a button out of the tab order and blocks hover, so a tooltip could never be reached.
23
+ // Only a disabled button that has a tooltip to show is kept focusable, via `aria-disabled`.
24
+ const _b = getButtonDisabledProps({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
25
+ const fullClassName = classNames(["zen-header-button", className, disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
26
+ const props = Object.assign(Object.assign(Object.assign({}, restProps), { link, "data-important": important ? "1" : "0" }), disabledProps);
27
+ const button = icon ? (_jsx(TextIconButton, Object.assign({ id: id, icon: icon, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children }))) : (_jsx(Button, Object.assign({ id: id, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
28
+ return tooltip ? (_jsx(Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
24
29
  };
25
30
  HeaderButton.displayName = HeaderButtonDisplayName;
@@ -10,60 +10,6 @@ import { useDrive } from "../../utils/theme/useDrive";
10
10
  import { getMenuButtonState } from "../utils/getMenuButtonState";
11
11
  import { isSafeHref } from "../utils/isSafeHref";
12
12
  import { getSafeRel } from "../utils/getSafeRel";
13
- // Templates below drop native `disabled` to stay focusable/hoverable; this guard blocks activation instead.
14
- const handleDisabledGuardedClick = (e, disabled, id, onClick) => {
15
- if (disabled) {
16
- e.preventDefault();
17
- e.stopPropagation();
18
- return;
19
- }
20
- onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
21
- };
22
- // Skip Space-to-activate when disabled, so it doesn't flash `--active` or self-click via `linkEl.click()`.
23
- const createDisabledGuardedLinkKeyEvents = (disabled) => ({
24
- onKeyDown: (e) => {
25
- if (disabled || e.key !== " ") {
26
- return;
27
- }
28
- e.preventDefault();
29
- const linkEl = e.target;
30
- linkEl.dataset.spaceDown = "1";
31
- linkEl.classList.add("zen-menu-button__action--active");
32
- },
33
- onKeyUp: (e) => {
34
- if (disabled || e.key !== " ") {
35
- return;
36
- }
37
- e.preventDefault();
38
- const linkEl = e.target;
39
- linkEl.classList.remove("zen-menu-button__action--active");
40
- if (linkEl.dataset.spaceDown) {
41
- delete linkEl.dataset.spaceDown;
42
- linkEl.click();
43
- }
44
- }
45
- });
46
- const getMenuButtonActionClassName = (hasChildren, hasState, isActive, disabled, destructiveClass, driveClasses) => classNames([
47
- "zen-menu-button__action",
48
- "zen-button",
49
- "zen-caption",
50
- hasChildren ? "zen-menu-button__action--has-children" : "",
51
- hasState && isActive ? "zen-menu-button__action--active" : "",
52
- disabled ? "zen-menu-button__action--disabled" : "",
53
- disabled ? "zen-button--disabled" : "",
54
- disabled ? "zen-button--disabled-focusable" : "",
55
- destructiveClass,
56
- driveClasses || ""
57
- ]);
58
- const getMenuLinkActionClassName = (disabled, destructiveClass, driveClasses) => classNames(["zen-menu-button__action", "zen-caption", disabled ? "zen-menu-button__action--disabled" : "", destructiveClass, driveClasses || ""]);
59
- // An <a> without `href` isn't tabbable, so `tabIndex="0"` keeps a disabled link item in the tab order.
60
- const getMenuLinkAccessibilityProps = (disabled, link) => ({
61
- href: disabled || !isSafeHref(link) ? undefined : link,
62
- tabIndex: disabled ? 0 : undefined,
63
- "aria-disabled": disabled
64
- });
65
- const getMenuItemRole = (hasState) => (hasState ? "menuitemcheckbox" : "menuitem");
66
- const getMenuItemAriaChecked = (hasState, isActive) => (hasState ? isActive : undefined);
67
13
  const createMenuIcon = (iconComponent, size, className) => iconComponent ? createElement(iconComponent, { size, className }) : null;
68
14
  const resolveTarget = (target, iconOpen) => target !== null && target !== void 0 ? target : (iconOpen ? "_blank" : undefined);
69
15
  export const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen, destructive, name, link, target, rel, className = "", active = null, ref, dataAnalyticsId }) => {
@@ -74,10 +20,46 @@ export const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen,
74
20
  const destructiveClass = destructive ? "zen-menu-button__action--destructive" : "";
75
21
  const effectiveTarget = resolveTarget(target, iconOpen);
76
22
  const ChevronIcon = useDirectionalIcon(IconChevronRight, IconChevronLeft);
77
- const linkEvents = useMemo(() => createDisabledGuardedLinkKeyEvents(disabled), [disabled]);
78
- const onClickHandler = useCallback((e) => handleDisabledGuardedClick(e, disabled, id, onClick), [id, onClick, disabled]);
23
+ const linkEvents = useMemo(() => ({
24
+ onKeyDown: e => {
25
+ if (e.key === " ") {
26
+ e.preventDefault();
27
+ const linkEl = e.target;
28
+ linkEl.dataset.spaceDown = "1";
29
+ linkEl.classList.add("zen-menu-button__action--active");
30
+ }
31
+ },
32
+ onKeyUp: e => {
33
+ if (e.key === " ") {
34
+ e.preventDefault();
35
+ const linkEl = e.target;
36
+ linkEl.classList.remove("zen-menu-button__action--active");
37
+ if (linkEl.dataset.spaceDown) {
38
+ delete linkEl.dataset.spaceDown;
39
+ linkEl.click();
40
+ }
41
+ }
42
+ }
43
+ }), []);
44
+ const onClickHandler = useCallback((e) => {
45
+ onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
46
+ }, [id, onClick]);
79
47
  const iconAfterElement = createMenuIcon(iconOpen ? IconOpen : undefined, iconSize, "zen-menu-button__icon-after");
80
- const buttonTemplate = (_jsxs("button", { type: "button", "data-id": id, ref: ref, title: name, className: getMenuButtonActionClassName(hasChildren, hasState, isActive, disabled, destructiveClass, driveMenuButtonActionClasses), role: getMenuItemRole(hasState), onClick: onClickHandler, "aria-disabled": disabled ? true : undefined, "aria-checked": getMenuItemAriaChecked(hasState, isActive), "data-analytics-id": dataAnalyticsId, children: [createMenuIcon(icon, iconSize, classNames(["zen-caption__pre-content", "zen-menu-button__icon"])), _jsx("span", { className: "zen-menu-button__action-text", children: name }), iconAfterElement, !!hasChildren && _jsx(ChevronIcon, { size: iconSize, className: classNames(["zen-caption__pre-content", "zen-menu-button__more-icon"]) })] }));
81
- const linkTemplate = (_jsxs("a", Object.assign({ "data-id": id, role: "menuitem", className: getMenuLinkActionClassName(disabled, destructiveClass, driveMenuButtonActionClasses) }, getMenuLinkAccessibilityProps(disabled, link), { onClick: onClickHandler, target: effectiveTarget, rel: getSafeRel(rel, effectiveTarget), "data-analytics-id": dataAnalyticsId }, linkEvents, { children: [createMenuIcon(icon, iconSize, "zen-caption__pre-content"), iconAfterElement ? _jsx("span", { className: "zen-menu-button__action-text", children: name }) : name, iconAfterElement] })));
48
+ const buttonTemplate = (_jsxs("button", { type: "button", "data-id": id, ref: ref, disabled: !!disabled, title: name, className: classNames([
49
+ "zen-menu-button__action",
50
+ "zen-button",
51
+ "zen-caption",
52
+ hasChildren ? "zen-menu-button__action--has-children" : "",
53
+ hasState && isActive ? "zen-menu-button__action--active" : "",
54
+ destructiveClass,
55
+ driveMenuButtonActionClasses || ""
56
+ ]), role: hasState ? "menuitemcheckbox" : "menuitem", onClick: onClickHandler, "aria-checked": hasState ? isActive : undefined, "data-analytics-id": dataAnalyticsId, children: [createMenuIcon(icon, iconSize, classNames(["zen-caption__pre-content", "zen-menu-button__icon"])), _jsx("span", { className: "zen-menu-button__action-text", children: name }), iconAfterElement, !!hasChildren && _jsx(ChevronIcon, { size: iconSize, className: classNames(["zen-caption__pre-content", "zen-menu-button__more-icon"]) })] }));
57
+ const linkTemplate = (_jsxs("a", Object.assign({ "data-id": id, role: "menuitem", className: classNames([
58
+ "zen-menu-button__action",
59
+ "zen-caption",
60
+ disabled ? "zen-menu-button__action--disabled" : "",
61
+ destructiveClass,
62
+ driveMenuButtonActionClasses || ""
63
+ ]), href: disabled || !isSafeHref(link) ? undefined : link, "aria-disabled": disabled, onClick: onClickHandler, target: effectiveTarget, rel: getSafeRel(rel, effectiveTarget), "data-analytics-id": dataAnalyticsId }, linkEvents, { children: [createMenuIcon(icon, iconSize, "zen-caption__pre-content"), iconAfterElement ? _jsx("span", { className: "zen-menu-button__action-text", children: name }) : name, iconAfterElement] })));
82
64
  return (_jsx("li", { role: "presentation", className: classNames(["zen-menu-button", className]), children: link ? linkTemplate : buttonTemplate }));
83
65
  };