@geotab/zenith 3.17.0-beta.2 → 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.
@@ -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
  };
@@ -18,36 +18,19 @@ const classNames_1 = require("../commonHelpers/classNames/classNames");
18
18
  const useDriveClassName_1 = require("../utils/theme/useDriveClassName");
19
19
  const filterGridProps_1 = require("../gridLayout/utils/filterGridProps");
20
20
  const useMobileClassName_1 = require("../utils/theme/useMobileClassName");
21
- const blockActivation = (event) => {
22
- event.preventDefault();
23
- event.stopPropagation();
24
- };
25
- // Guards on `disabled` alone, not just `focusableWhenDisabled`, so clicks are blocked in JS regardless of
26
- // CSS. `onKeyDown`/`onKeyUp` stay unguarded — Tooltip needs them.
27
- const getDisabledGuardedHandlers = (disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp) => ({
28
- onClick: disabled ? blockActivation : onClick,
29
- onMouseDown: isFocusableWhenDisabled ? blockActivation : onMouseDown,
30
- onMouseUp: isFocusableWhenDisabled ? blockActivation : onMouseUp
31
- });
32
- const getButtonClassName = (type, disabled, isFocusableWhenDisabled, mobileOrDriveClasses, className) => {
21
+ const Button = (_a) => {
22
+ 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"]);
33
23
  const modifierClassName = type && type !== buttonType_1.ButtonType.Secondary ? `zen-button--${type}` : "";
34
24
  const disabledClassName = disabled ? "zen-button--disabled" : "";
35
- const disabledFocusableClassName = isFocusableWhenDisabled ? "zen-button--disabled-focusable" : "";
36
- return (0, classNames_1.classNames)(["zen-button", mobileOrDriveClasses, modifierClassName, disabledClassName, disabledFocusableClassName, className !== null && className !== void 0 ? className : ""]);
37
- };
38
- const Button = (_a) => {
39
- 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"]);
40
- const isFocusableWhenDisabled = Boolean(disabled) && Boolean(focusableWhenDisabled);
41
25
  const driveClasses = (0, useDriveClassName_1.useDriveClassName)("zen-button");
42
26
  const ariaLabelValue = ariaLabel || title;
43
27
  const mobileClasses = (0, useMobileClassName_1.useMobileClassName)("zen-button");
44
28
  const commonArgs = Object.assign(Object.assign(Object.assign({ id,
45
- title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className: getButtonClassName(type, disabled, isFocusableWhenDisabled, mobileClasses || driveClasses || "", className) });
46
- const { onClick: onClickWithDisabledGuard, onMouseDown: onMouseDownWithDisabledGuard, onMouseUp: onMouseUpWithDisabledGuard } = getDisabledGuardedHandlers(disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp);
29
+ title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className: (0, classNames_1.classNames)(["zen-button", mobileClasses || driveClasses || "", modifierClassName, disabledClassName, className !== null && className !== void 0 ? className : ""]) });
47
30
  if (link) {
48
- return ((0, jsx_runtime_1.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, (0, filterGridProps_1.filterGridProps)(other), { children: children })));
31
+ return ((0, jsx_runtime_1.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, (0, filterGridProps_1.filterGridProps)(other), { children: children })));
49
32
  }
50
- return ((0, jsx_runtime_1.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, (0, filterGridProps_1.filterGridProps)(other), { children: children })));
33
+ return ((0, jsx_runtime_1.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, (0, filterGridProps_1.filterGridProps)(other), { children: children })));
51
34
  };
52
35
  exports.Button = Button;
53
36
  exports.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,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getButtonDisabledProps = exports.BUTTON_DISABLED_FOCUSABLE_CLASS = exports.BUTTON_DISABLED_CLASS = void 0;
