1- import React from 'react' ;
1+ import React , { useState , useRef , useCallback , useEffect } from 'react' ;
22import type { ScriptMetadata } from '../types' ;
33import type { ThemeColors } from './theme-colors' ;
44import { SectionHeader } from './section-header' ;
@@ -8,26 +8,151 @@ export interface MainFunctionSectionProps {
88 colors : ThemeColors ;
99}
1010
11- export const MainFunctionSection : React . FC < MainFunctionSectionProps > = ( { metadata, colors } ) => (
12- < div >
13- < SectionHeader colors = { colors } label = "主函数" />
14- < div style = { { padding : '4px 12px 4px 20px' } } >
15- < div style = { { display : 'flex' , alignItems : 'center' , gap : 6 , fontSize : 12 } } >
16- < span style = { { color : colors . methodColor , fontSize : 11 , flexShrink : 0 } } >
17- ƒ
18- </ span >
19- < span style = { { color : colors . text , fontWeight : 500 } } >
20- { metadata . mainMethod }
21- </ span >
22- < span style = { { color : colors . textSecondary , fontSize : 11 } } >
23- ({ metadata . requests . map ( ( r ) => `${ r . name } : ${ r . dataType } ` ) . join ( ', ' ) } )
24- </ span >
25- { metadata . returnType && (
26- < span style = { { color : colors . typeColor , fontSize : 11 , marginLeft : 'auto' } } >
27- → { metadata . returnType }
11+ // ── Tooltip 定位:基于 fixed 坐标,避免被滚动容器裁剪 ──────────
12+
13+ interface TooltipPos {
14+ top : number ;
15+ left : number ;
16+ maxWidth : number ;
17+ maxHeight : number ;
18+ }
19+
20+ const TOOLTIP_MARGIN = 6 ;
21+ const TOOLTIP_MAX_WIDTH = 380 ;
22+
23+ function calcTooltipPos (
24+ anchorRect : DOMRect ,
25+ tooltipEl : HTMLDivElement | null ,
26+ ) : TooltipPos {
27+ const vw = window . innerWidth ;
28+ const vh = window . innerHeight ;
29+
30+ // 优先显示在 anchor 下方,空间不足时翻到上方
31+ const tooltipH = tooltipEl ?. scrollHeight ?? 200 ;
32+ const spaceBelow = vh - anchorRect . bottom - TOOLTIP_MARGIN ;
33+ const spaceAbove = anchorRect . top - TOOLTIP_MARGIN ;
34+ const placeBelow = spaceBelow >= Math . min ( tooltipH , 260 ) || spaceBelow >= spaceAbove ;
35+
36+ const top = placeBelow
37+ ? anchorRect . bottom + TOOLTIP_MARGIN
38+ : Math . max ( TOOLTIP_MARGIN , anchorRect . top - tooltipH - TOOLTIP_MARGIN ) ;
39+
40+ // 水平方向:以 anchor 左边为起点,超出右边界则左移
41+ let left = anchorRect . left ;
42+ const maxRight = vw - TOOLTIP_MARGIN ;
43+ if ( left + TOOLTIP_MAX_WIDTH > maxRight ) {
44+ left = Math . max ( TOOLTIP_MARGIN , maxRight - TOOLTIP_MAX_WIDTH ) ;
45+ }
46+
47+ const maxWidth = Math . min ( TOOLTIP_MAX_WIDTH , maxRight - left ) ;
48+ const maxHeight = placeBelow ? spaceBelow - TOOLTIP_MARGIN : spaceAbove - TOOLTIP_MARGIN ;
49+
50+ return { top, left, maxWidth, maxHeight : Math . max ( maxHeight , 80 ) } ;
51+ }
52+
53+ // ── 组件 ──────────────────────────────────────────────────────
54+
55+ export const MainFunctionSection : React . FC < MainFunctionSectionProps > = ( { metadata, colors } ) => {
56+ const [ showTip , setShowTip ] = useState ( false ) ;
57+ const [ pos , setPos ] = useState < TooltipPos | null > ( null ) ;
58+ const anchorRef = useRef < HTMLSpanElement > ( null ) ;
59+ const tooltipRef = useRef < HTMLDivElement > ( null ) ;
60+
61+ const updatePos = useCallback ( ( ) => {
62+ if ( ! anchorRef . current ) return ;
63+ const rect = anchorRef . current . getBoundingClientRect ( ) ;
64+ setPos ( calcTooltipPos ( rect , tooltipRef . current ) ) ;
65+ } , [ ] ) ;
66+
67+ useEffect ( ( ) => {
68+ if ( ! showTip ) return ;
69+ updatePos ( ) ;
70+ window . addEventListener ( 'scroll' , updatePos , true ) ;
71+ window . addEventListener ( 'resize' , updatePos ) ;
72+ return ( ) => {
73+ window . removeEventListener ( 'scroll' , updatePos , true ) ;
74+ window . removeEventListener ( 'resize' , updatePos ) ;
75+ } ;
76+ } , [ showTip , updatePos ] ) ;
77+
78+ return (
79+ < div >
80+ < SectionHeader colors = { colors } label = "主函数" />
81+ < div style = { { padding : '4px 12px 4px 20px' } } >
82+ < div style = { { display : 'flex' , alignItems : 'center' , gap : 6 , fontSize : 12 } } >
83+ < span style = { { color : colors . methodColor , fontSize : 11 , flexShrink : 0 } } >
84+ ƒ
85+ </ span >
86+ < span style = { { color : colors . text , fontWeight : 500 } } >
87+ { metadata . mainMethod }
2888 </ span >
29- ) }
89+ { metadata . description && (
90+ < span
91+ ref = { anchorRef }
92+ style = { { display : 'inline-flex' , cursor : 'help' , flexShrink : 0 } }
93+ onMouseEnter = { ( ) => setShowTip ( true ) }
94+ onMouseLeave = { ( ) => setShowTip ( false ) }
95+ >
96+ < span
97+ style = { {
98+ display : 'inline-flex' ,
99+ alignItems : 'center' ,
100+ justifyContent : 'center' ,
101+ width : 14 ,
102+ height : 14 ,
103+ borderRadius : '50%' ,
104+ border : `1px solid ${ colors . textSecondary } ` ,
105+ color : colors . textSecondary ,
106+ fontSize : 10 ,
107+ lineHeight : 1 ,
108+ fontWeight : 600 ,
109+ } }
110+ >
111+ ?
112+ </ span >
113+ </ span >
114+ ) }
115+ < span style = { { color : colors . textSecondary , fontSize : 11 } } >
116+ ({ metadata . requests . map ( ( r ) => `${ r . name } : ${ r . dataType } ` ) . join ( ', ' ) } )
117+ </ span >
118+ { metadata . returnType && (
119+ < span style = { { color : colors . typeColor , fontSize : 11 , marginLeft : 'auto' } } >
120+ → { metadata . returnType }
121+ </ span >
122+ ) }
123+ </ div >
30124 </ div >
125+
126+ { /* Tooltip 渲染在 fixed 层,脱离滚动容器 */ }
127+ { showTip && pos && metadata . description && (
128+ < div
129+ ref = { tooltipRef }
130+ onMouseEnter = { ( ) => setShowTip ( true ) }
131+ onMouseLeave = { ( ) => setShowTip ( false ) }
132+ style = { {
133+ position : 'fixed' ,
134+ top : pos . top ,
135+ left : pos . left ,
136+ maxWidth : pos . maxWidth ,
137+ maxHeight : pos . maxHeight ,
138+ overflowY : 'auto' ,
139+ padding : '8px 12px' ,
140+ background : colors . headerBg ,
141+ border : `1px solid ${ colors . border } ` ,
142+ borderRadius : 6 ,
143+ color : colors . text ,
144+ fontSize : 12 ,
145+ lineHeight : 1.65 ,
146+ whiteSpace : 'pre-wrap' ,
147+ wordBreak : 'break-word' ,
148+ zIndex : 9999 ,
149+ boxShadow : '0 4px 16px rgba(0,0,0,0.3)' ,
150+ pointerEvents : 'auto' ,
151+ } }
152+ >
153+ { metadata . description }
154+ </ div >
155+ ) }
31156 </ div >
32- </ div >
33- ) ;
157+ ) ;
158+ } ;
0 commit comments