fix(desktop): rebuild thread autoscroll on use-stick-to-bottom
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import { atom, type WritableAtom } from 'nanostores'
|
||||
|
||||
// `$threadScrolledUp` flips the instant the viewport leaves the bottom (dims the
|
||||
// composer / status stack). `$threadJumpButtonVisible` trips a little further up
|
||||
// (~10px) so the floating jump control only shows once meaningfully away.
|
||||
// "Is the thread parked at the bottom" is owned by use-stick-to-bottom inside
|
||||
// ThreadMessageList (the scroll container). That state lives only in that
|
||||
// subtree, so ThreadMessageList mirrors it into these atoms for the composer,
|
||||
// status stack, and floating jump button — all of which render OUTSIDE the thread.
|
||||
//
|
||||
// `$threadScrolledUp` dims the composer / status stack; `$threadJumpButtonVisible`
|
||||
// shows the floating jump control. Both track `!isAtBottom` today, but stay
|
||||
// separate so their thresholds can diverge again without touching consumers.
|
||||
export const $threadScrolledUp = atom(false)
|
||||
export const $threadJumpButtonVisible = atom(false)
|
||||
|
||||
@@ -13,17 +18,19 @@ const setter = (target: WritableAtom<boolean>) => (value: boolean) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const setThreadScrolledUp = setter($threadScrolledUp)
|
||||
export const setThreadJumpButtonVisible = setter($threadJumpButtonVisible)
|
||||
const setScrolledUp = setter($threadScrolledUp)
|
||||
const setJumpButtonVisible = setter($threadJumpButtonVisible)
|
||||
|
||||
export const resetThreadScroll = () => {
|
||||
setThreadScrolledUp(false)
|
||||
setThreadJumpButtonVisible(false)
|
||||
export const setThreadAtBottom = (isAtBottom: boolean) => {
|
||||
setScrolledUp(!isAtBottom)
|
||||
setJumpButtonVisible(!isAtBottom)
|
||||
}
|
||||
|
||||
// Cross-component bridge: the jump button lives by the composer, the re-arm +
|
||||
// pin machinery lives in the virtualizer. The virtualizer registers a handler;
|
||||
// the button fires it. Mirrors the composer focus/insert emitter pattern.
|
||||
export const resetThreadScroll = () => setThreadAtBottom(true)
|
||||
|
||||
// Cross-component bridge: the jump button lives by the composer, the viewport's
|
||||
// `scrollToBottom` lives inside the thread. The bridge registers a handler; the
|
||||
// button fires it. Mirrors the composer focus/insert emitter pattern.
|
||||
const handlers = new Set<() => void>()
|
||||
|
||||
export const onScrollToBottomRequest = (handler: () => void) => {
|
||||
@@ -33,3 +40,25 @@ export const onScrollToBottomRequest = (handler: () => void) => {
|
||||
}
|
||||
|
||||
export const requestScrollToBottom = () => handlers.forEach(handler => handler())
|
||||
|
||||
// Inline edit grows a sticky human bubble. Fire on pointerdown so the viewport
|
||||
// escapes stick-to-bottom before focus/layout; close clears the edit flag when
|
||||
// the inline composer unmounts.
|
||||
const editOpenHandlers = new Set<() => void>()
|
||||
const editCloseHandlers = new Set<() => void>()
|
||||
|
||||
export const onThreadEditOpen = (handler: () => void) => {
|
||||
editOpenHandlers.add(handler)
|
||||
|
||||
return () => void editOpenHandlers.delete(handler)
|
||||
}
|
||||
|
||||
export const notifyThreadEditOpen = () => editOpenHandlers.forEach(handler => handler())
|
||||
|
||||
export const onThreadEditClose = (handler: () => void) => {
|
||||
editCloseHandlers.add(handler)
|
||||
|
||||
return () => void editCloseHandlers.delete(handler)
|
||||
}
|
||||
|
||||
export const notifyThreadEditClose = () => editCloseHandlers.forEach(handler => handler())
|
||||
|
||||
Reference in New Issue
Block a user