🚸 Auto scroll once picture choice images are fully loaded
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.2.19",
|
"version": "0.2.20",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
@ -27,6 +27,7 @@ type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
|
|||||||
export const ChatChunk = (props: Props) => {
|
export const ChatChunk = (props: Props) => {
|
||||||
let inputRef: HTMLDivElement | undefined
|
let inputRef: HTMLDivElement | undefined
|
||||||
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(0)
|
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(0)
|
||||||
|
const [lastBubbleOffsetTop, setLastBubbleOffsetTop] = createSignal<number>()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (props.streamingMessageId) return
|
if (props.streamingMessageId) return
|
||||||
@ -48,6 +49,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
)
|
)
|
||||||
props.onScrollToBottom(bubbleOffsetTop)
|
props.onScrollToBottom(bubbleOffsetTop)
|
||||||
if (displayedMessageIndex() === props.messages.length) {
|
if (displayedMessageIndex() === props.messages.length) {
|
||||||
|
setLastBubbleOffsetTop(bubbleOffsetTop)
|
||||||
props.onAllBubblesDisplayed()
|
props.onAllBubblesDisplayed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,6 +113,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
defaultSettings.general.isInputPrefillEnabled
|
defaultSettings.general.isInputPrefillEnabled
|
||||||
}
|
}
|
||||||
hasError={props.hasError}
|
hasError={props.hasError}
|
||||||
|
onTransitionEnd={() => props.onScrollToBottom(lastBubbleOffsetTop())}
|
||||||
onSubmit={props.onSubmit}
|
onSubmit={props.onSubmit}
|
||||||
onSkip={props.onSkip}
|
onSkip={props.onSkip}
|
||||||
/>
|
/>
|
||||||
|
@ -46,6 +46,7 @@ type Props = {
|
|||||||
context: BotContext
|
context: BotContext
|
||||||
isInputPrefillEnabled: boolean
|
isInputPrefillEnabled: boolean
|
||||||
hasError: boolean
|
hasError: boolean
|
||||||
|
onTransitionEnd: () => void
|
||||||
onSubmit: (answer: string) => void
|
onSubmit: (answer: string) => void
|
||||||
onSkip: () => void
|
onSkip: () => void
|
||||||
}
|
}
|
||||||
@ -102,6 +103,7 @@ export const InputChatBlock = (props: Props) => {
|
|||||||
block={props.block}
|
block={props.block}
|
||||||
inputIndex={props.inputIndex}
|
inputIndex={props.inputIndex}
|
||||||
isInputPrefillEnabled={props.isInputPrefillEnabled}
|
isInputPrefillEnabled={props.isInputPrefillEnabled}
|
||||||
|
onTransitionEnd={props.onTransitionEnd}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onSkip={handleSkip}
|
onSkip={handleSkip}
|
||||||
/>
|
/>
|
||||||
@ -116,6 +118,7 @@ const Input = (props: {
|
|||||||
block: NonNullable<ContinueChatResponse['input']>
|
block: NonNullable<ContinueChatResponse['input']>
|
||||||
inputIndex: number
|
inputIndex: number
|
||||||
isInputPrefillEnabled: boolean
|
isInputPrefillEnabled: boolean
|
||||||
|
onTransitionEnd: () => void
|
||||||
onSubmit: (answer: InputSubmitContent) => void
|
onSubmit: (answer: InputSubmitContent) => void
|
||||||
onSkip: (label: string) => void
|
onSkip: (label: string) => void
|
||||||
}) => {
|
}) => {
|
||||||
@ -208,6 +211,7 @@ const Input = (props: {
|
|||||||
defaultItems={block.items}
|
defaultItems={block.items}
|
||||||
options={block.options}
|
options={block.options}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
|
onTransitionEnd={props.onTransitionEnd}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={block.options?.isMultipleChoice}>
|
<Match when={block.options?.isMultipleChoice}>
|
||||||
@ -215,6 +219,7 @@ const Input = (props: {
|
|||||||
defaultItems={block.items}
|
defaultItems={block.items}
|
||||||
options={block.options}
|
options={block.options}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
|
onTransitionEnd={props.onTransitionEnd}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
@ -245,6 +250,7 @@ const Input = (props: {
|
|||||||
} as PaymentInputBlock['options'] & RuntimeOptions
|
} as PaymentInputBlock['options'] & RuntimeOptions
|
||||||
}
|
}
|
||||||
onSuccess={submitPaymentSuccess}
|
onSuccess={submitPaymentSuccess}
|
||||||
|
onTransitionEnd={props.onTransitionEnd}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
@ -6,6 +6,7 @@ type Props = {
|
|||||||
context: BotContext
|
context: BotContext
|
||||||
options: PaymentInputBlock['options'] & RuntimeOptions
|
options: PaymentInputBlock['options'] & RuntimeOptions
|
||||||
onSuccess: () => void
|
onSuccess: () => void
|
||||||
|
onTransitionEnd: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PaymentForm = (props: Props) => (
|
export const PaymentForm = (props: Props) => (
|
||||||
@ -13,5 +14,6 @@ export const PaymentForm = (props: Props) => (
|
|||||||
onSuccess={props.onSuccess}
|
onSuccess={props.onSuccess}
|
||||||
options={props.options}
|
options={props.options}
|
||||||
context={props.context}
|
context={props.context}
|
||||||
|
onTransitionEnd={props.onTransitionEnd}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ type Props = {
|
|||||||
context: BotContext
|
context: BotContext
|
||||||
options: PaymentInputBlock['options'] & RuntimeOptions
|
options: PaymentInputBlock['options'] & RuntimeOptions
|
||||||
onSuccess: () => void
|
onSuccess: () => void
|
||||||
|
onTransitionEnd: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotName = 'stripe-payment-form'
|
const slotName = 'stripe-payment-form'
|
||||||
@ -48,7 +49,10 @@ export const StripePaymentForm = (props: Props) => {
|
|||||||
layout: 'tabs',
|
layout: 'tabs',
|
||||||
})
|
})
|
||||||
paymentElement.mount('#payment-element')
|
paymentElement.mount('#payment-element')
|
||||||
setTimeout(() => setIsMounted(true), 1000)
|
setTimeout(() => {
|
||||||
|
setIsMounted(true)
|
||||||
|
props.onTransitionEnd()
|
||||||
|
}, 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSubmit = async (event: Event & { submitter: HTMLElement }) => {
|
const handleSubmit = async (event: Event & { submitter: HTMLElement }) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { InputSubmitContent } from '@/types'
|
import { InputSubmitContent } from '@/types'
|
||||||
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
||||||
import { For, Show, createSignal, onMount } from 'solid-js'
|
import { For, Show, createEffect, createSignal, onMount } from 'solid-js'
|
||||||
import { Checkbox } from '../buttons/components/Checkbox'
|
import { Checkbox } from '../buttons/components/Checkbox'
|
||||||
import { SendButton } from '@/components'
|
import { SendButton } from '@/components'
|
||||||
import { isDefined, isEmpty, isSvgSrc } from '@typebot.io/lib'
|
import { isDefined, isEmpty, isSvgSrc } from '@typebot.io/lib'
|
||||||
@ -12,12 +12,14 @@ type Props = {
|
|||||||
defaultItems: PictureChoiceBlock['items']
|
defaultItems: PictureChoiceBlock['items']
|
||||||
options: PictureChoiceBlock['options']
|
options: PictureChoiceBlock['options']
|
||||||
onSubmit: (value: InputSubmitContent) => void
|
onSubmit: (value: InputSubmitContent) => void
|
||||||
|
onTransitionEnd: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MultiplePictureChoice = (props: Props) => {
|
export const MultiplePictureChoice = (props: Props) => {
|
||||||
let inputRef: HTMLInputElement | undefined
|
let inputRef: HTMLInputElement | undefined
|
||||||
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
|
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
|
||||||
const [selectedItemIds, setSelectedItemIds] = createSignal<string[]>([])
|
const [selectedItemIds, setSelectedItemIds] = createSignal<string[]>([])
|
||||||
|
const [totalLoadedImages, setTotalLoadedImages] = createSignal(0)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!isMobile() && inputRef) inputRef.focus()
|
if (!isMobile() && inputRef) inputRef.focus()
|
||||||
@ -64,6 +66,18 @@ export const MultiplePictureChoice = (props: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (
|
||||||
|
totalLoadedImages() ===
|
||||||
|
props.defaultItems.filter((item) => isDefined(item.pictureSrc)).length
|
||||||
|
)
|
||||||
|
props.onTransitionEnd()
|
||||||
|
})
|
||||||
|
|
||||||
|
const onImageLoad = () => {
|
||||||
|
setTotalLoadedImages((acc) => acc + 1)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form class="flex flex-col gap-2 w-full items-end" onSubmit={handleSubmit}>
|
<form class="flex flex-col gap-2 w-full items-end" onSubmit={handleSubmit}>
|
||||||
<Show when={props.options?.isSearchable}>
|
<Show when={props.options?.isSearchable}>
|
||||||
@ -112,6 +126,7 @@ export const MultiplePictureChoice = (props: Props) => {
|
|||||||
elementtiming={`Picture choice ${index() + 1}`}
|
elementtiming={`Picture choice ${index() + 1}`}
|
||||||
fetchpriority={'high'}
|
fetchpriority={'high'}
|
||||||
class="m-auto"
|
class="m-auto"
|
||||||
|
onLoad={onImageLoad}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import { SearchInput } from '@/components/inputs/SearchInput'
|
import { SearchInput } from '@/components/inputs/SearchInput'
|
||||||
import { InputSubmitContent } from '@/types'
|
import { InputSubmitContent } from '@/types'
|
||||||
import { isMobile } from '@/utils/isMobileSignal'
|
import { isMobile } from '@/utils/isMobileSignal'
|
||||||
import { isSvgSrc } from '@typebot.io/lib/utils'
|
import { isDefined, isSvgSrc } from '@typebot.io/lib/utils'
|
||||||
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
||||||
import { For, Show, createSignal, onMount } from 'solid-js'
|
import { For, Show, createEffect, createSignal, onMount } from 'solid-js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
defaultItems: PictureChoiceBlock['items']
|
defaultItems: PictureChoiceBlock['items']
|
||||||
options: PictureChoiceBlock['options']
|
options: PictureChoiceBlock['options']
|
||||||
onSubmit: (value: InputSubmitContent) => void
|
onSubmit: (value: InputSubmitContent) => void
|
||||||
|
onTransitionEnd: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SinglePictureChoice = (props: Props) => {
|
export const SinglePictureChoice = (props: Props) => {
|
||||||
let inputRef: HTMLInputElement | undefined
|
let inputRef: HTMLInputElement | undefined
|
||||||
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
|
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
|
||||||
|
const [totalLoadedImages, setTotalLoadedImages] = createSignal(0)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!isMobile() && inputRef) inputRef.focus()
|
if (!isMobile() && inputRef) inputRef.focus()
|
||||||
@ -41,6 +43,18 @@ export const SinglePictureChoice = (props: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (
|
||||||
|
totalLoadedImages() ===
|
||||||
|
props.defaultItems.filter((item) => isDefined(item.pictureSrc)).length
|
||||||
|
)
|
||||||
|
props.onTransitionEnd()
|
||||||
|
})
|
||||||
|
|
||||||
|
const onImageLoad = () => {
|
||||||
|
setTotalLoadedImages((acc) => acc + 1)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-2 w-full">
|
<div class="flex flex-col gap-2 w-full">
|
||||||
<Show when={props.options?.isSearchable}>
|
<Show when={props.options?.isSearchable}>
|
||||||
@ -77,6 +91,7 @@ export const SinglePictureChoice = (props: Props) => {
|
|||||||
elementtiming={`Picture choice ${index() + 1}`}
|
elementtiming={`Picture choice ${index() + 1}`}
|
||||||
fetchpriority={'high'}
|
fetchpriority={'high'}
|
||||||
class="m-auto"
|
class="m-auto"
|
||||||
|
onLoad={onImageLoad}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/nextjs",
|
"name": "@typebot.io/nextjs",
|
||||||
"version": "0.2.19",
|
"version": "0.2.20",
|
||||||
"description": "Convenient library to display typebots on your Next.js website",
|
"description": "Convenient library to display typebots on your Next.js website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.2.19",
|
"version": "0.2.20",
|
||||||
"description": "Convenient library to display typebots on your React app",
|
"description": "Convenient library to display typebots on your React app",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
Reference in New Issue
Block a user