11import React from "react" ;
2- import { Popover as BlueprintPopover , PopoverProps as BlueprintPopoverProps } from "@blueprintjs/core" ;
2+ import {
3+ Classes as BlueprintClasses ,
4+ Popover as BlueprintPopover ,
5+ PopoverProps as BlueprintPopoverProps ,
6+ Utils as BlueprintUtils ,
7+ } from "@blueprintjs/core" ;
38
49import { CLASSPREFIX as eccgui } from "../../configuration/constants" ;
510
@@ -13,6 +18,11 @@ export interface ContextOverlayProps extends Omit<BlueprintPopoverProps, "positi
1318 * Use it when you need to display modal dialogs out of the context overlay.
1419 */
1520 preventTopPosition ?: boolean ;
21+ /**
22+ * Use the overlay target as placeholder before the real `<ContextOverlay /` is rendered on first hover or focus event.
23+ * Currently experimental.
24+ */
25+ usePlaceholder ?: boolean ;
1626}
1727
1828/**
@@ -24,18 +34,79 @@ export const ContextOverlay = ({
2434 portalClassName,
2535 preventTopPosition,
2636 className = "" ,
27- ...restProps
37+ usePlaceholder = false ,
38+ ...otherPopoverProps
2839} : ContextOverlayProps ) => {
40+ const placeholderRef = React . useRef ( null ) ;
41+ const eventmemory = React . useRef < undefined | "afterhover" | "afterfocus" > ( undefined ) ;
42+ const [ placeholder , setPlaceholder ] = React . useState < boolean > (
43+ // use placeholder only for "simple" overlays without special states
44+ ( ! otherPopoverProps ?. disabled ||
45+ ! ! otherPopoverProps ?. defaultIsOpen ||
46+ ! ! otherPopoverProps ?. isOpen ||
47+ ! otherPopoverProps ?. renderTarget ) &&
48+ usePlaceholder
49+ ) ;
50+
51+ const targetClassName = `${ eccgui } -contextoverlay` + ( className ? ` ${ className } ` : "" ) ;
52+
53+ React . useEffect ( ( ) => {
54+ if ( placeholderRef . current ) {
55+ const swap = ( ev : MouseEvent | globalThis . FocusEvent ) => {
56+ eventmemory . current = ev . type === "focusin" ? "afterfocus" : "afterhover" ;
57+ setPlaceholder ( false ) ;
58+ } ;
59+ ( placeholderRef . current as HTMLElement ) . addEventListener ( "mouseenter" , swap ) ;
60+ ( placeholderRef . current as HTMLElement ) . addEventListener ( "focusin" , swap ) ;
61+ }
62+ } , [ ! ! placeholderRef . current ] ) ;
63+
64+ const refocus = React . useCallback ( ( node ) => {
65+ if ( eventmemory . current === "afterfocus" && node ) {
66+ const target = node . targetRef . current . children [ 0 ] ;
67+ if ( target ) {
68+ target . focus ( ) ;
69+ }
70+ }
71+ } , [ ] ) ;
72+
73+ const displayPlaceholder = ( ) => {
74+ const PlaceholderElement = otherPopoverProps ?. targetTagName ?? ( otherPopoverProps ?. fill ? "div" : "span" ) ;
75+ const childTarget = BlueprintUtils . ensureElement ( React . Children . toArray ( children ) [ 0 ] ) ;
76+ if ( ! childTarget ) {
77+ return null ;
78+ }
79+ return React . createElement (
80+ PlaceholderElement ,
81+ {
82+ ...otherPopoverProps ?. targetProps ,
83+ className : `${ BlueprintClasses . POPOVER_TARGET } ${ targetClassName } ` ,
84+ ref : placeholderRef ,
85+ } ,
86+ React . cloneElement ( childTarget , {
87+ ...childTarget . props ,
88+ className :
89+ childTarget . props . className ?? "" + ( otherPopoverProps . fill ? ` ${ BlueprintClasses . FILL } ` : "" ) ,
90+ tabIndex :
91+ childTarget . props . tabIndex ??
92+ ( ! otherPopoverProps ?. disabled && otherPopoverProps ?. openOnTargetFocus ? 0 : undefined ) ,
93+ } )
94+ ) ;
95+ } ;
96+
2997 const portalClassNameFinal =
3098 ( preventTopPosition ? `${ eccgui } -contextoverlay__portal--lowertop` : "" ) +
3199 ( portalClassName ? ` ${ portalClassName } ` : "" ) ;
32100
33- return (
101+ return placeholder ? (
102+ displayPlaceholder ( )
103+ ) : (
34104 < BlueprintPopover
35105 placement = "bottom"
36- { ...restProps }
37- className = { ` ${ eccgui } -contextoverlay` + ( className ? ` ${ className } ` : "" ) }
106+ { ...otherPopoverProps }
107+ className = { targetClassName }
38108 portalClassName = { portalClassNameFinal . trim ( ) ?? undefined }
109+ ref = { refocus }
39110 >
40111 { children }
41112 </ BlueprintPopover >
0 commit comments