From 35b3eeb9b1e34b7292f2bf6f1ba9e99337cdd9cb Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 20:38:06 -0600
Subject: [PATCH 01/32] a11y(Tooltip): "tooltip" role
---
.../core/src/components/popover/popover.tsx | 13 ++---
.../components/popover/popoverSharedProps.ts | 14 ++++-
.../core/src/components/tooltip/tooltip.tsx | 46 ++++++++--------
packages/core/test/popover/popoverTests.tsx | 2 +-
packages/core/test/tooltip/tooltipTests.tsx | 54 ++++++++++++++++++-
5 files changed, 92 insertions(+), 37 deletions(-)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index 9f8d65335b..16f3e8dba0 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -25,6 +25,7 @@ import {
Reference,
type ReferenceChildrenProps,
} from "react-popper";
+import innerText from "react-innertext";
import {
AbstractPureComponent,
@@ -40,7 +41,6 @@ import { Overlay2 } from "../overlay2/overlay2";
import { ResizeSensor } from "../resize-sensor/resizeSensor";
// eslint-disable-next-line import/no-cycle
import { Tooltip } from "../tooltip/tooltip";
-
import { matchReferenceWidthModifier } from "./customModifiers";
import { POPOVER_ARROW_SVG_SIZE, PopoverArrow } from "./popoverArrow";
import { positionToPlacement } from "./popoverPlacementUtils";
@@ -74,11 +74,6 @@ export interface PopoverProps;
- /**
- * The content displayed inside the popover.
- */
- content?: string | React.JSX.Element;
-
/**
* The kind of interaction that triggers the display of the popover.
*
@@ -252,7 +247,7 @@ export class Popover<
const { disabled, content, placement, position = "auto", positioningStrategy } = this.props;
const { isOpen } = this.state;
- const isContentEmpty = content == null || (typeof content === "string" && content.trim() === "");
+ const isContentEmpty = content == null || innerText(content).trim() == "";
if (isContentEmpty) {
// need to do this check in render(), because `isOpen` is derived from
// state, and state can't necessarily be accessed in validateProps.
@@ -301,7 +296,7 @@ export class Popover<
}
}
- protected validateProps(props: PopoverProps & { children?: React.ReactNode }) {
+ protected validateProps(props: PopoverProps) {
if (props.isOpen == null && props.onInteraction != null) {
console.warn(Errors.POPOVER_WARN_UNCONTROLLED_ONINTERACTION);
}
@@ -417,7 +412,7 @@ export class Popover<
tabIndex: targetTabIndex,
});
} else {
- const childTarget = Utils.ensureElement(React.Children.toArray(children)[0])!;
+ const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
if (childTarget === undefined) {
return null;
diff --git a/packages/core/src/components/popover/popoverSharedProps.ts b/packages/core/src/components/popover/popoverSharedProps.ts
index 5c82ee1d2d..e471082c92 100644
--- a/packages/core/src/components/popover/popoverSharedProps.ts
+++ b/packages/core/src/components/popover/popoverSharedProps.ts
@@ -86,10 +86,17 @@ export type PopoverClickTargetHandlers extends OverlayableProps, Props {
+export interface PopoverSharedProps
+ extends OverlayableProps,
+ Props {
/** Interactive element which will trigger the popover. */
children?: React.ReactNode;
+ /**
+ * The content displayed inside the popover.
+ */
+ content?: string | React.JSX.Element;
+
/**
* A boundary element supplied to the "flip" and "preventOverflow" modifiers.
* This is a shorthand for overriding Popper.js modifier options with the `modifiers` prop.
@@ -238,7 +245,10 @@ export interface PopoverSharedProps. Instead of discriminating, we union the different possible event handlers
// that may be passed (they are all optional properties anyway).
- props: PopoverTargetProps & PopoverHoverTargetHandlers & PopoverClickTargetHandlers,
+ props: PopoverTargetProps &
+ PopoverHoverTargetHandlers &
+ PopoverClickTargetHandlers &
+ AdditionalTargetRendererProps,
) => React.JSX.Element;
/**
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 4a09e4285d..a34a751d22 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -17,8 +17,9 @@
import classNames from "classnames";
import * as React from "react";
-import { AbstractPureComponent, DISPLAYNAME_PREFIX, type IntentProps } from "../../common";
+import { AbstractPureComponent, DISPLAYNAME_PREFIX, type IntentProps, Utils } from "../../common";
import * as Classes from "../../common/classes";
+import * as Errors from "../../common/errors";
// eslint-disable-next-line import/no-cycle
import { Popover, type PopoverInteractionKind } from "../popover/popover";
import { TOOLTIP_ARROW_SVG_SIZE } from "../popover/popoverArrow";
@@ -26,13 +27,8 @@ import type { DefaultPopoverTargetHTMLProps, PopoverSharedProps } from "../popov
import { TooltipContext, type TooltipContextState, TooltipProvider } from "../popover/tooltipContext";
export interface TooltipProps
- extends Omit, "shouldReturnFocusOnClose">,
+ extends Omit, "shouldReturnFocusOnClose">,
IntentProps {
- /**
- * The content that will be displayed inside of the tooltip.
- */
- content: React.JSX.Element | string;
-
/**
* Whether to use a compact appearance, which reduces the visual padding around
* tooltip content.
@@ -42,19 +38,11 @@ export interface TooltipProps) {
+ const childrenCount = React.Children.count(props.children);
+ if (childrenCount > 1) {
+ console.warn(Errors.POPOVER_WARN_TOO_MANY_CHILDREN);
+ }
+ // all other warnings should occur in Popover, not here.
+ }
+
// any descendant ContextMenus may update this ctxState
private renderPopover = (ctxState: TooltipContextState) => {
- const { children, compact, disabled, intent, popoverClassName, ...restProps } = this.props;
+ const { children, content, renderTarget, compact, disabled, intent, popoverClassName, ...restProps } =
+ this.props;
+
const popoverClasses = classNames(Classes.TOOLTIP, Classes.intentClass(intent), popoverClassName, {
[Classes.COMPACT]: compact,
});
+ const tooltipId = Utils.uniqueId("tooltip");
+
+ const childTarget = Utils.ensureElement(React.Children.toArray(children)[0], undefined, {
+ "aria-describedby": tooltipId,
+ });
+
return (
renderTarget({ ...props, tooltipId }) : undefined}
+ content={Utils.ensureElement(content, undefined, { role: "tooltip", id: tooltipId })}
autoFocus={false}
canEscapeKeyClose={false}
disabled={ctxState.forceDisabled ?? disabled}
@@ -143,7 +143,7 @@ export class Tooltip<
portalContainer={this.props.portalContainer}
ref={this.popoverRef}
>
- {children}
+ {childTarget}
);
};
diff --git a/packages/core/test/popover/popoverTests.tsx b/packages/core/test/popover/popoverTests.tsx
index 4061178f55..ad5d9bf3e8 100644
--- a/packages/core/test/popover/popoverTests.tsx
+++ b/packages/core/test/popover/popoverTests.tsx
@@ -78,7 +78,7 @@ describe("", () => {
assert.isTrue(warnSpy.calledWith(Errors.POPOVER_WARN_TOO_MANY_CHILDREN));
});
- it("warns if given children and target prop", () => {
+ it("warns if given children and renderTarget prop", () => {
shallow( "boom"}>pow);
assert.isTrue(warnSpy.calledWith(Errors.POPOVER_WARN_DOUBLE_TARGET));
});
diff --git a/packages/core/test/tooltip/tooltipTests.tsx b/packages/core/test/tooltip/tooltipTests.tsx
index 64ed778c73..83f2364ad1 100644
--- a/packages/core/test/tooltip/tooltipTests.tsx
+++ b/packages/core/test/tooltip/tooltipTests.tsx
@@ -21,14 +21,64 @@ import { spy, stub } from "sinon";
import { Classes } from "../../src/common";
import { Button, Overlay2 } from "../../src/components";
+import * as Errors from "../../src/common/errors";
import { Popover } from "../../src/components/popover/popover";
import { Tooltip, type TooltipProps } from "../../src/components/tooltip/tooltip";
const TARGET_SELECTOR = `.${Classes.POPOVER_TARGET}`;
const TOOLTIP_SELECTOR = `.${Classes.TOOLTIP}`;
-const TEST_TARGET_ID = "test-target";
describe("", () => {
+ describe("validation", () => {
+ let warnSpy: sinon.SinonStub;
+
+ // use sinon.stub to prevent warnings from appearing in the test logs
+ before(() => (warnSpy = stub(console, "warn")));
+ beforeEach(() => warnSpy.resetHistory());
+ after(() => warnSpy.restore());
+
+ it("throws error if given no target", () => {
+ mount(Text
} usePortal={false} />);
+ assert.isTrue(warnSpy.calledWith(Errors.POPOVER_REQUIRES_TARGET));
+ });
+
+ it("warns if given > 1 target elements", () => {
+ mount(
+
+
+
+ ,
+ );
+ assert.isTrue(warnSpy.calledWith(Errors.POPOVER_WARN_TOO_MANY_CHILDREN));
+ });
+
+ it("warns if given children and renderTarget prop", () => {
+ mount( "boom"}>pow);
+ assert.isTrue(warnSpy.calledWith(Errors.POPOVER_WARN_DOUBLE_TARGET));
+ });
+
+ it("warns if given targetProps and renderTarget", () => {
+ mount( "boom"} />);
+ assert.isTrue(warnSpy.calledWith(Errors.POPOVER_WARN_TARGET_PROPS_WITH_RENDER_TARGET));
+ });
+
+ it("warns if attempting to open with empty content", () => {
+ renderTooltip({ content: undefined, isOpen: true });
+ assert.isTrue(warnSpy.calledWith(Errors.POPOVER_WARN_EMPTY_CONTENT));
+ });
+
+ it("warns and disables if given empty content", () => {
+ const tooltip = renderTooltip({ content: undefined, isOpen: true });
+
+ assert.isFalse(tooltip.find(Overlay2).exists(), "not open for undefined content");
+ assert.equal(warnSpy.callCount, 1);
+
+ tooltip.setProps({ content: " " });
+ assert.isFalse(tooltip.find(Overlay2).exists(), "not open for white-space string content");
+ assert.equal(warnSpy.callCount, 2);
+ });
+ });
+
describe("rendering", () => {
it("propogates class names correctly", () => {
const tooltip = renderTooltip({
@@ -159,7 +209,7 @@ describe("", () => {
function renderTooltip(props?: Partial) {
return mount(
Text} hoverOpenDelay={0} {...props} usePortal={false}>
-
+
,
);
}
From a007008e723c29ad62c6218d583cb50b2b609335 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 21:23:54 -0600
Subject: [PATCH 02/32] chore: add dep
---
packages/core/package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/core/package.json b/packages/core/package.json
index b79c1b75d7..3d46df07df 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -54,6 +54,7 @@
"@popperjs/core": "^2.11.8",
"classnames": "^2.3.1",
"normalize.css": "^8.0.1",
+ "react-innertext": "^1.1.5",
"react-popper": "^2.3.0",
"react-transition-group": "^4.4.5",
"react-uid": "^2.3.3",
From 8ef625b1b276d82de0bfecf55deed054affba82a Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 21:30:33 -0600
Subject: [PATCH 03/32] lint fixes
---
packages/core/src/components/popover/popover.tsx | 5 +++--
packages/core/src/components/popover/popoverSharedProps.ts | 7 +++++--
packages/core/src/components/tooltip/tooltip.tsx | 6 ++++--
packages/core/test/tooltip/tooltipTests.tsx | 2 +-
4 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index 16f3e8dba0..67dc0cee3f 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -17,6 +17,7 @@
import type { State as PopperState, PositioningStrategy } from "@popperjs/core";
import classNames from "classnames";
import * as React from "react";
+import innerText from "react-innertext";
import {
Manager,
type Modifier,
@@ -25,7 +26,6 @@ import {
Reference,
type ReferenceChildrenProps,
} from "react-popper";
-import innerText from "react-innertext";
import {
AbstractPureComponent,
@@ -41,6 +41,7 @@ import { Overlay2 } from "../overlay2/overlay2";
import { ResizeSensor } from "../resize-sensor/resizeSensor";
// eslint-disable-next-line import/no-cycle
import { Tooltip } from "../tooltip/tooltip";
+
import { matchReferenceWidthModifier } from "./customModifiers";
import { POPOVER_ARROW_SVG_SIZE, PopoverArrow } from "./popoverArrow";
import { positionToPlacement } from "./popoverPlacementUtils";
@@ -247,7 +248,7 @@ export class Popover<
const { disabled, content, placement, position = "auto", positioningStrategy } = this.props;
const { isOpen } = this.state;
- const isContentEmpty = content == null || innerText(content).trim() == "";
+ const isContentEmpty = content == null || innerText(content).trim() === "";
if (isContentEmpty) {
// need to do this check in render(), because `isOpen` is derived from
// state, and state can't necessarily be accessed in validateProps.
diff --git a/packages/core/src/components/popover/popoverSharedProps.ts b/packages/core/src/components/popover/popoverSharedProps.ts
index e471082c92..cdc7eb9e17 100644
--- a/packages/core/src/components/popover/popoverSharedProps.ts
+++ b/packages/core/src/components/popover/popoverSharedProps.ts
@@ -86,8 +86,11 @@ export type PopoverClickTargetHandlers
- extends OverlayableProps,
+export interface PopoverSharedProps<
+ TProps extends DefaultPopoverTargetHTMLProps,
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ AdditionalTargetRendererProps = {},
+> extends OverlayableProps,
Props {
/** Interactive element which will trigger the popover. */
children?: React.ReactNode;
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index a34a751d22..7e82c81dca 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -21,7 +21,7 @@ import { AbstractPureComponent, DISPLAYNAME_PREFIX, type IntentProps, Utils } fr
import * as Classes from "../../common/classes";
import * as Errors from "../../common/errors";
// eslint-disable-next-line import/no-cycle
-import { Popover, type PopoverInteractionKind } from "../popover/popover";
+import { Popover, type PopoverInteractionKind, type PopoverProps } from "../popover/popover";
import { TOOLTIP_ARROW_SVG_SIZE } from "../popover/popoverArrow";
import type { DefaultPopoverTargetHTMLProps, PopoverSharedProps } from "../popover/popoverSharedProps";
import { TooltipContext, type TooltipContextState, TooltipProvider } from "../popover/tooltipContext";
@@ -119,6 +119,8 @@ export class Tooltip<
"aria-describedby": tooltipId,
});
+ const maybeRenderTarget:PopoverProps['renderTarget'] =renderTarget? (props => renderTarget({ ...props, tooltipId })) : undefined
+
return (
renderTarget({ ...props, tooltipId }) : undefined}
+ renderTarget={maybeRenderTarget}
content={Utils.ensureElement(content, undefined, { role: "tooltip", id: tooltipId })}
autoFocus={false}
canEscapeKeyClose={false}
diff --git a/packages/core/test/tooltip/tooltipTests.tsx b/packages/core/test/tooltip/tooltipTests.tsx
index 83f2364ad1..1a281a8537 100644
--- a/packages/core/test/tooltip/tooltipTests.tsx
+++ b/packages/core/test/tooltip/tooltipTests.tsx
@@ -20,8 +20,8 @@ import * as React from "react";
import { spy, stub } from "sinon";
import { Classes } from "../../src/common";
-import { Button, Overlay2 } from "../../src/components";
import * as Errors from "../../src/common/errors";
+import { Button, Overlay2 } from "../../src/components";
import { Popover } from "../../src/components/popover/popover";
import { Tooltip, type TooltipProps } from "../../src/components/tooltip/tooltip";
From d5b5e015c4f14d6e6885b6d75d3a064fbde30085 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 21:30:57 -0600
Subject: [PATCH 04/32] lint fixes
---
packages/core/src/components/tooltip/tooltip.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 7e82c81dca..faae026c28 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -119,7 +119,9 @@ export class Tooltip<
"aria-describedby": tooltipId,
});
- const maybeRenderTarget:PopoverProps['renderTarget'] =renderTarget? (props => renderTarget({ ...props, tooltipId })) : undefined
+ const maybeRenderTarget: PopoverProps["renderTarget"] = renderTarget
+ ? props => renderTarget({ ...props, tooltipId })
+ : undefined;
return (
Date: Mon, 24 Jun 2024 21:36:27 -0600
Subject: [PATCH 05/32] chore: yarn.lock
---
yarn.lock | 1 +
1 file changed, 1 insertion(+)
diff --git a/yarn.lock b/yarn.lock
index de45637926..ef0d129364 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -464,6 +464,7 @@ __metadata:
npm-run-all: "npm:^4.1.5"
react: "npm:^16.14.0"
react-dom: "npm:^16.14.0"
+ react-innertext: "npm:^1.1.5"
react-popper: "npm:^2.3.0"
react-test-renderer: "npm:^16.14.0"
react-transition-group: "npm:^4.4.5"
From 8f47c5d013e070504eade3d7f3aef53488c50523 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 21:47:22 -0600
Subject: [PATCH 06/32] none if empty content
---
packages/core/src/components/popover/popover.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index 67dc0cee3f..f09fd2a342 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -248,7 +248,7 @@ export class Popover<
const { disabled, content, placement, position = "auto", positioningStrategy } = this.props;
const { isOpen } = this.state;
- const isContentEmpty = content == null || innerText(content).trim() === "";
+ const isContentEmpty = innerText(content).trim() === "";
if (isContentEmpty) {
// need to do this check in render(), because `isOpen` is derived from
// state, and state can't necessarily be accessed in validateProps.
From 8a7ede2b73634e12213cb5be098480f348bfc79f Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 22:03:12 -0600
Subject: [PATCH 07/32] style: lint fix
---
packages/core/src/components/tooltip/tooltip.tsx | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index faae026c28..cbb23bed47 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -21,7 +21,7 @@ import { AbstractPureComponent, DISPLAYNAME_PREFIX, type IntentProps, Utils } fr
import * as Classes from "../../common/classes";
import * as Errors from "../../common/errors";
// eslint-disable-next-line import/no-cycle
-import { Popover, type PopoverInteractionKind, type PopoverProps } from "../popover/popover";
+import { Popover, type PopoverInteractionKind } from "../popover/popover";
import { TOOLTIP_ARROW_SVG_SIZE } from "../popover/popoverArrow";
import type { DefaultPopoverTargetHTMLProps, PopoverSharedProps } from "../popover/popoverSharedProps";
import { TooltipContext, type TooltipContextState, TooltipProvider } from "../popover/tooltipContext";
@@ -119,10 +119,6 @@ export class Tooltip<
"aria-describedby": tooltipId,
});
- const maybeRenderTarget: PopoverProps["renderTarget"] = renderTarget
- ? props => renderTarget({ ...props, tooltipId })
- : undefined;
-
return (
renderTarget({ ...props, tooltipId }) : undefined}
content={Utils.ensureElement(content, undefined, { role: "tooltip", id: tooltipId })}
autoFocus={false}
canEscapeKeyClose={false}
From bdcb9a8c2c53d272371571dd6e74e572b5df6e34 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 22:10:55 -0600
Subject: [PATCH 08/32] no innerText check
---
packages/core/package.json | 1 -
packages/core/src/common/utils/reactUtils.ts | 2 +-
packages/core/src/components/popover/popover.tsx | 4 +---
packages/core/src/components/tooltip/tooltip.tsx | 7 ++++++-
yarn.lock | 1 -
5 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/packages/core/package.json b/packages/core/package.json
index 3d46df07df..b79c1b75d7 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -54,7 +54,6 @@
"@popperjs/core": "^2.11.8",
"classnames": "^2.3.1",
"normalize.css": "^8.0.1",
- "react-innertext": "^1.1.5",
"react-popper": "^2.3.0",
"react-transition-group": "^4.4.5",
"react-uid": "^2.3.3",
diff --git a/packages/core/src/common/utils/reactUtils.ts b/packages/core/src/common/utils/reactUtils.ts
index 661686da38..2fc9188ecf 100644
--- a/packages/core/src/common/utils/reactUtils.ts
+++ b/packages/core/src/common/utils/reactUtils.ts
@@ -26,8 +26,8 @@ import { isEmptyString } from "./jsUtils";
export function isReactNodeEmpty(node?: React.ReactNode, skipArray = false): boolean {
return (
node == null ||
- node === "" ||
node === false ||
+ isEmptyString(node) ||
(!skipArray &&
Array.isArray(node) &&
// only recurse one level through arrays, for performance
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index f09fd2a342..911ae71923 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -17,7 +17,6 @@
import type { State as PopperState, PositioningStrategy } from "@popperjs/core";
import classNames from "classnames";
import * as React from "react";
-import innerText from "react-innertext";
import {
Manager,
type Modifier,
@@ -248,8 +247,7 @@ export class Popover<
const { disabled, content, placement, position = "auto", positioningStrategy } = this.props;
const { isOpen } = this.state;
- const isContentEmpty = innerText(content).trim() === "";
- if (isContentEmpty) {
+ if (Utils.isReactNodeEmpty(content)) {
// need to do this check in render(), because `isOpen` is derived from
// state, and state can't necessarily be accessed in validateProps.
if (!disabled && isOpen !== false && !Utils.isNodeEnv("production")) {
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index cbb23bed47..cc51812294 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -134,7 +134,12 @@ export class Tooltip<
{...restProps}
// eslint-disable-next-line react/jsx-no-bind
renderTarget={renderTarget ? props => renderTarget({ ...props, tooltipId }) : undefined}
- content={Utils.ensureElement(content, undefined, { role: "tooltip", id: tooltipId })}
+ content={
+ // want Popover to warn if empty, so don't wrap in element if empty.
+ Utils.isReactNodeEmpty(content)
+ ? content
+ : Utils.ensureElement(content, undefined, { role: "tooltip", id: tooltipId })
+ }
autoFocus={false}
canEscapeKeyClose={false}
disabled={ctxState.forceDisabled ?? disabled}
diff --git a/yarn.lock b/yarn.lock
index ef0d129364..de45637926 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -464,7 +464,6 @@ __metadata:
npm-run-all: "npm:^4.1.5"
react: "npm:^16.14.0"
react-dom: "npm:^16.14.0"
- react-innertext: "npm:^1.1.5"
react-popper: "npm:^2.3.0"
react-test-renderer: "npm:^16.14.0"
react-transition-group: "npm:^4.4.5"
From 1345c97cd966fcedf9461369cbcffc9c44ade486 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 22:20:46 -0600
Subject: [PATCH 09/32] fix test
---
packages/core/test/tooltip/tooltipTests.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/core/test/tooltip/tooltipTests.tsx b/packages/core/test/tooltip/tooltipTests.tsx
index 1a281a8537..42cf08cf2b 100644
--- a/packages/core/test/tooltip/tooltipTests.tsx
+++ b/packages/core/test/tooltip/tooltipTests.tsx
@@ -71,11 +71,11 @@ describe("", () => {
const tooltip = renderTooltip({ content: undefined, isOpen: true });
assert.isFalse(tooltip.find(Overlay2).exists(), "not open for undefined content");
- assert.equal(warnSpy.callCount, 1);
+ assert.equal(warnSpy.callCount, 2);
tooltip.setProps({ content: " " });
assert.isFalse(tooltip.find(Overlay2).exists(), "not open for white-space string content");
- assert.equal(warnSpy.callCount, 2);
+ assert.equal(warnSpy.callCount, 3);
});
});
From 73a0519a959da1a9485419f73d0a203b3a5f9998 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 24 Jun 2024 22:47:16 -0600
Subject: [PATCH 10/32] add changelog entry
---
packages/core/changelog/@unreleased/pr-6865.v2.yml | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 packages/core/changelog/@unreleased/pr-6865.v2.yml
diff --git a/packages/core/changelog/@unreleased/pr-6865.v2.yml b/packages/core/changelog/@unreleased/pr-6865.v2.yml
new file mode 100644
index 0000000000..11de083ef9
--- /dev/null
+++ b/packages/core/changelog/@unreleased/pr-6865.v2.yml
@@ -0,0 +1,5 @@
+type: improvement
+fix:
+ description: '[core] a11y(Tooltip): wrap contents in "tooltip" aria role'
+ links:
+ - https://github.com/palantir/blueprint/pull/6865
From 8c4438208669d82912fa3c952c1e3fe31ddbdf73 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Wed, 26 Jun 2024 12:44:45 -0600
Subject: [PATCH 11/32] revert change to Utils.isReactNodeEmpty
---
packages/core/src/common/utils/reactUtils.ts | 2 +-
packages/core/src/components/popover/popover.tsx | 4 +++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/core/src/common/utils/reactUtils.ts b/packages/core/src/common/utils/reactUtils.ts
index 2fc9188ecf..661686da38 100644
--- a/packages/core/src/common/utils/reactUtils.ts
+++ b/packages/core/src/common/utils/reactUtils.ts
@@ -26,8 +26,8 @@ import { isEmptyString } from "./jsUtils";
export function isReactNodeEmpty(node?: React.ReactNode, skipArray = false): boolean {
return (
node == null ||
+ node === "" ||
node === false ||
- isEmptyString(node) ||
(!skipArray &&
Array.isArray(node) &&
// only recurse one level through arrays, for performance
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index 911ae71923..24b960c45d 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -247,7 +247,9 @@ export class Popover<
const { disabled, content, placement, position = "auto", positioningStrategy } = this.props;
const { isOpen } = this.state;
- if (Utils.isReactNodeEmpty(content)) {
+ const isContentEmpty =
+ Utils.isReactNodeEmpty(content) || (typeof content === "string" && content.trim() === "");
+ if (isContentEmpty) {
// need to do this check in render(), because `isOpen` is derived from
// state, and state can't necessarily be accessed in validateProps.
if (!disabled && isOpen !== false && !Utils.isNodeEnv("production")) {
From 730d177bfaa936d6ef973f7fb0e83757e7b889ce Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Wed, 26 Jun 2024 12:52:11 -0600
Subject: [PATCH 12/32] fix: aria-describedby
---
packages/core/src/components/tooltip/tooltip.tsx | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index cc51812294..d8a1c06d6c 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -115,8 +115,14 @@ export class Tooltip<
const tooltipId = Utils.uniqueId("tooltip");
- const childTarget = Utils.ensureElement(React.Children.toArray(children)[0], undefined, {
- "aria-describedby": tooltipId,
+ const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
+
+ if (childTarget === undefined) {
+ return null;
+ }
+
+ const clonedTarget: React.JSX.Element = React.cloneElement(childTarget, {
+ "aria-describedby": [tooltipId, childTarget.props["aria-describedby"]].filter(Boolean).join(" "),
});
return (
@@ -149,7 +155,7 @@ export class Tooltip<
portalContainer={this.props.portalContainer}
ref={this.popoverRef}
>
- {childTarget}
+ {clonedTarget}
);
};
From 8851895863543b47d9f3c4ed56a7a34efe49c5f2 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Wed, 26 Jun 2024 13:30:04 -0600
Subject: [PATCH 13/32] fix children for popover warning
---
packages/core/src/components/tooltip/tooltip.tsx | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index d8a1c06d6c..ea49ff3b3d 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -116,14 +116,11 @@ export class Tooltip<
const tooltipId = Utils.uniqueId("tooltip");
const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
-
- if (childTarget === undefined) {
- return null;
- }
-
- const clonedTarget: React.JSX.Element = React.cloneElement(childTarget, {
- "aria-describedby": [tooltipId, childTarget.props["aria-describedby"]].filter(Boolean).join(" "),
- });
+ const clonedTarget = childTarget
+ ? React.cloneElement(childTarget, {
+ "aria-describedby": [tooltipId, childTarget.props["aria-describedby"]].filter(Boolean).join(" "),
+ })
+ : childTarget;
return (
Date: Wed, 26 Jun 2024 14:20:43 -0600
Subject: [PATCH 14/32] re-add jsdocs
---
packages/core/src/components/tooltip/tooltip.tsx | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index ea49ff3b3d..e04b42352d 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -38,11 +38,19 @@ export interface TooltipProps
Date: Wed, 26 Jun 2024 15:12:01 -0600
Subject: [PATCH 15/32] style: re-add jsdocs
---
packages/core/src/components/popover/popover.tsx | 5 +++++
packages/core/src/components/tooltip/tooltip.tsx | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index 24b960c45d..fb559dc700 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -74,6 +74,11 @@ export interface PopoverProps;
+ /**
+ * The content displayed inside the popover.
+ */
+ content?: string | React.JSX.Element;
+
/**
* The kind of interaction that triggers the display of the popover.
*
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index e04b42352d..d376c247c0 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -29,6 +29,11 @@ import { TooltipContext, type TooltipContextState, TooltipProvider } from "../po
export interface TooltipProps
extends Omit, "shouldReturnFocusOnClose">,
IntentProps {
+ /**
+ * The content that will be displayed inside of the tooltip.
+ */
+ content: React.JSX.Element | string;
+
/**
* Whether to use a compact appearance, which reduces the visual padding around
* tooltip content.
From 8d19874eeaefc695520781607c194f94a54654b2 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Wed, 26 Jun 2024 15:14:05 -0600
Subject: [PATCH 16/32] duplicate type
---
packages/core/src/components/popover/popover.tsx | 5 -----
1 file changed, 5 deletions(-)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index fb559dc700..24b960c45d 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -74,11 +74,6 @@ export interface PopoverProps;
- /**
- * The content displayed inside the popover.
- */
- content?: string | React.JSX.Element;
-
/**
* The kind of interaction that triggers the display of the popover.
*
From 1d5a83a4df5d7fa0501c54ed52fcdcdd57c80467 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Wed, 26 Jun 2024 15:18:37 -0600
Subject: [PATCH 17/32] fix: isContentEmpty
---
packages/core/src/components/popover/popover.tsx | 8 +++++---
packages/core/src/components/tooltip/tooltip.tsx | 4 ++--
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index 24b960c45d..f773caf5d5 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -133,6 +133,10 @@ export interface PopoverState {
isOpen: boolean;
}
+export function isContentEmpty(content: PopoverProps["content"]) {
+ return Utils.isReactNodeEmpty(content) || (typeof content === "string" && content.trim() === "");
+}
+
/**
* Popover component, used to display a floating UI next to and tethered to a target element.
*
@@ -247,9 +251,7 @@ export class Popover<
const { disabled, content, placement, position = "auto", positioningStrategy } = this.props;
const { isOpen } = this.state;
- const isContentEmpty =
- Utils.isReactNodeEmpty(content) || (typeof content === "string" && content.trim() === "");
- if (isContentEmpty) {
+ if (isContentEmpty(content)) {
// need to do this check in render(), because `isOpen` is derived from
// state, and state can't necessarily be accessed in validateProps.
if (!disabled && isOpen !== false && !Utils.isNodeEnv("production")) {
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index d376c247c0..0ceac2698b 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -21,7 +21,7 @@ import { AbstractPureComponent, DISPLAYNAME_PREFIX, type IntentProps, Utils } fr
import * as Classes from "../../common/classes";
import * as Errors from "../../common/errors";
// eslint-disable-next-line import/no-cycle
-import { Popover, type PopoverInteractionKind } from "../popover/popover";
+import { isContentEmpty, Popover, type PopoverInteractionKind } from "../popover/popover";
import { TOOLTIP_ARROW_SVG_SIZE } from "../popover/popoverArrow";
import type { DefaultPopoverTargetHTMLProps, PopoverSharedProps } from "../popover/popoverSharedProps";
import { TooltipContext, type TooltipContextState, TooltipProvider } from "../popover/tooltipContext";
@@ -158,7 +158,7 @@ export class Tooltip<
renderTarget={renderTarget ? props => renderTarget({ ...props, tooltipId }) : undefined}
content={
// want Popover to warn if empty, so don't wrap in element if empty.
- Utils.isReactNodeEmpty(content)
+ isContentEmpty(content)
? content
: Utils.ensureElement(content, undefined, { role: "tooltip", id: tooltipId })
}
From be12eb309882e8251c8d6e81a170c20f9904cfb0 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Wed, 26 Jun 2024 15:34:20 -0600
Subject: [PATCH 18/32] refactor to allow for passed content with ID
---
packages/core/src/components/tooltip/tooltip.tsx | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 0ceac2698b..cda8a9a2e1 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -132,7 +132,11 @@ export class Tooltip<
[Classes.COMPACT]: compact,
});
- const tooltipId = Utils.uniqueId("tooltip");
+ const contentElement = Utils.ensureElement(content);
+ const tooltipId = contentElement?.props?.id ?? Utils.uniqueId("tooltip");
+ const clonedContent = contentElement
+ ? React.cloneElement(contentElement, { role: "tooltip", id: tooltipId })
+ : contentElement;
const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
const clonedTarget = childTarget
@@ -157,10 +161,8 @@ export class Tooltip<
// eslint-disable-next-line react/jsx-no-bind
renderTarget={renderTarget ? props => renderTarget({ ...props, tooltipId }) : undefined}
content={
- // want Popover to warn if empty, so don't wrap in element if empty.
- isContentEmpty(content)
- ? content
- : Utils.ensureElement(content, undefined, { role: "tooltip", id: tooltipId })
+ // want Popover to warn if empty, so don't provide the element if so.
+ isContentEmpty(content) ? content : clonedContent
}
autoFocus={false}
canEscapeKeyClose={false}
From 720a1b2bc3a5c5c71fe4cd46fd4710a62a1e8c52 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 1 Jul 2024 10:55:39 -0600
Subject: [PATCH 19/32] use useMemo
---
packages/core/src/components/tooltip/tooltip.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index cda8a9a2e1..1d4846cca4 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -133,7 +133,7 @@ export class Tooltip<
});
const contentElement = Utils.ensureElement(content);
- const tooltipId = contentElement?.props?.id ?? Utils.uniqueId("tooltip");
+ const tooltipId = contentElement?.props?.id ?? React.useMemo(() => Utils.uniqueId("tooltip"), []);
const clonedContent = contentElement
? React.cloneElement(contentElement, { role: "tooltip", id: tooltipId })
: contentElement;
From 3b062f15065a5dd59e15ba3565f4df8525de3ee5 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 1 Jul 2024 11:02:30 -0600
Subject: [PATCH 20/32] useCallback
---
packages/core/src/components/tooltip/tooltip.tsx | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 1d4846cca4..e43d6db81b 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -158,8 +158,11 @@ export class Tooltip<
},
}}
{...restProps}
- // eslint-disable-next-line react/jsx-no-bind
- renderTarget={renderTarget ? props => renderTarget({ ...props, tooltipId }) : undefined}
+ renderTarget={
+ renderTarget
+ ? React.useCallback(props => renderTarget({ ...props, tooltipId }), [renderTarget, tooltipId])
+ : undefined
+ }
content={
// want Popover to warn if empty, so don't provide the element if so.
isContentEmpty(content) ? content : clonedContent
From 05113cbe6229d2c885854afd0745b8ed148bdaff Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 1 Jul 2024 11:09:41 -0600
Subject: [PATCH 21/32] Utils.uniq
---
packages/core/src/common/utils/jsUtils.ts | 5 +++++
packages/core/src/components/tooltip/tooltip.tsx | 4 +++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/common/utils/jsUtils.ts b/packages/core/src/common/utils/jsUtils.ts
index b8cba3e827..6129754692 100644
--- a/packages/core/src/common/utils/jsUtils.ts
+++ b/packages/core/src/common/utils/jsUtils.ts
@@ -25,6 +25,11 @@ export function isNodeEnv(env: string) {
return typeof NODE_ENV !== "undefined" && NODE_ENV === env;
}
+/** Poor man's lodash.uniq without the dep. */
+export function uniq(arr: T[]) {
+ return Array.from(new Set(arr)).sort();
+}
+
/**
* Returns the difference in length between two arrays. A `null` argument is
* considered an empty list. The return value will be positive if `a` is longer
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index e43d6db81b..dff397522c 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -141,7 +141,9 @@ export class Tooltip<
const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
const clonedTarget = childTarget
? React.cloneElement(childTarget, {
- "aria-describedby": [tooltipId, childTarget.props["aria-describedby"]].filter(Boolean).join(" "),
+ "aria-describedby": Utils.uniq(
+ [tooltipId, childTarget.props["aria-describedby"]].filter(Boolean),
+ ).join(" "),
})
: childTarget;
From be533a0f056508aa04fbc46bf71dd3f797de080f Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 1 Jul 2024 12:47:19 -0600
Subject: [PATCH 22/32] refactor:: other describedby
---
packages/core/src/components/tooltip/tooltip.tsx | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index dff397522c..9d0e1a52c9 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -141,9 +141,7 @@ export class Tooltip<
const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
const clonedTarget = childTarget
? React.cloneElement(childTarget, {
- "aria-describedby": Utils.uniq(
- [tooltipId, childTarget.props["aria-describedby"]].filter(Boolean),
- ).join(" "),
+ "aria-describedby": childTarget.props["aria-describedby"] ?? tooltipId,
})
: childTarget;
From 2acbda819756d1e4d91019586b4f5aeb9ca082de Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 1 Jul 2024 12:48:50 -0600
Subject: [PATCH 23/32] remove uniq
---
packages/core/src/common/utils/jsUtils.ts | 5 -----
1 file changed, 5 deletions(-)
diff --git a/packages/core/src/common/utils/jsUtils.ts b/packages/core/src/common/utils/jsUtils.ts
index 6129754692..b8cba3e827 100644
--- a/packages/core/src/common/utils/jsUtils.ts
+++ b/packages/core/src/common/utils/jsUtils.ts
@@ -25,11 +25,6 @@ export function isNodeEnv(env: string) {
return typeof NODE_ENV !== "undefined" && NODE_ENV === env;
}
-/** Poor man's lodash.uniq without the dep. */
-export function uniq(arr: T[]) {
- return Array.from(new Set(arr)).sort();
-}
-
/**
* Returns the difference in length between two arrays. A `null` argument is
* considered an empty list. The return value will be positive if `a` is longer
From 31a7fdc6798e9a872a2bfedc8b7a7fc583ebf6e9 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Mon, 1 Jul 2024 15:20:08 -0600
Subject: [PATCH 24/32] style: use Utils.isEmptyString
---
packages/core/src/components/popover/popover.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index 0bed191ff3..d165defc59 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -134,7 +134,7 @@ export interface PopoverState {
}
export function isContentEmpty(content: PopoverProps["content"]) {
- return Utils.isReactNodeEmpty(content) || (typeof content === "string" && content.trim() === "");
+ return Utils.isReactNodeEmpty(content) || Utils.isEmptyString(content);
}
/**
From 1d5157e2fc9e005b0d46e57ef2448a5c3805d259 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Thu, 8 Aug 2024 13:13:16 -0600
Subject: [PATCH 25/32] fix merge
---
packages/core/src/components/popover/popover.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
index d165defc59..abb735bf70 100644
--- a/packages/core/src/components/popover/popover.tsx
+++ b/packages/core/src/components/popover/popover.tsx
@@ -134,7 +134,7 @@ export interface PopoverState {
}
export function isContentEmpty(content: PopoverProps["content"]) {
- return Utils.isReactNodeEmpty(content) || Utils.isEmptyString(content);
+ return content == null || Utils.isEmptyString(content);
}
/**
From 7ff1a3bddbadb88c1f28f0c2b1e1ee80ddc18867 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Sun, 11 Aug 2024 21:49:19 -0600
Subject: [PATCH 26/32] use content role if passed
---
packages/core/src/components/tooltip/tooltip.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 9d0e1a52c9..6236776cba 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -134,8 +134,9 @@ export class Tooltip<
const contentElement = Utils.ensureElement(content);
const tooltipId = contentElement?.props?.id ?? React.useMemo(() => Utils.uniqueId("tooltip"), []);
+ const tooltipRole = contentElement?.props?.role ?? "tooltip"
const clonedContent = contentElement
- ? React.cloneElement(contentElement, { role: "tooltip", id: tooltipId })
+ ? React.cloneElement(contentElement, { role: tooltipRole, id: tooltipId })
: contentElement;
const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
From cfd20f6cb82091cf8a7829910f604f183cb9fb31 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Sun, 11 Aug 2024 21:55:29 -0600
Subject: [PATCH 27/32] reformat
---
packages/core/src/components/tooltip/tooltip.tsx | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 6236776cba..33fbd5b87d 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -134,17 +134,14 @@ export class Tooltip<
const contentElement = Utils.ensureElement(content);
const tooltipId = contentElement?.props?.id ?? React.useMemo(() => Utils.uniqueId("tooltip"), []);
- const tooltipRole = contentElement?.props?.role ?? "tooltip"
- const clonedContent = contentElement
- ? React.cloneElement(contentElement, { role: tooltipRole, id: tooltipId })
- : contentElement;
+ const tooltipRole = contentElement?.props?.role ?? "tooltip";
+ const clonedContent =
+ contentElement && React.cloneElement(contentElement, { role: tooltipRole, id: tooltipId });
const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
- const clonedTarget = childTarget
- ? React.cloneElement(childTarget, {
- "aria-describedby": childTarget.props["aria-describedby"] ?? tooltipId,
- })
- : childTarget;
+ const clonedTarget =
+ childTarget &&
+ React.cloneElement(childTarget, { "aria-describedby": childTarget.props["aria-describedby"] ?? tooltipId });
return (
Date: Sun, 11 Aug 2024 21:59:21 -0600
Subject: [PATCH 28/32] dont use ternary
---
packages/core/src/components/tooltip/tooltip.tsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 33fbd5b87d..653894b538 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -157,9 +157,8 @@ export class Tooltip<
}}
{...restProps}
renderTarget={
- renderTarget
- ? React.useCallback(props => renderTarget({ ...props, tooltipId }), [renderTarget, tooltipId])
- : undefined
+ renderTarget &&
+ React.useCallback(props => renderTarget({ ...props, tooltipId }), [renderTarget, tooltipId])
}
content={
// want Popover to warn if empty, so don't provide the element if so.
From 76f22f9746fc6975036c490d394f77e35f9d6a92 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Sun, 11 Aug 2024 22:04:34 -0600
Subject: [PATCH 29/32] refactor ts types
---
.../components/popover/popoverSharedProps.ts | 18 ++++++++----------
.../core/src/components/tooltip/tooltip.tsx | 16 ++++++++++++++--
2 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/packages/core/src/components/popover/popoverSharedProps.ts b/packages/core/src/components/popover/popoverSharedProps.ts
index cdc7eb9e17..a69c3a2e36 100644
--- a/packages/core/src/components/popover/popoverSharedProps.ts
+++ b/packages/core/src/components/popover/popoverSharedProps.ts
@@ -80,18 +80,19 @@ export type PopoverHoverTargetHandlers =
Pick;
+/**
+ * Arguments for the `renderTarget` prop.
+ */
+export type PopoverRenderTargetArgs =
+ PopoverTargetProps & PopoverHoverTargetHandlers & PopoverClickTargetHandlers;
+
/**
* Props shared between `Popover` and `Tooltip`.
*
* @template TProps HTML props interface for target element,
* defaults to props for HTMLElement in IPopoverProps and ITooltipProps
*/
-export interface PopoverSharedProps<
- TProps extends DefaultPopoverTargetHTMLProps,
- // eslint-disable-next-line @typescript-eslint/ban-types
- AdditionalTargetRendererProps = {},
-> extends OverlayableProps,
- Props {
+export interface PopoverSharedProps extends OverlayableProps, Props {
/** Interactive element which will trigger the popover. */
children?: React.ReactNode;
@@ -248,10 +249,7 @@ export interface PopoverSharedProps<
// improvement would be better implemented if we added another type param to Popover, something like
// Popover. Instead of discriminating, we union the different possible event handlers
// that may be passed (they are all optional properties anyway).
- props: PopoverTargetProps &
- PopoverHoverTargetHandlers &
- PopoverClickTargetHandlers &
- AdditionalTargetRendererProps,
+ props: PopoverRenderTargetArgs,
) => React.JSX.Element;
/**
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 653894b538..27b9a54c41 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -23,11 +23,15 @@ import * as Errors from "../../common/errors";
// eslint-disable-next-line import/no-cycle
import { isContentEmpty, Popover, type PopoverInteractionKind } from "../popover/popover";
import { TOOLTIP_ARROW_SVG_SIZE } from "../popover/popoverArrow";
-import type { DefaultPopoverTargetHTMLProps, PopoverSharedProps } from "../popover/popoverSharedProps";
+import type {
+ DefaultPopoverTargetHTMLProps,
+ PopoverRenderTargetArgs,
+ PopoverSharedProps,
+} from "../popover/popoverSharedProps";
import { TooltipContext, type TooltipContextState, TooltipProvider } from "../popover/tooltipContext";
export interface TooltipProps
- extends Omit, "shouldReturnFocusOnClose">,
+ extends Omit, "shouldReturnFocusOnClose" | "renderTarget">,
IntentProps {
/**
* The content that will be displayed inside of the tooltip.
@@ -42,6 +46,14 @@ export interface TooltipProps & { tooltipId: string }) => React.JSX.Element;
+
/**
* The amount of time in milliseconds the tooltip should remain open after
* the user hovers off the trigger. The timer is canceled if the user mouses
From 2ca0b3921bc2c074169608c2e143902173baae0f Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Sun, 11 Aug 2024 22:05:10 -0600
Subject: [PATCH 30/32] revert test change
---
packages/core/test/tooltip/tooltipTests.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/core/test/tooltip/tooltipTests.tsx b/packages/core/test/tooltip/tooltipTests.tsx
index 42cf08cf2b..7de049d88a 100644
--- a/packages/core/test/tooltip/tooltipTests.tsx
+++ b/packages/core/test/tooltip/tooltipTests.tsx
@@ -27,6 +27,7 @@ import { Tooltip, type TooltipProps } from "../../src/components/tooltip/tooltip
const TARGET_SELECTOR = `.${Classes.POPOVER_TARGET}`;
const TOOLTIP_SELECTOR = `.${Classes.TOOLTIP}`;
+const TEST_TARGET_ID = "test-target";
describe("", () => {
describe("validation", () => {
@@ -209,7 +210,7 @@ describe("", () => {
function renderTooltip(props?: Partial) {
return mount(
Text} hoverOpenDelay={0} {...props} usePortal={false}>
-
+
,
);
}
From 4646017ac3b9862936340bf874c9c368f00f6eb5 Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Sun, 11 Aug 2024 22:06:39 -0600
Subject: [PATCH 31/32] rename const
---
packages/core/src/components/popover/popoverSharedProps.ts | 4 ++--
packages/core/src/components/tooltip/tooltip.tsx | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/core/src/components/popover/popoverSharedProps.ts b/packages/core/src/components/popover/popoverSharedProps.ts
index a69c3a2e36..9d264722cd 100644
--- a/packages/core/src/components/popover/popoverSharedProps.ts
+++ b/packages/core/src/components/popover/popoverSharedProps.ts
@@ -83,7 +83,7 @@ export type PopoverClickTargetHandlers =
+export type PopoverRenderTargetProps =
PopoverTargetProps & PopoverHoverTargetHandlers & PopoverClickTargetHandlers;
/**
@@ -249,7 +249,7 @@ export interface PopoverSharedProps. Instead of discriminating, we union the different possible event handlers
// that may be passed (they are all optional properties anyway).
- props: PopoverRenderTargetArgs,
+ props: PopoverRenderTargetProps,
) => React.JSX.Element;
/**
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 27b9a54c41..7e76569e08 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -25,7 +25,7 @@ import { isContentEmpty, Popover, type PopoverInteractionKind } from "../popover
import { TOOLTIP_ARROW_SVG_SIZE } from "../popover/popoverArrow";
import type {
DefaultPopoverTargetHTMLProps,
- PopoverRenderTargetArgs,
+ PopoverRenderTargetProps,
PopoverSharedProps,
} from "../popover/popoverSharedProps";
import { TooltipContext, type TooltipContextState, TooltipProvider } from "../popover/tooltipContext";
@@ -52,7 +52,7 @@ export interface TooltipProps & { tooltipId: string }) => React.JSX.Element;
+ renderTarget?: (props: PopoverRenderTargetProps & { tooltipId: string }) => React.JSX.Element;
/**
* The amount of time in milliseconds the tooltip should remain open after
From 03367967550f47ba9be5d684ca7b4df55c2c30ed Mon Sep 17 00:00:00 2001
From: Blake Vandercar
Date: Sun, 11 Aug 2024 22:19:30 -0600
Subject: [PATCH 32/32] ensure aria-describedby is unique
---
packages/core/src/components/tooltip/tooltip.tsx | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/core/src/components/tooltip/tooltip.tsx b/packages/core/src/components/tooltip/tooltip.tsx
index 7e76569e08..7d3a03cdc2 100644
--- a/packages/core/src/components/tooltip/tooltip.tsx
+++ b/packages/core/src/components/tooltip/tooltip.tsx
@@ -148,12 +148,21 @@ export class Tooltip<
const tooltipId = contentElement?.props?.id ?? React.useMemo(() => Utils.uniqueId("tooltip"), []);
const tooltipRole = contentElement?.props?.role ?? "tooltip";
const clonedContent =
- contentElement && React.cloneElement(contentElement, { role: tooltipRole, id: tooltipId });
+ contentElement &&
+ React.cloneElement(contentElement, {
+ role: tooltipRole,
+ id: tooltipId,
+ });
const childTarget = Utils.ensureElement(React.Children.toArray(children)[0]);
const clonedTarget =
childTarget &&
- React.cloneElement(childTarget, { "aria-describedby": childTarget.props["aria-describedby"] ?? tooltipId });
+ React.cloneElement(childTarget, {
+ // aria-describedby can have multiple values, space separated. Use Set to ensure unique.
+ "aria-describedby": Array.from(new Set([childTarget.props["aria-describedby"], tooltipId]))
+ .filter(Boolean)
+ .join(" "),
+ });
return (