fix: cr
This commit is contained in:
12 files changed
+117
-36
No files matched your search
@@ -119,13 +119,14 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
export interface ConversationSessionOwnerProps {
|
||||
/**
|
||||
* Wrap the view ring in the transcript scrollport that also hosts the
|
||||
* sticky composer. Supplied for every real session (hero/settling/active)
|
||||
* so the composer keeps one tree seat across the blank → active flip; the
|
||||
* header stays outside that wrapper as ordinary column chrome
|
||||
* (`flex: none`), while active CSS sticks the composer to the bottom of
|
||||
* the same scrollport so wheel over the footer scrolls the flow.
|
||||
* sticky composer seat (whole `'conversation.composer'` chain output).
|
||||
* Supplied for every real session (hero/settling/active) so the composer
|
||||
* keeps one tree seat across the blank → active flip; the header stays
|
||||
* outside that wrapper as ordinary column chrome (`flex: none`), while
|
||||
* active CSS sticks the seat to the bottom of the same scrollport so wheel
|
||||
* over the footer scrolls the flow.
|
||||
* @param view - the session view-ring content (null while blank chrome is hidden).
|
||||
* @returns the scrollport containing `view` and the sticky composer.
|
||||
* @returns the scrollport containing `view` and the sticky composer seat.
|
||||
*/
|
||||
wrapActiveBody?: (view: ReactNode) => ReactNode
|
||||
}
|
||||
|
||||
@@ -133,9 +133,16 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Common seat for the composer chain (fallback + elected overlay siblings). */
|
||||
.composerSeat {
|
||||
display: flex;
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Active phase: header is ordinary column chrome above the scrollport (not
|
||||
sticky). The scroll body holds the transcript and the sticky composer so
|
||||
wheel over the footer moves the flow. */
|
||||
sticky). The scroll body holds the transcript and the sticky composer seat
|
||||
so wheel over the footer moves the flow. */
|
||||
.root[data-phase='active'] {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -157,13 +164,12 @@
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.root[data-phase='active'] .composerStack {
|
||||
.root[data-phase='active'] .composerSeat {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
/* Above markdown CodeBlock sticky banners (z-index 6) so the footer never
|
||||
paints under a sticking code header while scrolling. */
|
||||
z-index: 7;
|
||||
flex: none;
|
||||
background: var(--dsw-alias-bg-base);
|
||||
}
|
||||
|
||||
@@ -212,8 +218,8 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Settling (session replaying, hero/docked unknown): keep the composer
|
||||
/* Settling (session replaying, hero/docked unknown): keep the composer seat
|
||||
mounted but invisible so no wrong layout flashes before the phase lands. */
|
||||
.root[data-phase='settling'] .composerStack {
|
||||
.root[data-phase='settling'] .composerSeat {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -128,14 +128,24 @@ export function ConversationRoot({
|
||||
{ fallback: composerBar, overlay: true },
|
||||
)
|
||||
|
||||
// Sticky wraps the whole chain output (fallback + elected overlay), not
|
||||
// only `.composerStack`: overlay:true renders those as siblings, and sticky
|
||||
// on the fallback alone would leave Question/Approval panels at the content
|
||||
// end off-screen when the user is not pinned to the floor.
|
||||
const composerSeat = (
|
||||
<div className={css.composerSeat} data-composer-seat="">
|
||||
{composer}
|
||||
</div>
|
||||
)
|
||||
|
||||
// Header stays column chrome above this scrollport; the sticky composer
|
||||
// lives inside it with the transcript. Always wrap while a session exists
|
||||
// (hero/settling/active) so the composer keeps one tree seat across the
|
||||
// blank → active flip — relocating it only in active remounted the textarea.
|
||||
// seat lives inside it with the transcript. Always wrap while a session
|
||||
// exists (hero/settling/active) so the composer keeps one tree seat across
|
||||
// the blank → active flip — relocating it only in active remounted the textarea.
|
||||
const wrapActiveBody = (view: ReactNode): ReactNode => (
|
||||
<div className={css.scrollBody} data-conversation-scroll="">
|
||||
{view}
|
||||
{composer}
|
||||
{composerSeat}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -149,7 +159,7 @@ export function ConversationRoot({
|
||||
'conversation.session',
|
||||
{ wrapActiveBody },
|
||||
)}
|
||||
{sessionId === undefined ? composer : null}
|
||||
{sessionId === undefined ? composerSeat : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -75,15 +75,20 @@ export function InputBar({
|
||||
if (!locked) inputRef.current?.focus()
|
||||
}, [locked])
|
||||
|
||||
// Active conversation scrollport: never let the textarea become a nested
|
||||
// wheel target; forward delta to `[data-conversation-scroll]` instead.
|
||||
// Hero mounts have no host, so the textarea keeps native wheel scrolling.
|
||||
// Active conversation scrollport: chain the wheel. While the textarea (capped
|
||||
// at 14 lines with overflow-y:auto) can still move in this direction, keep
|
||||
// the native scroll; only at its own edge forward delta to the host so a
|
||||
// short draft never traps the gesture and a long draft stays scrollable.
|
||||
// Hero mounts have no host and keep native wheel scrolling.
|
||||
useEffect(() => {
|
||||
const el = inputRef.current
|
||||
if (el === null) return
|
||||
const onWheel = (e: WheelEvent): void => {
|
||||
const host = el.closest('[data-conversation-scroll]')
|
||||
if (!(host instanceof HTMLElement)) return
|
||||
if (!(host instanceof HTMLElement) || e.deltaY === 0) return
|
||||
const atTop = el.scrollTop <= 0
|
||||
const atEnd = el.scrollTop + el.clientHeight >= el.scrollHeight - 1
|
||||
if ((e.deltaY < 0 && !atTop) || (e.deltaY > 0 && !atEnd)) return
|
||||
e.preventDefault()
|
||||
host.scrollTop += e.deltaY
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user