33 Classes as BlueprintClasses ,
44 Tooltip as BlueprintTooltip ,
55 TooltipProps as BlueprintTooltipProps ,
6+ Utils as BlueprintUtils ,
67} from "@blueprintjs/core" ;
78
89import { 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
3847export 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