4
+ const classNames_1 = require("../commonHelpers/classNames/classNames");
5
+ // `button.less` styles these classes: `--disabled-focusable` keeps pointer events so hover/tooltip still work.
6
+ exports.BUTTON_DISABLED_CLASS = "zen-button--disabled";
7
+ exports.BUTTON_DISABLED_FOCUSABLE_CLASS = "zen-button--disabled-focusable";
8
+ const blockActivation = (event) => {
9
+ event.preventDefault();
10
+ event.stopPropagation();
11
+ };
12
+ /**
13
+ * Props that make `Button` (or `TextIconButton`) disabled. `Button` itself only applies them.
14
+ *
15
+ * - enabled: `{ disabled }`
16
+ * - disabled: native `disabled`, the button leaves the tab order
17
+ * - disabled and `focusable`: `aria-disabled`, activation is blocked, the button stays focusable and hoverable
18
+ *
19
+ * `onKeyDown`/`onKeyUp` are left untouched: Tooltip needs them. Enter/Space on a focused button without the native
20
+ * `disabled` attribute fire a click, which is blocked here.
21
+ *
22
+ * The caller merges the returned `className` with its own and wraps the button in whatever explains the state.
23
+ */
24
+ const getButtonDisabledProps = ({ disabled, focusable, link }) => {
25
+ if (!disabled || !focusable) {
26
+ return { disabled };
27
+ }
28
+ const hasLink = Boolean(link);
29
+ return Object.assign(Object.assign({}, (hasLink ? { disabled: true, tabIndex: 0 } : { "aria-disabled": true })), {
30
+ // Button adds `zen-button--disabled` to a disabled link itself.
31
+ className: (0, classNames_1.classNames)([hasLink ? "" : exports.BUTTON_DISABLED_CLASS, exports.BUTTON_DISABLED_FOCUSABLE_CLASS]), onClick: blockActivation, onMouseDown: blockActivation, onMouseUp: blockActivation });
32
+ };
33
+ exports.getButtonDisabledProps = getButtonDisabledProps;
@@ -2,6 +2,7 @@ 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;
@@ -11,10 +12,23 @@ export interface IHeaderButton extends Omit<IButton, "onClick"> {
11
12
  iconPosition?: TButtonIconPosition;
12
13
  iconClasses?: string;
13
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;
14
24
  onClick?: (id: string, event: React.MouseEvent) => void;
15
25
  }
26
+ interface IHeaderButtonInternal {
27
+ tooltipAlignment?: TAlignment;
28
+ }
16
29
  export declare const HeaderButtonDisplayName = "HeaderButton";
