Skip to content

Commit 1b6320d

Browse files
committed
refactor(app): wire timeline scroll intent helper
1 parent e0eecc3 commit 1b6320d

1 file changed

Lines changed: 21 additions & 106 deletions

File tree

packages/app/src/pages/session/message-timeline.tsx

Lines changed: 21 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
1010
import { Dialog } from "@opencode-ai/ui/dialog"
1111
import { Spinner } from "@opencode-ai/ui/spinner"
1212
import { 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"
1414
import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
1515
import { showToast } from "@opencode-ai/ui/toast"
1616
import { Binary } from "@opencode-ai/util/binary"
1717
import { getFilename } from "@opencode-ai/util/path"
18-
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
1918
import { collectTimelineScrollMetrics } from "@/pages/session/session-timeline-scroll-anchors"
2019
import {
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"
2731
import { createTimelineStaging } from "@/pages/session/session-timeline-staging"
2832
import { taskDescription } from "@/pages/session/task-description"
2933
import { buildTurnMessagesByUserID, emptyAssistantMessages } from "@/pages/session/session-messages"
@@ -120,78 +124,6 @@ const messageComments = (parts: Part[]): MessageComment[] =>
120124

121125
export { 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-
195127
export 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

Comments
 (0)