@@ -9,8 +9,52 @@ export function averageY(t0: { clientY: number }, t1: { clientY: number }): numb
99}
1010
1111/** SGR mouse wheel escape sequence for a given direction */
12- export function scrollSeq ( direction : 'up' | 'down' ) : string {
13- return direction === 'up' ? '\x1b[\x3c64;1;1M' : '\x1b[\x3c65;1;1M'
12+ export function scrollSeq ( direction : 'up' | 'down' , x : number , y : number ) : string {
13+ const code = direction === 'up' ? 64 : 65
14+ return `\x1b[\x3c${ code } ;${ x } ;${ y } M`
15+ }
16+
17+ /** Page navigation key sequence for a given direction */
18+ export function pageSeq ( direction : 'up' | 'down' ) : string {
19+ return direction === 'up' ? '\x1b[5~' : '\x1b[6~'
20+ }
21+
22+ function clamp ( value : number , min : number , max : number ) : number {
23+ return Math . min ( max , Math . max ( min , value ) )
24+ }
25+
26+ function terminalGrid ( screenRect : DOMRect , term : XTerminal ) : { cols : number ; rows : number } {
27+ const colsFromTerm = term . cols
28+ const rowsFromTerm = term . rows
29+ if ( typeof colsFromTerm === 'number' && typeof rowsFromTerm === 'number' ) {
30+ if ( colsFromTerm > 0 && rowsFromTerm > 0 ) {
31+ return { cols : Math . round ( colsFromTerm ) , rows : Math . round ( rowsFromTerm ) }
32+ }
33+ }
34+
35+ const measure = document . querySelector ( '.xterm-char-measure-element' )
36+ if ( measure instanceof HTMLElement ) {
37+ const measureRect = measure . getBoundingClientRect ( )
38+ if ( measureRect . width > 0 && measureRect . height > 0 ) {
39+ const cols = Math . max ( 1 , Math . round ( screenRect . width / measureRect . width ) )
40+ const rows = Math . max ( 1 , Math . round ( screenRect . height / measureRect . height ) )
41+ return { cols, rows }
42+ }
43+ }
44+
45+ return { cols : 80 , rows : 24 }
46+ }
47+
48+ function touchToCell ( touch : Touch , screen : HTMLElement , term : XTerminal ) : { x : number ; y : number } {
49+ const rect = screen . getBoundingClientRect ( )
50+ const { cols, rows } = terminalGrid ( rect , term )
51+ const width = Math . max ( 1 , rect . width )
52+ const height = Math . max ( 1 , rect . height )
53+ const relX = clamp ( touch . clientX - rect . left , 0 , width )
54+ const relY = clamp ( touch . clientY - rect . top , 0 , height )
55+ const x = clamp ( Math . floor ( ( relX / width ) * cols ) + 1 , 1 , cols )
56+ const y = clamp ( Math . floor ( ( relY / height ) * rows ) + 1 , 1 , rows )
57+ return { x, y }
1458}
1559
1660/** Attach single-finger vertical scroll to the xterm screen */
@@ -23,8 +67,11 @@ export function attachScrollGesture(
2367 let startY = 0
2468 let lastY = 0
2569 let accDelta = 0
70+ let lastWheelAt = 0
71+ let screenEl : HTMLElement | null = null
2672
27- function onTouchStart ( e : TouchEvent ) : void {
73+ function onTouchStart ( e : Event ) : void {
74+ if ( ! ( e instanceof TouchEvent ) ) return
2875 if ( e . touches . length === 1 ) {
2976 const t = e . touches [ 0 ]
3077 if ( ! t ) return
@@ -34,7 +81,8 @@ export function attachScrollGesture(
3481 }
3582 }
3683
37- function onTouchMove ( e : TouchEvent ) : void {
84+ function onTouchMove ( e : Event ) : void {
85+ if ( ! ( e instanceof TouchEvent ) ) return
3886 if ( e . touches . length !== 1 || isDrawerOpen ( ) ) return
3987 const t = e . touches [ 0 ]
4088 if ( ! t ) return
@@ -56,33 +104,50 @@ export function attachScrollGesture(
56104 lastY = y
57105 accDelta += moveDy
58106
59- // Send one wheel event per sensitivity-worth of pixels
107+ // Send one scroll action per sensitivity-worth of pixels
60108 while ( Math . abs ( accDelta ) >= config . sensitivity ) {
61109 const dir = accDelta < 0 ? 'down' : 'up'
62- sendData ( term , scrollSeq ( dir ) )
110+
111+ if ( config . strategy === 'keys' ) {
112+ sendData ( term , pageSeq ( dir ) )
113+ } else {
114+ const now = Date . now ( )
115+ if ( now - lastWheelAt < config . wheelIntervalMs ) break
116+ lastWheelAt = now
117+
118+ const screen = screenEl
119+ if ( ! screen ) break
120+ const { x, y : row } = touchToCell ( t , screen , term )
121+ sendData ( term , scrollSeq ( dir , x , row ) )
122+ }
123+
63124 accDelta -= ( accDelta < 0 ? - 1 : 1 ) * config . sensitivity
64125 }
65126 }
66127
67- function onTouchEnd ( ) : void {
128+ function onTouchEnd ( e : Event ) : void {
129+ if ( ! ( e instanceof TouchEvent ) ) return
68130 if ( lock . current === 'scroll' ) {
69131 resetLock ( lock )
70132 }
71133 }
72134
73135 function attach ( ) : void {
74136 const screen = document . querySelector ( '.xterm-screen' )
75- if ( ! screen ) {
137+ if ( ! ( screen instanceof HTMLElement ) ) {
76138 setTimeout ( attach , 200 )
77139 return
78140 }
79- screen . addEventListener ( 'touchstart' , ( e : Event ) => onTouchStart ( e as TouchEvent ) , {
141+
142+ screenEl = screen
143+ screen . addEventListener ( 'touchstart' , onTouchStart , {
80144 passive : true ,
81145 } )
82- screen . addEventListener ( 'touchmove' , ( e : Event ) => onTouchMove ( e as TouchEvent ) , {
146+ screen . addEventListener ( 'touchmove' , onTouchMove , {
83147 passive : false ,
84148 } )
85- screen . addEventListener ( 'touchend' , ( ) => onTouchEnd ( ) , { passive : true } )
149+ screen . addEventListener ( 'touchend' , onTouchEnd , { passive : true } )
150+ screen . addEventListener ( 'touchcancel' , onTouchEnd , { passive : true } )
86151 }
87152
88153 attach ( )
0 commit comments