17
30
  export declare const HeaderButton: {
18
- ({ id, children, icon, iconOpen, 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;
19
32
  displayName: string;
20
33
  };
34
+ export {};
@@ -16,14 +16,19 @@ const jsx_runtime_1 = require("react/jsx-runtime");
16
16
  const button_1 = require("../button/button");
17
17
  const textIconButton_1 = require("../textIconButton/textIconButton");
18
18
  const classNames_1 = require("../commonHelpers/classNames/classNames");
19
+ const tooltip_1 = require("../tooltip/tooltip");
20
+ const buttonDisabledProps_1 = require("../button/buttonDisabledProps");
19
21
  exports.HeaderButtonDisplayName = "HeaderButton";
20
22
  const HeaderButton = (_a) => {
21
- var { id, children, icon, iconOpen, iconPosition = textIconButton_1.ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabled, focusableWhenDisabled } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconOpen", "iconPosition", "className", "important", "onClick", "inContextMenu", "disabled", "focusableWhenDisabled"]);
22
- const fullClassName = (0, classNames_1.classNames)(["zen-header-button", className]);
23
- const props = Object.assign(Object.assign({}, restProps), { disabled, "data-important": important ? "1" : "0",
24
- // Defaults on when disabled; pass `focusableWhenDisabled={false}` to opt back into native `disabled`.
25
- focusableWhenDisabled: focusableWhenDisabled !== null && focusableWhenDisabled !== void 0 ? focusableWhenDisabled : Boolean(disabled) });
26
- return icon ? ((0, jsx_runtime_1.jsx)(textIconButton_1.TextIconButton, Object.assign({ id: id, icon: icon, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children }))) : ((0, jsx_runtime_1.jsx)(button_1.Button, Object.assign({ id: id, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
23
+ var { id, children, icon, iconOpen, iconPosition = textIconButton_1.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"]);
24
+ const tooltip = disabled ? disabledTooltip : undefined;
25
+ // Native `disabled` takes a button out of the tab order and blocks hover, so a tooltip could never be reached.
26
+ // Only a disabled button that has a tooltip to show is kept focusable, via `aria-disabled`.
27
+ const _b = (0, buttonDisabledProps_1.getButtonDisabledProps)({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
28
+ const fullClassName = (0, classNames_1.classNames)(["zen-header-button", className, disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
29
+ const props = Object.assign(Object.assign(Object.assign({}, restProps), { link, "data-important": important ? "1" : "0" }), disabledProps);
30
+ const button = icon ? ((0, jsx_runtime_1.jsx)(textIconButton_1.TextIconButton, Object.assign({ id: id, icon: icon, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children }))) : ((0, jsx_runtime_1.jsx)(button_1.Button, Object.assign({ id: id, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
31
+ return tooltip ? ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
27
32
  };
28
33
  exports.HeaderButton = HeaderButton;
29
34
  exports.HeaderButton.displayName = exports.HeaderButtonDisplayName;
package/dist/index.css CHANGED
@@ -4007,8 +4007,8 @@ html:lang(ar) .zen-menu-button__action {
4007
4007
  padding-block: 0.75rem;
4008
4008
  padding-inline: 1rem 0.5rem;
4009
4009
  }
4010
- .zen-menu-button__action:focus:not(.zen-menu-button__action--disabled),
4011
- .zen-menu-button__action:focus-visible:not(.zen-menu-button__action--disabled),
4010
+ .zen-menu-button__action:focus,
4011
+ .zen-menu-button__action:focus-visible,
4012
4012
  .zen-menu-button__action:hover:not(.zen-menu-button__action--disabled):not(.zen-menu-button__action--selected):not(.zen-menu-button__action--active) {
4013
4013
  color: var(--zen-menu-action-color);
4014
4014
  fill: var(--zen-menu-action-fill);
@@ -4083,10 +4083,6 @@ html:lang(ar) .zen-menu-button__action {
4083
4083
  .zen-menu-button__action.zen-menu-button__action {
4084
4084
  outline: none;
4085
4085
  }
4086
- .zen-menu-button__action--disabled:focus-visible {
4087
- outline: 1px solid var(--borders-form-field--active);
4088
- outline-offset: -2px;
4089
- }
4090
4086
  .zen-menu-button__icon {
4091
4087
  flex: 0 0 auto;
4092
4088
  }
@@ -13,60 +13,6 @@ const useDrive_1 = require("../../utils/theme/useDrive");
13
13
  const getMenuButtonState_1 = require("../utils/getMenuButtonState");
14
14
  const isSafeHref_1 = require("../utils/isSafeHref");
15
15
  const getSafeRel_1 = require("../utils/getSafeRel");
16
- // Templates below drop native `disabled` to stay focusable/hoverable; this guard blocks activation instead.
17
- const handleDisabledGuardedClick = (e, disabled, id, onClick) => {
18
- if (disabled) {
19
- e.preventDefault();
20
- e.stopPropagation();
21
- return;
22
- }
23
- onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
24
- };
25
- // Skip Space-to-activate when disabled, so it doesn't flash `--active` or self-click via `linkEl.click()`.
26
- const createDisabledGuardedLinkKeyEvents = (disabled) => ({
27
- onKeyDown: (e) => {
28
- if (disabled || e.key !== " ") {
29
- return;
30
- }
31
- e.preventDefault();
32
- const linkEl = e.target;
33
- linkEl.dataset.spaceDown = "1";
34
- linkEl.classList.add("zen-menu-button__action--active");
35
- },
36
- onKeyUp: (e) => {
37
- if (disabled || e.key !== " ") {
38
- return;
39
- }
40
- e.preventDefault();
41
- const linkEl = e.target;
42
- linkEl.classList.remove("zen-menu-button__action--active");
43
- if (linkEl.dataset.spaceDown) {
44
- delete linkEl.dataset.spaceDown;
45
- linkEl.click();
46
- }
47
- }
48
- });
49
- const getMenuButtonActionClassName = (hasChildren, hasState, isActive, disabled, destructiveClass, driveClasses) => (0, classNames_1.classNames)([
50
- "zen-menu-button__action",
51
- "zen-button",
52
- "zen-caption",
53
- hasChildren ? "zen-menu-button__action--has-children" : "",
54
- hasState && isActive ? "zen-menu-button__action--active" : "",
55
- disabled ? "zen-menu-button__action--disabled" : "",
56
- disabled ? "zen-button--disabled" : "",
57
- disabled ? "zen-button--disabled-focusable" : "",
58
- destructiveClass,
59
- driveClasses || ""
60
- ]);
61
- const getMenuLinkActionClassName = (disabled, destructiveClass, driveClasses) => (0, classNames_1.classNames)(["zen-menu-button__action", "zen-caption", disabled ? "zen-menu-button__action--disabled" : "", destructiveClass, driveClasses || ""]);
62
- // An <a> without `href` isn't tabbable, so `tabIndex="0"` keeps a disabled link item in the tab order.
63
- const getMenuLinkAccessibilityProps = (disabled, link) => ({
64
- href: disabled || !(0, isSafeHref_1.isSafeHref)(link) ? undefined : link,
65
- tabIndex: disabled ? 0 : undefined,
66
- "aria-disabled": disabled
67
- });
68
- const getMenuItemRole = (hasState) => (hasState ? "menuitemcheckbox" : "menuitem");
69
- const getMenuItemAriaChecked = (hasState, isActive) => (hasState ? isActive : undefined);
70
16
  const createMenuIcon = (iconComponent, size, className) => iconComponent ? (0, react_1.createElement)(iconComponent, { size, className }) : null;
71
17
  const resolveTarget = (target, iconOpen) => target !== null && target !== void 0 ? target : (iconOpen ? "_blank" : undefined);
72
18
  const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen, destructive, name, link, target, rel, className = "", active = null, ref, dataAnalyticsId }) => {
@@ -77,11 +23,47 @@ const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen, destru
77
23
  const destructiveClass = destructive ? "zen-menu-button__action--destructive" : "";
78
24
  const effectiveTarget = resolveTarget(target, iconOpen);
79
25
  const ChevronIcon = (0, useDirectionalIcon_1.useDirectionalIcon)(iconChevronRight_1.IconChevronRight, iconChevronLeft_1.IconChevronLeft);
80
- const linkEvents = (0, react_1.useMemo)(() => createDisabledGuardedLinkKeyEvents(disabled), [disabled]);
81
- const onClickHandler = (0, react_1.useCallback)((e) => handleDisabledGuardedClick(e, disabled, id, onClick), [id, onClick, disabled]);
26
+ const linkEvents = (0, react_1.useMemo)(() => ({
27
+ onKeyDown: e => {
28
+ if (e.key === " ") {
29
+ e.preventDefault();
30
+ const linkEl = e.target;
31
+ linkEl.dataset.spaceDown = "1";
32
+ linkEl.classList.add("zen-menu-button__action--active");
33
+ }
34
+ },
35
+ onKeyUp: e => {
36
+ if (e.key === " ") {
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
+ }), []);
47
+ const onClickHandler = (0, react_1.useCallback)((e) => {
48
+ onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
49
+ }, [id, onClick]);
82
50
  const iconAfterElement = createMenuIcon(iconOpen ? iconOpen_1.IconOpen : undefined, iconSize, "zen-menu-button__icon-after");
83
- const buttonTemplate = ((0, jsx_runtime_1.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, (0, classNames_1.classNames)(["zen-caption__pre-content", "zen-menu-button__icon"])), (0, jsx_runtime_1.jsx)("span", { className: "zen-menu-button__action-text", children: name }), iconAfterElement, !!hasChildren && (0, jsx_runtime_1.jsx)(ChevronIcon, { size: iconSize, className: (0, classNames_1.classNames)(["zen-caption__pre-content", "zen-menu-button__more-icon"]) })] }));
84
- const linkTemplate = ((0, jsx_runtime_1.jsxs)("a", Object.assign({ "data-id": id, role: "menuitem", className: getMenuLinkActionClassName(disabled, destructiveClass, driveMenuButtonActionClasses) }, getMenuLinkAccessibilityProps(disabled, link), { onClick: onClickHandler, target: effectiveTarget, rel: (0, getSafeRel_1.getSafeRel)(rel, effectiveTarget), "data-analytics-id": dataAnalyticsId }, linkEvents, { children: [createMenuIcon(icon, iconSize, "zen-caption__pre-content"), iconAfterElement ? (0, jsx_runtime_1.jsx)("span", { className: "zen-menu-button__action-text", children: name }) : name, iconAfterElement] })));
51
+ const buttonTemplate = ((0, jsx_runtime_1.jsxs)("button", { type: "button", "data-id": id, ref: ref, disabled: !!disabled, title: name, className: (0, classNames_1.classNames)([
52
+ "zen-menu-button__action",
53
+ "zen-button",
54
+ "zen-caption",
55
+ hasChildren ? "zen-menu-button__action--has-children" : "",
56
+ hasState && isActive ? "zen-menu-button__action--active" : "",
57
+ destructiveClass,
58
+ driveMenuButtonActionClasses || ""
59
+ ]), role: hasState ? "menuitemcheckbox" : "menuitem", onClick: onClickHandler, "aria-checked": hasState ? isActive : undefined, "data-analytics-id": dataAnalyticsId, children: [createMenuIcon(icon, iconSize, (0, classNames_1.classNames)(["zen-caption__pre-content", "zen-menu-button__icon"])), (0, jsx_runtime_1.jsx)("span", { className: "zen-menu-button__action-text", children: name }), iconAfterElement, !!hasChildren && (0, jsx_runtime_1.jsx)(ChevronIcon, { size: iconSize, className: (0, classNames_1.classNames)(["zen-caption__pre-content", "zen-menu-button__more-icon"]) })] }));
60
+ const linkTemplate = ((0, jsx_runtime_1.jsxs)("a", Object.assign({ "data-id": id, role: "menuitem", className: (0, classNames_1.classNames)([
61
+ "zen-menu-button__action",
62
+ "zen-caption",
63
+ disabled ? "zen-menu-button__action--disabled" : "",
64
+ destructiveClass,
65
+ driveMenuButtonActionClasses || ""
66
+ ]), href: disabled || !(0, isSafeHref_1.isSafeHref)(link) ? undefined : link, "aria-disabled": disabled, onClick: onClickHandler, target: effectiveTarget, rel: (0, getSafeRel_1.getSafeRel)(rel, effectiveTarget), "data-analytics-id": dataAnalyticsId }, linkEvents, { children: [createMenuIcon(icon, iconSize, "zen-caption__pre-content"), iconAfterElement ? (0, jsx_runtime_1.jsx)("span", { className: "zen-menu-button__action-text", children: name }) : name, iconAfterElement] })));
85
67
  return ((0, jsx_runtime_1.jsx)("li", { role: "presentation", className: (0, classNames_1.classNames)(["zen-menu-button", className]), children: link ? linkTemplate : buttonTemplate }));
86
68
  };
87
69
  exports.MenuButton = MenuButton;
@@ -2,6 +2,7 @@ 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 IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "title"> {
6
7
  id: string;
7
8
  icon: React.FC<IIcon>;
@@ -9,10 +10,23 @@ export interface IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "t
9
10
  iconPosition?: TButtonIconPosition;
10
11
  iconClasses?: string;
11
12
  iconSize?: TSvgIconSize;
13
+ /**
14
+ * Tooltip content shown only while the button is `disabled`.
15
+ *
16
+ * When `disabled` and `disabledTooltip` are both set, the button uses `aria-disabled` instead of the native
17
+ * `disabled` attribute, so it stays focusable and hoverable and the tooltip can explain why it is unavailable.
18
+ * In every other case the standard `disabled` attribute is used, and `disabledTooltip` is ignored while the
19
+ * button is enabled.
20
+ */
21
+ disabledTooltip?: React.ReactNode;
12
22
  onClick?: (id: string, event: React.MouseEvent) => void;
13
23
  }
24
+ interface IPageHeaderButtonInternal {
25
+ tooltipAlignment?: TAlignment;
26
+ }
14
27
  export declare const PageHeaderButtonDisplayName = "PageHeaderButton";
15
28
  export declare const PageHeaderButton: {
16
- ({ id, children, icon, iconPosition, className, onClick, title, disabled, focusableWhenDisabled, ...restProps }: IPageHeaderButton): import("react/jsx-runtime").JSX.Element;
29
+ ({ id, children, icon, iconPosition, className, onClick, title, disabledTooltip, tooltipAlignment, disabled, link, ...restProps }: IPageHeaderButton & IPageHeaderButtonInternal): import("react/jsx-runtime").JSX.Element;
17
30
  displayName: string;
18
31
  };
32
+ export {};
@@ -15,14 +15,16 @@ exports.PageHeaderButton = exports.PageHeaderButtonDisplayName = void 0;
15
15
  const jsx_runtime_1 = require("react/jsx-runtime");
16
16
  const textIconButton_1 = require("../textIconButton/textIconButton");
17
17
  const classNames_1 = require("../commonHelpers/classNames/classNames");
18
+ const tooltip_1 = require("../tooltip/tooltip");
19
+ const buttonDisabledProps_1 = require("../button/buttonDisabledProps");
18
20
  exports.PageHeaderButtonDisplayName = "PageHeaderButton";
19
21
  const PageHeaderButton = (_a) => {
20
- var { id, children, icon, iconPosition = textIconButton_1.ButtonIconPosition.Start, className = "", onClick, title, disabled, focusableWhenDisabled } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconPosition", "className", "onClick", "title", "disabled", "focusableWhenDisabled"]);
21
- const fullClassName = (0, classNames_1.classNames)(["zen-page-header-button", className || ""]);
22
- const props = Object.assign(Object.assign({}, restProps), { disabled,
23
- // Defaults on when disabled; pass `focusableWhenDisabled={false}` to opt back into native `disabled`.
24
- focusableWhenDisabled: focusableWhenDisabled !== null && focusableWhenDisabled !== void 0 ? focusableWhenDisabled : Boolean(disabled) });
25
- return ((0, jsx_runtime_1.jsx)(textIconButton_1.TextIconButton, Object.assign({ id: id, icon: icon, title: title, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
22
+ var { id, children, icon, iconPosition = textIconButton_1.ButtonIconPosition.Start, className = "", onClick, title, disabledTooltip, tooltipAlignment = "bottom", disabled, link } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconPosition", "className", "onClick", "title", "disabledTooltip", "tooltipAlignment", "disabled", "link"]);
23
+ const tooltip = disabled ? disabledTooltip : undefined;
24
+ const _b = (0, buttonDisabledProps_1.getButtonDisabledProps)({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
25
+ const fullClassName = (0, classNames_1.classNames)(["zen-page-header-button", className || "", disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
26
+ const button = ((0, jsx_runtime_1.jsx)(textIconButton_1.TextIconButton, Object.assign({ id: id, icon: icon, title: title, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined, link: link }, restProps, disabledProps, { children: children })));
27
+ return tooltip ? ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
26
28
  };
27
29
  exports.PageHeaderButton = PageHeaderButton;
28
30
  exports.PageHeaderButton.displayName = exports.PageHeaderButtonDisplayName;
@@ -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
+ };
@@ -2,6 +2,7 @@ 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;
@@ -11,10 +12,23 @@ export interface IHeaderButton extends Omit<IButton, "onClick"> {
11
12
  iconPosition?: TButtonIconPosition;
12
13
  iconClasses?: string;
13
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;
14
24
  onClick?: (id: string, event: React.MouseEvent) => void;
15
25
  }
26
+ interface IHeaderButtonInternal {
27
+ tooltipAlignment?: TAlignment;
28
+ }
16
29
  export declare const HeaderButtonDisplayName = "HeaderButton";
17
30
  export declare const HeaderButton: {
18
- ({ id, children, icon, iconOpen, 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;
19
32
  displayName: string;
20
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, iconPosition = ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabled, focusableWhenDisabled } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconOpen", "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
  };
@@ -2,6 +2,7 @@ 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 IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "title"> {
6
7
  id: string;
7
8
  icon: React.FC<IIcon>;
@@ -9,10 +10,23 @@ export interface IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "t
9
10
  iconPosition?: TButtonIconPosition;
10
11
  iconClasses?: string;
11
12
  iconSize?: TSvgIconSize;
13
+ /**
14
+ * Tooltip content shown only while the button is `disabled`.
15
+ *
16
+ * When `disabled` and `disabledTooltip` are both set, the button uses `aria-disabled` instead of the native
17
+ * `disabled` attribute, so it stays focusable and hoverable and the tooltip can explain why it is unavailable.
18
+ * In every other case the standard `disabled` attribute is used, and `disabledTooltip` is ignored while the
19
+ * button is enabled.
20
+ */
21
+ disabledTooltip?: React.ReactNode;
12
22
  onClick?: (id: string, event: React.MouseEvent) => void;
13
23
  }
24
+ interface IPageHeaderButtonInternal {
25
+ tooltipAlignment?: TAlignment;
26
+ }
14
27
  export declare const PageHeaderButtonDisplayName = "PageHeaderButton";
15
28
  export declare const PageHeaderButton: {
16
- ({ id, children, icon, iconPosition, className, onClick, title, disabled, focusableWhenDisabled, ...restProps }: IPageHeaderButton): import("react/jsx-runtime").JSX.Element;
29
+ ({ id, children, icon, iconPosition, className, onClick, title, disabledTooltip, tooltipAlignment, disabled, link, ...restProps }: IPageHeaderButton & IPageHeaderButtonInternal): import("react/jsx-runtime").JSX.Element;
17
30
  displayName: string;
18
31
  };
32
+ export {};
@@ -12,13 +12,15 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
13
  import { ButtonIconPosition, TextIconButton } from "../textIconButton/textIconButton";
14
14
  import { classNames } from "../commonHelpers/classNames/classNames";
15
+ import { Tooltip } from "../tooltip/tooltip";
16
+ import { getButtonDisabledProps } from "../button/buttonDisabledProps";
15
17
  export const PageHeaderButtonDisplayName = "PageHeaderButton";
16
18
  export const PageHeaderButton = (_a) => {
17
- var { id, children, icon, iconPosition = ButtonIconPosition.Start, className = "", onClick, title, disabled, focusableWhenDisabled } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconPosition", "className", "onClick", "title", "disabled", "focusableWhenDisabled"]);
18
- const fullClassName = classNames(["zen-page-header-button", className || ""]);
19
- const props = Object.assign(Object.assign({}, restProps), { disabled,
20
- // Defaults on when disabled; pass `focusableWhenDisabled={false}` to opt back into native `disabled`.
21
- focusableWhenDisabled: focusableWhenDisabled !== null && focusableWhenDisabled !== void 0 ? focusableWhenDisabled : Boolean(disabled) });
22
- return (_jsx(TextIconButton, Object.assign({ id: id, icon: icon, title: title, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
19
+ var { id, children, icon, iconPosition = ButtonIconPosition.Start, className = "", onClick, title, disabledTooltip, tooltipAlignment = "bottom", disabled, link } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconPosition", "className", "onClick", "title", "disabledTooltip", "tooltipAlignment", "disabled", "link"]);
20
+ const tooltip = disabled ? disabledTooltip : undefined;
21
+ const _b = getButtonDisabledProps({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
22
+ const fullClassName = classNames(["zen-page-header-button", className || "", disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
23
+ const button = (_jsx(TextIconButton, Object.assign({ id: id, icon: icon, title: title, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined, link: link }, restProps, disabledProps, { children: children })));
24
+ return tooltip ? (_jsx(Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
23
25
  };
24
26
  PageHeaderButton.displayName = PageHeaderButtonDisplayName;
@@ -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;
@@ -110,12 +110,14 @@ const getInitialDeviceType = () => {
110
110
  }
111
111
  return DeviceType.Desktop;
112
112
  };
113
+ // A disabled trigger (native `disabled`, or `aria-disabled` when it must stay focusable) never runs its own
114
+ // `onClick`, so the tooltip has to react to the click capture phase instead.
115
+ 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";
113
116
  const EventWrapper = ({
114
117
  children,
115
118
  deviceType,
116
119
  onKeyDownHandler,
117
120
  handleUniversalClick,
118
- isTriggerDisabledFocusable,
119
121
  setTooltipVisible,
120
122
  timerRef,
121
123
  timerMouseLeaveRef,
@@ -124,8 +126,9 @@ const EventWrapper = ({
124
126
  const isCurrentDeviceDesktop = deviceType === DeviceType.Desktop;
125
127
  const triggerProps = children.props;
126
128
  const originalOnClick = triggerProps.onClick;
127
- // A disabled-focusable trigger's own `onClick` is a no-op guard, so `composedOnClick` never runs on
128
- // mobile — see the `onClickCapture` handling in `mobileEvents` below.
129
+ // A disabled trigger doesn't run its own `onClick`, so on mobile the tap is handled in the capture phase
130
+ // instead — see `mobileEvents` below.
131
+ const isDisabledTrigger = isTriggerDisabled(triggerProps);
129
132
  const composedOnClick = useCallback(e => {
130
133
  // Call original onClick first if it exists
131
134
  if (originalOnClick) {
@@ -164,7 +167,7 @@ const EventWrapper = ({
164
167
  }, [handleUniversalClick]);
165
168
  const mobileEvents = Object.assign({
166
169
  onKeyDown: mobileOnKeyDown
167
- }, isTriggerDisabledFocusable ? {
170
+ }, isDisabledTrigger ? {
168
171
  onClickCapture: handleUniversalClick
169
172
  } : {
170
173
  onClick: composedOnClick
@@ -199,7 +202,7 @@ export const Tooltip = ({
199
202
  const [deviceType, setDeviceType] = useState(getInitialDeviceType);
200
203
  const isMobile = deviceType === DeviceType.Mobile;
201
204
  const triggerProps = trigger === null || trigger === void 0 ? void 0 : trigger.props;
202
- const isTriggerDisabledFocusable = Boolean(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.disabled) && (triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.focusableWhenDisabled) !== false;
205
+ const isDisabledTrigger = isTriggerDisabled(triggerProps);
203
206
  const tooltipId = useId();
204
207
  const {
205
208
  translate
@@ -230,7 +233,7 @@ export const Tooltip = ({
230
233
  }
231
234
  };
232
235
  const handleUniversalClick = useCallback(e => {
233
- const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !isTriggerDisabledFocusable;
236
+ const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !isDisabledTrigger;
234
237
  if (deviceType === DeviceType.Desktop) {
235
238
  setTooltipVisible(currentVisible => {
236
239
  if (currentVisible) {
@@ -239,14 +242,14 @@ export const Tooltip = ({
239
242
  return currentVisible;
240
243
  });
241
244
  } else {
242
- if (!isTriggerDisabledFocusable) {
245
+ if (!isDisabledTrigger) {
243
246
  e === null || e === void 0 ? void 0 : e.stopPropagation();
244
247
  }
245
248
  if (!hasExternalClickHandler) {
246
249
  setTooltipVisible(isVisible => !isVisible);
247
250
  }
248
251
  }
249
- }, [deviceType, isTriggerDisabledFocusable, triggerProps]);
252
+ }, [deviceType, isDisabledTrigger, triggerProps]);
250
253
  // Apply `triggerAriaLabel` to a `Tooltip.InlineTrigger` trigger (it has no `ariaLabel` prop
251
254
  // of its own); the default info-icon trigger below handles `triggerAriaLabel` separately.
252
255
  // An `aria-label` set directly on the `Tooltip.InlineTrigger` element takes priority over
@@ -273,7 +276,6 @@ export const Tooltip = ({
273
276
  isTooltipVisible: isTooltipVisible,
274
277
  onKeyDownHandler: onKeyDownHandler,
275
278
  handleUniversalClick: handleUniversalClick,
276
- isTriggerDisabledFocusable: isTriggerDisabledFocusable,
277
279
  setTooltipVisible: setTooltipVisible,
278
280
  timerRef: timerRef,
279
281
  timerMouseLeaveRef: timerMouseLeaveRef,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geotab/zenith",
3
- "version": "3.17.0-beta.2",
3
+ "version": "3.17.0",
4
4
  "description": "Zenith components library on React",
5
5
  "main": "dist/index.js",
6
6
  "types": "esm/index.d.ts",
@@ -156,5 +156,8 @@
156
156
  "minimatch@<9": {
157
157
  "brace-expansion": "^1.1.18"
158
158
  }
159
+ },
160
+ "publishConfig": {
161
+ "access": "public"
159
162
  }
160
163
  }