🚸 Improve auto scroll behavior
Only trigger when the last element is entirely visible
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.2.75",
|
||||
"version": "0.2.76",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
@ -22,7 +22,7 @@ type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
|
||||
streamingMessageId: ChatChunkType['streamingMessageId']
|
||||
isTransitionDisabled?: boolean
|
||||
onNewBubbleDisplayed: (blockId: string) => Promise<void>
|
||||
onScrollToBottom: (top?: number) => void
|
||||
onScrollToBottom: (ref?: HTMLDivElement, offset?: number) => void
|
||||
onSubmit: (input?: string) => void
|
||||
onSkip: () => void
|
||||
onAllBubblesDisplayed: () => void
|
||||
@ -33,19 +33,17 @@ export const ChatChunk = (props: Props) => {
|
||||
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(
|
||||
props.isTransitionDisabled ? props.messages.length : 0
|
||||
)
|
||||
const [lastBubbleOffsetTop, setLastBubbleOffsetTop] = createSignal<number>()
|
||||
const [lastBubble, setLastBubble] = createSignal<HTMLDivElement>()
|
||||
|
||||
onMount(() => {
|
||||
if (props.streamingMessageId) return
|
||||
if (props.messages.length === 0) {
|
||||
props.onAllBubblesDisplayed()
|
||||
}
|
||||
props.onScrollToBottom(
|
||||
inputRef?.offsetTop ? inputRef?.offsetTop - 50 : undefined
|
||||
)
|
||||
props.onScrollToBottom(inputRef, 50)
|
||||
})
|
||||
|
||||
const displayNextMessage = async (bubbleOffsetTop?: number) => {
|
||||
const displayNextMessage = async (bubbleRef?: HTMLDivElement) => {
|
||||
if (
|
||||
(props.settings.typingEmulation?.delayBetweenBubbles ??
|
||||
defaultSettings.typingEmulation.delayBetweenBubbles) > 0 &&
|
||||
@ -66,9 +64,9 @@ export const ChatChunk = (props: Props) => {
|
||||
? displayedMessageIndex()
|
||||
: displayedMessageIndex() + 1
|
||||
)
|
||||
props.onScrollToBottom(bubbleOffsetTop)
|
||||
props.onScrollToBottom(bubbleRef)
|
||||
if (displayedMessageIndex() === props.messages.length) {
|
||||
setLastBubbleOffsetTop(bubbleOffsetTop)
|
||||
setLastBubble(bubbleRef)
|
||||
props.onAllBubblesDisplayed()
|
||||
}
|
||||
}
|
||||
@ -143,7 +141,7 @@ export const ChatChunk = (props: Props) => {
|
||||
defaultSettings.general.isInputPrefillEnabled
|
||||
}
|
||||
hasError={props.hasError}
|
||||
onTransitionEnd={() => props.onScrollToBottom(lastBubbleOffsetTop())}
|
||||
onTransitionEnd={() => props.onScrollToBottom(lastBubble())}
|
||||
onSubmit={props.onSubmit}
|
||||
onSkip={props.onSkip}
|
||||
/>
|
||||
|
@ -224,14 +224,27 @@ export const ConversationContainer = (props: Props) => {
|
||||
])
|
||||
}
|
||||
|
||||
const autoScrollToBottom = (offsetTop?: number) => {
|
||||
const chunks = chatChunks()
|
||||
const lastChunkWasStreaming =
|
||||
chunks.length >= 2 && chunks[chunks.length - 2].streamingMessageId
|
||||
if (lastChunkWasStreaming) return
|
||||
setTimeout(() => {
|
||||
chatContainer?.scrollTo(0, offsetTop ?? chatContainer.scrollHeight)
|
||||
}, 50)
|
||||
const autoScrollToBottom = (lastElement?: HTMLDivElement, offset = 0) => {
|
||||
if (!chatContainer) return
|
||||
if (!lastElement) {
|
||||
setTimeout(() => {
|
||||
chatContainer?.scrollTo(0, chatContainer.scrollHeight)
|
||||
}, 50)
|
||||
return
|
||||
}
|
||||
const lastElementRect = lastElement.getBoundingClientRect()
|
||||
const containerRect = chatContainer.getBoundingClientRect()
|
||||
const lastElementTopRelative =
|
||||
lastElementRect.top - containerRect.top + chatContainer.scrollTop
|
||||
|
||||
const isLastElementInVisibleArea =
|
||||
lastElementTopRelative < chatContainer.scrollTop + containerRect.height &&
|
||||
lastElementTopRelative + lastElementRect.height > chatContainer.scrollTop
|
||||
|
||||
if (isLastElementInVisibleArea)
|
||||
setTimeout(() => {
|
||||
chatContainer?.scrollTo(0, lastElement.offsetTop - offset)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
const handleAllBubblesDisplayed = async () => {
|
||||
|
@ -21,7 +21,7 @@ type Props = {
|
||||
message: ChatMessage
|
||||
typingEmulation: Settings['typingEmulation']
|
||||
isTypingSkipped: boolean
|
||||
onTransitionEnd?: (offsetTop?: number) => void
|
||||
onTransitionEnd?: (ref?: HTMLDivElement) => void
|
||||
onCompleted: (reply?: string) => void
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import clsx from 'clsx'
|
||||
|
||||
type Props = {
|
||||
content: AudioBubbleBlock['content']
|
||||
onTransitionEnd?: (offsetTop?: number) => void
|
||||
onTransitionEnd?: (ref?: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
const showAnimationDuration = 400
|
||||
@ -28,10 +28,7 @@ export const AudioBubble = (props: Props) => {
|
||||
if (isPlayed) return
|
||||
isPlayed = true
|
||||
setIsTyping(false)
|
||||
setTimeout(
|
||||
() => props.onTransitionEnd?.(ref?.offsetTop),
|
||||
showAnimationDuration
|
||||
)
|
||||
setTimeout(() => props.onTransitionEnd?.(ref), showAnimationDuration)
|
||||
}, typingDuration)
|
||||
})
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { botContainerHeight } from '@/utils/botContainerHeightSignal'
|
||||
|
||||
type Props = {
|
||||
content: CustomEmbedBubbleProps['content']
|
||||
onTransitionEnd?: (offsetTop?: number) => void
|
||||
onTransitionEnd?: (ref?: HTMLDivElement) => void
|
||||
onCompleted: (reply?: string) => void
|
||||
}
|
||||
|
||||
@ -43,10 +43,7 @@ export const CustomEmbedBubble = (props: Props) => {
|
||||
|
||||
typingTimeout = setTimeout(() => {
|
||||
setIsTyping(false)
|
||||
setTimeout(
|
||||
() => props.onTransitionEnd?.(ref?.offsetTop),
|
||||
showAnimationDuration
|
||||
)
|
||||
setTimeout(() => props.onTransitionEnd?.(ref), showAnimationDuration)
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { defaultEmbedBubbleContent } from '@typebot.io/schemas/features/blocks/b
|
||||
|
||||
type Props = {
|
||||
content: EmbedBubbleBlock['content']
|
||||
onTransitionEnd?: (offsetTop?: number) => void
|
||||
onTransitionEnd?: (ref?: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
let typingTimeout: NodeJS.Timeout
|
||||
@ -24,7 +24,7 @@ export const EmbedBubble = (props: Props) => {
|
||||
typingTimeout = setTimeout(() => {
|
||||
setIsTyping(false)
|
||||
setTimeout(() => {
|
||||
props.onTransitionEnd?.(ref?.offsetTop)
|
||||
props.onTransitionEnd?.(ref)
|
||||
}, showAnimationDuration)
|
||||
}, 2000)
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ import { defaultImageBubbleContent } from '@typebot.io/schemas/features/blocks/b
|
||||
|
||||
type Props = {
|
||||
content: ImageBubbleBlock['content']
|
||||
onTransitionEnd?: (offsetTop?: number) => void
|
||||
onTransitionEnd?: (ref?: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
export const showAnimationDuration = 400
|
||||
@ -27,7 +27,7 @@ export const ImageBubble = (props: Props) => {
|
||||
if (!isTyping()) return
|
||||
setIsTyping(false)
|
||||
setTimeout(() => {
|
||||
props.onTransitionEnd?.(ref?.offsetTop)
|
||||
props.onTransitionEnd?.(ref)
|
||||
}, showAnimationDuration)
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ type Props = {
|
||||
content: TextBubbleBlock['content']
|
||||
typingEmulation: Settings['typingEmulation']
|
||||
isTypingSkipped: boolean
|
||||
onTransitionEnd?: (offsetTop?: number) => void
|
||||
onTransitionEnd?: (ref?: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
export const showAnimationDuration = 400
|
||||
@ -28,7 +28,7 @@ export const TextBubble = (props: Props) => {
|
||||
if (!isTyping()) return
|
||||
setIsTyping(false)
|
||||
setTimeout(() => {
|
||||
props.onTransitionEnd?.(ref?.offsetTop)
|
||||
props.onTransitionEnd?.(ref)
|
||||
}, showAnimationDuration)
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
|
||||
type Props = {
|
||||
content: VideoBubbleBlock['content']
|
||||
onTransitionEnd?: (offsetTop?: number) => void
|
||||
onTransitionEnd?: (ref?: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
export const showAnimationDuration = 400
|
||||
@ -39,7 +39,7 @@ export const VideoBubble = (props: Props) => {
|
||||
if (!isTyping()) return
|
||||
setIsTyping(false)
|
||||
setTimeout(() => {
|
||||
props.onTransitionEnd?.(ref?.offsetTop)
|
||||
props.onTransitionEnd?.(ref)
|
||||
}, showAnimationDuration)
|
||||
}, typingDuration)
|
||||
})
|
||||
|
@ -525,7 +525,6 @@ const setInputs = (
|
||||
: '0'
|
||||
)
|
||||
|
||||
console.log(hexToRgb(inputs?.border?.color ?? '').join(', '))
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.inputs.borderColor,
|
||||
hexToRgb(inputs?.border?.color ?? '').join(', ')
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.2.75",
|
||||
"version": "0.2.76",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.2.75",
|
||||
"version": "0.2.76",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
Reference in New Issue
Block a user