@@ -10,20 +10,24 @@ import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
1010import { Dialog } from "@opencode-ai/ui/dialog"
1111import { Spinner } from "@opencode-ai/ui/spinner"
1212import { SessionTurn } from "@opencode-ai/ui/session-turn"
13- import { ScrollView , type ScrollViewScrollIntent } from "@opencode-ai/ui/scroll-view"
13+ import { ScrollView } from "@opencode-ai/ui/scroll-view"
1414import type { AssistantMessage , Message as MessageType , Part , TextPart , UserMessage } from "@opencode-ai/sdk/v2"
1515import { showToast } from "@opencode-ai/ui/toast"
1616import { Binary } from "@opencode-ai/util/binary"
1717import { getFilename } from "@opencode-ai/util/path"
18- import { shouldMarkBoundaryGesture , normalizeWheelDelta } from "@/pages/session/message-gesture"
1918import { collectTimelineScrollMetrics } from "@/pages/session/session-timeline-scroll-anchors"
2019import {
21- classifyTimelineScrollGesture ,
2220 type TimelineScrollControllerResult ,
2321 type TimelineScrollIntent ,
24- type TimelineScrollMetrics ,
2522 type TimelineScrollObservation ,
2623} from "@/pages/session/session-timeline-scroll-controller"
24+ import {
25+ createTouchTimelineScrollIntent ,
26+ createWheelTimelineScrollIntent ,
27+ scrollViewIntentToTimelineIntent ,
28+ shouldMarkLegacyScrollIntent ,
29+ shouldMarkTimelineBoundaryGesture ,
30+ } from "@/pages/session/session-timeline-scroll-intents"
2731import { createTimelineStaging } from "@/pages/session/session-timeline-staging"
2832import { taskDescription } from "@/pages/session/task-description"
2933import { buildTurnMessagesByUserID , emptyAssistantMessages } from "@/pages/session/session-messages"
@@ -120,78 +124,6 @@ const messageComments = (parts: Part[]): MessageComment[] =>
120124
121125export { taskDescription }
122126
123- const boundaryTarget = ( root : HTMLElement , target : EventTarget | null ) => {
124- const current = target instanceof Element ? target : undefined
125- const nested = current ?. closest ( "[data-scrollable]" )
126- if ( ! nested || nested === root ) return root
127- if ( ! ( nested instanceof HTMLElement ) ) return root
128- return nested
129- }
130-
131- const boundaryGesture = ( input : {
132- root : HTMLDivElement
133- target : EventTarget | null
134- delta : number
135- } ) => {
136- const target = boundaryTarget ( input . root , input . target )
137- if ( target === input . root ) return { nestedScrollable : false , atNestedBoundary : true }
138- return {
139- nestedScrollable : true ,
140- atNestedBoundary : shouldMarkBoundaryGesture ( {
141- delta : input . delta ,
142- scrollTop : target . scrollTop ,
143- scrollHeight : target . scrollHeight ,
144- clientHeight : target . clientHeight ,
145- } ) ,
146- }
147- }
148-
149- const markBoundaryGesture = ( input : {
150- root : HTMLDivElement
151- target : EventTarget | null
152- delta : number
153- onMarkScrollGesture : ( target ?: EventTarget | null ) => void
154- } ) => {
155- const boundary = boundaryGesture ( input )
156- if ( ! boundary . nestedScrollable || boundary . atNestedBoundary ) {
157- input . onMarkScrollGesture ( input . root )
158- }
159- }
160-
161- const scrollViewMetricsToTimelineMetrics = ( metrics : {
162- scrollTop : number
163- scrollHeight : number
164- clientHeight : number
165- } ) : TimelineScrollMetrics => {
166- const max = Math . max ( 0 , metrics . scrollHeight - metrics . clientHeight )
167- const distanceFromBottom = Math . max ( 0 , max - metrics . scrollTop )
168- return {
169- scrollTop : metrics . scrollTop ,
170- scrollHeight : metrics . scrollHeight ,
171- clientHeight : metrics . clientHeight ,
172- distanceFromTop : metrics . scrollTop ,
173- distanceFromBottom,
174- nearTop : metrics . scrollTop <= 12 ,
175- nearBottom : distanceFromBottom <= 2 ,
176- }
177- }
178-
179- const scrollViewIntentToTimelineIntent = ( intent : ScrollViewScrollIntent ) : TimelineScrollIntent => {
180- if ( intent . type === "keyboard_scroll" ) {
181- return { type : "keyboard_scroll" , key : intent . key , source : "scroll_view" }
182- }
183- return {
184- type : intent . type ,
185- source : "scroll_view" ,
186- metrics : scrollViewMetricsToTimelineMetrics ( intent . metrics ) ,
187- }
188- }
189-
190- const shouldMarkLegacyScrollIntent = ( intent : ScrollViewScrollIntent ) => {
191- if ( intent . type === "keyboard_scroll" ) return true
192- return intent . type === "scrollbar_drag_start"
193- }
194-
195127export function MessageTimeline ( props : {
196128 sessionID : string
197129 sessionKey : string
@@ -870,26 +802,15 @@ export function MessageTimeline(props: {
870802 props . onTimelineScrollIntent ( scrollViewIntentToTimelineIntent ( intent ) )
871803 } }
872804 onWheel = { ( e ) => {
873- const root = e . currentTarget
874- const delta = normalizeWheelDelta ( {
805+ const result = createWheelTimelineScrollIntent ( {
806+ root : e . currentTarget ,
807+ target : e . target ,
875808 deltaY : e . deltaY ,
876809 deltaMode : e . deltaMode ,
877- rootHeight : root . clientHeight ,
878- } )
879- if ( ! delta ) return
880- const boundary = boundaryGesture ( { root, target : e . target , delta } )
881- const gesture = classifyTimelineScrollGesture ( {
882- deltaY : delta ,
883- viewportHeight : root . clientHeight ,
884- nestedScrollable : boundary . nestedScrollable ,
885- atNestedBoundary : boundary . atNestedBoundary ,
886810 } )
887- props . onTimelineScrollIntent ( {
888- type : "wheel_scroll" ,
889- source : "timeline" ,
890- ...gesture ,
891- } )
892- markBoundaryGesture ( { root, target : e . target , delta, onMarkScrollGesture : props . onMarkScrollGesture } )
811+ if ( ! result ) return
812+ props . onTimelineScrollIntent ( result . intent )
813+ if ( shouldMarkTimelineBoundaryGesture ( result . boundary ) ) props . onMarkScrollGesture ( e . currentTarget )
893814 } }
894815 onTouchStart = { ( e ) => {
895816 touchGesture = e . touches [ 0 ] ?. clientY
@@ -903,20 +824,14 @@ export function MessageTimeline(props: {
903824 const delta = prev - next
904825 if ( ! delta ) return
905826
906- const root = e . currentTarget
907- const boundary = boundaryGesture ( { root, target : e . target , delta } )
908- const gesture = classifyTimelineScrollGesture ( {
909- deltaY : delta ,
910- viewportHeight : root . clientHeight ,
911- nestedScrollable : boundary . nestedScrollable ,
912- atNestedBoundary : boundary . atNestedBoundary ,
913- } )
914- props . onTimelineScrollIntent ( {
915- type : "touch_scroll" ,
916- source : "timeline" ,
917- ...gesture ,
827+ const result = createTouchTimelineScrollIntent ( {
828+ root : e . currentTarget ,
829+ target : e . target ,
830+ delta,
918831 } )
919- markBoundaryGesture ( { root, target : e . target , delta, onMarkScrollGesture : props . onMarkScrollGesture } )
832+ if ( ! result ) return
833+ props . onTimelineScrollIntent ( result . intent )
834+ if ( shouldMarkTimelineBoundaryGesture ( result . boundary ) ) props . onMarkScrollGesture ( e . currentTarget )
920835 } }
921836 onTouchEnd = { ( ) => {
922837 touchGesture = undefined
0 commit comments