Skip to content

Commit ddc24cb

Browse files
committed
allow placeholder lazy rendering process for Tooltip, enable it by default for all text (string) tooltips
1 parent 6d2d9f0 commit ddc24cb

2 files changed

Lines changed: 95 additions & 11 deletions

File tree

src/components/Tooltip/Tooltip.stories.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ export default {
1111
argTypes: {},
1212
} as Meta<typeof Tooltip>;
1313

14+
let forcedUpdateKey = 0; // @see https://github.com/storybookjs/storybook/issues/13375#issuecomment-1291011856
1415
const Template: StoryFn<typeof Tooltip> = (args) => (
1516
<OverlaysProvider>
16-
<Tooltip {...args} />
17+
<Tooltip {...args} key={++forcedUpdateKey} />
1718
</OverlaysProvider>
1819
);
1920

@@ -30,7 +31,7 @@ const testContent = loremIpsum({
3031
* */
3132
export const Default = Template.bind({});
3233
Default.args = {
33-
children: <span>hover me</span>,
34+
children: "hover me",
3435
content: testContent,
3536
addIndicator: true,
3637
};

src/components/Tooltip/Tooltip.tsx

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Classes as BlueprintClasses,
44
Tooltip as BlueprintTooltip,
55
TooltipProps as BlueprintTooltipProps,
6+
Utils as BlueprintUtils,
67
} from "@blueprintjs/core";
78

89
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
@@ -33,6 +34,14 @@ export interface TooltipProps extends Omit<BlueprintTooltipProps, "position"> {
3334
* Set properties for the Markdown parser
3435
*/
3536
markdownProps?: Omit<MarkdownProps, "children">;
37+
/**
38+
* Use the overlay target as placeholder before the real `<Tooltip /` is rendered on first hover or focus event.
39+
* This can boost performance massive but it is currently experimental.
40+
* Placeholders are never used when `disabled`, `defaultIsOpen` or `isOpen` is set to `true`, or if `renderTarget` is set.
41+
* If the tooltip `content` is only a string then a placeholder is automatically used, too.
42+
* You can prevent it in any case by setting it to `false`.
43+
*/
44+
usePlaceholder?: boolean;
3645
}
3746

3847
export const Tooltip = ({
@@ -43,8 +52,78 @@ export const Tooltip = ({
4352
addIndicator = false,
4453
markdownEnabler = "\n\n",
4554
markdownProps,
46-
...otherProps
55+
usePlaceholder,
56+
...otherTooltipProps
4757
}: TooltipProps) => {
58+
const placeholderRef = React.useRef(null);
59+
const eventmemory = React.useRef<null | "afterhover" | "afterfocus">(null);
60+
const searchId = React.useRef<null | string>(null);
61+
const [placeholder, setPlaceholder] = React.useState<boolean>(
62+
(!otherTooltipProps?.disabled ||
63+
!!otherTooltipProps?.defaultIsOpen ||
64+
!!otherTooltipProps?.isOpen ||
65+
!otherTooltipProps?.renderTarget) &&
66+
(usePlaceholder === true || (typeof content === "string" && usePlaceholder !== false))
67+
);
68+
69+
const targetClassName =
70+
`${eccgui}-tooltip__wrapper` +
71+
(className ? " " + className : "") +
72+
(addIndicator === true ? " " + BlueprintClasses.TOOLTIP_INDICATOR : "");
73+
React.useEffect(() => {
74+
if (placeholderRef.current) {
75+
const swap = (ev: MouseEvent | globalThis.FocusEvent) => {
76+
eventmemory.current = ev.type === "focusin" ? "afterfocus" : "afterhover";
77+
searchId.current = Date.now().toString(16) + Math.random().toString(16).slice(2);
78+
setPlaceholder(false);
79+
};
80+
(placeholderRef.current as HTMLElement).addEventListener("mouseenter", swap);
81+
(placeholderRef.current as HTMLElement).addEventListener("focusin", swap);
82+
}
83+
}, [!!placeholderRef.current]);
84+
85+
const refocus = React.useCallback((node) => {
86+
if (eventmemory.current && node) {
87+
// we do not have a `targetRef` here, so we need to workaround it
88+
// const target = node.targetRef.current.children[0];
89+
const target = document.body.querySelector(
90+
`[data-postplaceholder=id${eventmemory.current}${searchId.current}]`
91+
)?.children[0];
92+
if (target) {
93+
switch (eventmemory.current) {
94+
case "afterfocus":
95+
(target as HTMLElement).focus();
96+
break;
97+
case "afterhover":
98+
(target as HTMLElement).dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
99+
break;
100+
}
101+
}
102+
}
103+
}, []);
104+
105+
const displayPlaceholder = () => {
106+
const PlaceholderElement = otherTooltipProps?.targetTagName ?? (otherTooltipProps?.fill ? "div" : "span");
107+
const childTarget = BlueprintUtils.ensureElement(React.Children.toArray(children)[0]);
108+
if (!childTarget) {
109+
return null;
110+
}
111+
return React.createElement(
112+
PlaceholderElement,
113+
{
114+
...otherTooltipProps?.targetProps,
115+
className: `${BlueprintClasses.POPOVER_TARGET} ${targetClassName}`,
116+
ref: placeholderRef,
117+
},
118+
React.cloneElement(childTarget, {
119+
...childTarget.props,
120+
className:
121+
childTarget.props.className ?? "" + (otherTooltipProps.fill ? ` ${BlueprintClasses.FILL}` : ""),
122+
tabIndex: 0,
123+
})
124+
);
125+
};
126+
48127
let tooltipContent = content;
49128

50129
if (
@@ -55,23 +134,27 @@ export const Tooltip = ({
55134
tooltipContent = <Markdown {...markdownProps}>{content}</Markdown>;
56135
}
57136

58-
return (
137+
return placeholder ? (
138+
displayPlaceholder()
139+
) : (
59140
<BlueprintTooltip
60141
lazy={true}
61142
hoverOpenDelay={500}
62-
{...otherProps}
143+
{...otherTooltipProps}
63144
content={tooltipContent}
64-
className={
65-
`${eccgui}-tooltip__wrapper` +
66-
(className ? " " + className : "") +
67-
(addIndicator === true ? " " + BlueprintClasses.TOOLTIP_INDICATOR : "")
68-
}
69-
//targetClassName={`${eccgui}-tooltip__target` + (className ? " " + className + "__target" : "")}
145+
className={targetClassName}
70146
popoverClassName={
71147
`${eccgui}-tooltip__content` +
72148
` ${eccgui}-tooltip--${size}` +
73149
(className ? " " + className + "__content" : "")
74150
}
151+
ref={refocus}
152+
targetProps={
153+
{
154+
...otherTooltipProps.targetProps,
155+
"data-postplaceholder": `id${eventmemory.current}${searchId.current}`,
156+
} as React.HTMLProps<HTMLElement>
157+
}
75158
>
76159
{children}
77160
</BlueprintTooltip>

0 commit comments

Comments
 (0)