@@ -22,6 +22,75 @@ export function useNodeTooltip(options = {}) {
2222 const TOOLTIP_DELAY = 500
2323 const HIDE_DELAY = 200 // Allow time to move mouse to tooltip
2424
25+ /**
26+ * Create and show tooltip immediately for a node.
27+ * @param {Object } node - Node data
28+ * @param {Event|null } event - Mouse event for positioning (optional)
29+ */
30+ function createTooltip ( node , event = null ) {
31+ // Destroy existing tooltip
32+ if ( activeTooltip ) {
33+ activeTooltip . destroy ( )
34+ activeTooltip = null
35+ }
36+
37+ activeNodeId = node . id
38+
39+ const content = buildTooltipHTML ( node , {
40+ showCheckbox : node . type === 'task' ,
41+ hideSensitive : getHideSensitive ( ) ,
42+ } )
43+
44+ // Use dynamic position reference based on cursor location
45+ const fixedRef = getFixedTooltipReference ( event )
46+ const placement = getTooltipPlacement ( event )
47+
48+ activeTooltip = tippy ( fixedRef , {
49+ ...tooltipOptions ,
50+ placement,
51+ content,
52+ showOnCreate : true ,
53+ hideOnClick : false ,
54+ onHidden : instance => {
55+ instance . destroy ( )
56+ if ( activeTooltip === instance ) {
57+ activeTooltip = null
58+ }
59+ } ,
60+ onShown : instance => {
61+ // Hide tooltip when mouse enters it (unless locked)
62+ instance . popper . addEventListener ( 'mouseenter' , ( ) => {
63+ if ( ! instance . state . isDestroyed && ! locked ) {
64+ instance . hide ( )
65+ }
66+ } )
67+
68+ // Attach checkbox listener
69+ const checkbox = instance . popper . querySelector ( 'input[type="checkbox"][data-node-id]' )
70+ if ( checkbox && onToggleComplete ) {
71+ checkbox . addEventListener ( 'change' , evt => {
72+ const nodeId = parseInt ( evt . target . dataset . nodeId )
73+ onToggleComplete ( nodeId )
74+ if ( ! instance . state . isDestroyed ) {
75+ instance . hide ( )
76+ }
77+ } )
78+ }
79+ // Attach open detail button listener
80+ const openBtn = instance . popper . querySelector ( '.tt-open-detail[data-node-id]' )
81+ if ( openBtn && onOpenDetail ) {
82+ openBtn . addEventListener ( 'click' , evt => {
83+ const nodeId = parseInt ( evt . target . dataset . nodeId )
84+ onOpenDetail ( nodeId )
85+ if ( ! instance . state . isDestroyed ) {
86+ instance . hide ( )
87+ }
88+ } )
89+ }
90+ } ,
91+ } )
92+ }
93+
2594 function showTooltip ( event , node ) {
2695 // Check if tooltip should be shown for this node
2796 if ( ! shouldShowTooltip ( node ) ) {
@@ -55,61 +124,7 @@ export function useNodeTooltip(options = {}) {
55124 return
56125 }
57126
58- activeNodeId = node . id
59-
60- const content = buildTooltipHTML ( node , {
61- showCheckbox : node . type === 'task' ,
62- hideSensitive : getHideSensitive ( ) ,
63- } )
64-
65- // Use dynamic position reference based on cursor location
66- const fixedRef = getFixedTooltipReference ( storedEvent )
67- const placement = getTooltipPlacement ( storedEvent )
68-
69- activeTooltip = tippy ( fixedRef , {
70- ...tooltipOptions ,
71- placement,
72- content,
73- showOnCreate : true ,
74- hideOnClick : false ,
75- onHidden : instance => {
76- instance . destroy ( )
77- if ( activeTooltip === instance ) {
78- activeTooltip = null
79- }
80- } ,
81- onShown : instance => {
82- // Hide tooltip when mouse enters it
83- instance . popper . addEventListener ( 'mouseenter' , ( ) => {
84- if ( ! instance . state . isDestroyed ) {
85- instance . hide ( )
86- }
87- } )
88-
89- // Attach checkbox listener
90- const checkbox = instance . popper . querySelector ( 'input[type="checkbox"][data-node-id]' )
91- if ( checkbox && onToggleComplete ) {
92- checkbox . addEventListener ( 'change' , evt => {
93- const nodeId = parseInt ( evt . target . dataset . nodeId )
94- onToggleComplete ( nodeId )
95- if ( ! instance . state . isDestroyed ) {
96- instance . hide ( )
97- }
98- } )
99- }
100- // Attach open detail button listener
101- const openBtn = instance . popper . querySelector ( '.tt-open-detail[data-node-id]' )
102- if ( openBtn && onOpenDetail ) {
103- openBtn . addEventListener ( 'click' , evt => {
104- const nodeId = parseInt ( evt . target . dataset . nodeId )
105- onOpenDetail ( nodeId )
106- if ( ! instance . state . isDestroyed ) {
107- instance . hide ( )
108- }
109- } )
110- }
111- } ,
112- } )
127+ createTooltip ( node , storedEvent )
113128 } , TOOLTIP_DELAY )
114129 }
115130
@@ -169,13 +184,30 @@ export function useNodeTooltip(options = {}) {
169184 activeNodeId = null
170185 }
171186
172- function toggleLock ( node ) {
173- if ( locked && activeNodeId === node ?. id ) {
187+ function toggleLock ( node , event = null ) {
188+ if ( ! node ) return
189+
190+ // Clear any pending show/hide
191+ if ( tooltipShowTimeout ) {
192+ clearTimeout ( tooltipShowTimeout )
193+ tooltipShowTimeout = null
194+ }
195+ if ( tooltipHideTimeout ) {
196+ clearTimeout ( tooltipHideTimeout )
197+ tooltipHideTimeout = null
198+ }
199+
200+ if ( locked && activeNodeId === node . id ) {
174201 // Clicking same node again - unlock
175202 unlockTooltip ( )
176- } else if ( activeTooltip && ! activeTooltip . state . isDestroyed && activeNodeId === node ? .id ) {
203+ } else if ( activeTooltip && ! activeTooltip . state . isDestroyed && activeNodeId === node . id ) {
177204 // Tooltip visible for this node - lock it
178205 lockTooltip ( )
206+ } else if ( shouldShowTooltip ( node ) ) {
207+ // No tooltip visible - create one immediately and lock it
208+ createTooltip ( node , event )
209+ // Lock after a brief delay to ensure tooltip is shown
210+ setTimeout ( ( ) => lockTooltip ( ) , 10 )
179211 }
180212 }
181213
0 commit comments