@@ -9,6 +9,12 @@ export const useClipboard = () => {
99 const clipboardTimeoutRef = useRef < ReturnType < typeof setTimeout > | null > (
1010 null ,
1111 )
12+ const pendingCopyTimeoutRef = useRef < ReturnType < typeof setTimeout > | null > (
13+ null ,
14+ )
15+ const copyDelayRef = useRef < number > ( 2000 )
16+ const pendingSelectionRef = useRef < string | null > ( null )
17+ const lastCopiedRef = useRef < string | null > ( null )
1218
1319 const copyToClipboard = useCallback ( async ( text : string ) => {
1420 if ( ! text || text . trim ( ) . length === 0 ) return
@@ -37,7 +43,9 @@ export const useClipboard = () => {
3743 clearTimeout ( clipboardTimeoutRef . current )
3844 }
3945
40- setClipboardMessage ( 'Copied to clipboard' )
46+ const preview = text . replace ( / \s + / g, ' ' ) . trim ( )
47+ const truncated = preview . length > 40 ? `${ preview . slice ( 0 , 37 ) } …` : preview
48+ setClipboardMessage ( `Copied: "${ truncated } "` )
4149 clipboardTimeoutRef . current = setTimeout ( ( ) => {
4250 setClipboardMessage ( null )
4351 clipboardTimeoutRef . current = null
@@ -48,17 +56,49 @@ export const useClipboard = () => {
4856 } , [ ] )
4957
5058 useEffect ( ( ) => {
51- const handleSelection = ( ) => {
52- const selection = ( renderer as any ) ?. getSelection ?.( )
53- if ( selection && selection . length > 0 ) {
54- void copyToClipboard ( selection )
59+ const handleSelection = ( selectionEvent : any ) => {
60+ const selectionObj = selectionEvent ?? ( renderer as any ) ?. getSelection ?.( )
61+ const rawText : string | null = selectionObj ?. getSelectedText
62+ ? selectionObj . getSelectedText ( )
63+ : typeof selectionObj === 'string'
64+ ? selectionObj
65+ : null
66+
67+ if ( ! rawText || rawText . trim ( ) . length === 0 ) {
68+ pendingSelectionRef . current = null
69+ if ( pendingCopyTimeoutRef . current ) {
70+ clearTimeout ( pendingCopyTimeoutRef . current )
71+ pendingCopyTimeoutRef . current = null
72+ }
73+ return
74+ }
75+
76+ if ( rawText === pendingSelectionRef . current ) {
77+ return
5578 }
79+
80+ pendingSelectionRef . current = rawText
81+
82+ if ( pendingCopyTimeoutRef . current ) {
83+ clearTimeout ( pendingCopyTimeoutRef . current )
84+ }
85+
86+ pendingCopyTimeoutRef . current = setTimeout ( ( ) => {
87+ pendingCopyTimeoutRef . current = null
88+ const pending = pendingSelectionRef . current
89+ if ( ! pending || pending === lastCopiedRef . current ) {
90+ return
91+ }
92+
93+ lastCopiedRef . current = pending
94+ void copyToClipboard ( pending )
95+ } , copyDelayRef . current )
5696 }
5797
58- if ( renderer ) {
59- renderer . on ?. ( 'selectionchange ', handleSelection )
98+ if ( renderer ?. on ) {
99+ renderer . on ( 'selection ', handleSelection )
60100 return ( ) => {
61- renderer . off ?.( 'selectionchange ' , handleSelection )
101+ renderer . off ?.( 'selection ' , handleSelection )
62102 }
63103 }
64104 return undefined
@@ -69,6 +109,10 @@ export const useClipboard = () => {
69109 if ( clipboardTimeoutRef . current ) {
70110 clearTimeout ( clipboardTimeoutRef . current )
71111 }
112+ if ( pendingCopyTimeoutRef . current ) {
113+ clearTimeout ( pendingCopyTimeoutRef . current )
114+ pendingCopyTimeoutRef . current = null
115+ }
72116 }
73117 } , [ ] )
74118
0 commit comments