@@ -2,6 +2,7 @@ import React from "react";
22import {
33 Classes as BlueprintClasses ,
44 Popover as BlueprintPopover ,
5+ PopoverInteractionKind as InteractionKind ,
56 PopoverProps as BlueprintPopoverProps ,
67 Utils as BlueprintUtils ,
78} from "@blueprintjs/core" ;
@@ -37,9 +38,10 @@ export const ContextOverlay = ({
3738 usePlaceholder = false ,
3839 ...otherPopoverProps
3940} : ContextOverlayProps ) => {
40- const placeholderRef = React . useRef ( null ) ;
41- const eventMemory = React . useRef < undefined | "afterhover " | "afterfocus " > ( undefined ) ;
41+ const placeholderRef = React . useRef < HTMLElement > ( null ) ;
42+ const eventMemory = React . useRef < undefined | "mouseenter " | "focusin" | "click "> ( undefined ) ;
4243 const swapDelay = React . useRef < null | NodeJS . Timeout > ( null ) ;
44+ const interactionKind = React . useRef < InteractionKind > ( otherPopoverProps . interactionKind ?? InteractionKind . CLICK ) ;
4345 const swapDelayTime = 15 ;
4446 const [ placeholder , setPlaceholder ] = React . useState < boolean > (
4547 // use placeholder only for "simple" overlays without special states
@@ -50,37 +52,85 @@ export const ContextOverlay = ({
5052 usePlaceholder
5153 ) ;
5254
55+ const swap = ( ev : MouseEvent | globalThis . FocusEvent ) => {
56+ const waitForClick =
57+ interactionKind . current === InteractionKind . CLICK ||
58+ interactionKind . current === InteractionKind . CLICK_TARGET_ONLY ;
59+
60+ if ( swapDelay . current ) {
61+ clearTimeout ( swapDelay . current ) ;
62+ }
63+
64+ const replacePlaceholder = ( ) => {
65+ eventMemory . current = ev . type as "mouseenter" | "focusin" | "click" ;
66+ setPlaceholder ( false ) ;
67+ } ;
68+
69+ if ( waitForClick ) {
70+ ev . stopImmediatePropagation ( ) ;
71+ replacePlaceholder ( ) ;
72+ return ;
73+ }
74+
75+ swapDelay . current = setTimeout (
76+ replacePlaceholder ,
77+ // we delay the swap for hover/focus to prevent unwanted effects
78+ // (e.g. event hickup after replacing elements when it is not really necessary)
79+ swapDelayTime
80+ ) ;
81+ } ;
82+
5383 React . useEffect ( ( ) => {
84+ interactionKind . current = otherPopoverProps . interactionKind ?? InteractionKind . CLICK ;
85+ const waitForClick =
86+ interactionKind . current === InteractionKind . CLICK ||
87+ interactionKind . current === InteractionKind . CLICK_TARGET_ONLY ;
88+ const removeEvents = ( ) => {
89+ if ( placeholderRef . current ) {
90+ placeholderRef . current . removeEventListener ( "click" , swap ) ;
91+ placeholderRef . current . removeEventListener ( "mouseenter" , swap ) ;
92+ placeholderRef . current . removeEventListener ( "focusin" , swap ) ;
93+ }
94+ return ;
95+ } ;
5496 if ( placeholderRef . current ) {
55- const swap = ( ev : MouseEvent | globalThis . FocusEvent ) => {
56- if ( swapDelay . current ) {
57- clearTimeout ( swapDelay . current ) ;
58- }
59- swapDelay . current = setTimeout ( ( ) => {
60- // we delay the swap to prevent unwanted effects
61- // (e.g. event hickup after replacing elements when it is not really necessary)
62- eventMemory . current = ev . type === "focusin" ? "afterfocus" : "afterhover" ;
63- setPlaceholder ( false ) ;
64- } , swapDelayTime ) ;
65- } ;
66- ( placeholderRef . current as HTMLElement ) . addEventListener ( "mouseenter" , swap ) ;
67- ( placeholderRef . current as HTMLElement ) . addEventListener ( "focusin" , swap ) ;
97+ removeEvents ( ) ; // remove events in case of interaction kind changed during existence
98+ if ( waitForClick ) {
99+ placeholderRef . current . addEventListener ( "click" , swap ) ;
100+ } else {
101+ placeholderRef . current . addEventListener ( "mouseenter" , swap ) ;
102+ placeholderRef . current . addEventListener ( "focusin" , swap ) ;
103+ }
68104 return ( ) => {
69- if ( placeholderRef . current ) {
70- ( placeholderRef . current as HTMLElement ) . removeEventListener ( "mouseenter" , swap ) ;
71- ( placeholderRef . current as HTMLElement ) . removeEventListener ( "focusin" , swap ) ;
72- }
105+ removeEvents ( ) ;
73106 } ;
74107 }
75108 return ( ) => { } ;
76- } , [ ! ! placeholderRef . current ] ) ;
109+ } , [ ! ! placeholderRef . current , otherPopoverProps . interactionKind ] ) ;
77110
78111 const refocus = React . useCallback ( ( node ) => {
79- if ( eventMemory . current === "afterfocus" && node ) {
80- const target = node . targetRef . current . children [ 0 ] ;
81- if ( target ) {
112+ const target = node ?. targetRef . current . children [ 0 ] ;
113+ if ( ! eventMemory . current || ! target ) {
114+ return ;
115+ }
116+ switch ( eventMemory . current ) {
117+ case "focusin" :
82118 target . focus ( ) ;
83- }
119+ break ;
120+ case "click" :
121+ target . click ( ) ;
122+ break ;
123+ case "mouseenter" :
124+ // re-check if the cursor is still over the element after swapping the placeholder before triggering the event to bubble up
125+ ( target as HTMLElement ) . addEventListener (
126+ "mouseover" ,
127+ ( ) => ( target as HTMLElement ) . dispatchEvent ( new MouseEvent ( "mouseover" , { bubbles : true } ) ) ,
128+ {
129+ capture : true ,
130+ once : true ,
131+ }
132+ ) ;
133+ break ;
84134 }
85135 } , [ ] ) ;
86136
0 commit comments