2
0

🧑‍💻 (typebot-js) Implement easier commands: open / close / toggle

This commit is contained in:
Baptiste Arnaud
2022-11-15 16:24:14 +01:00
parent 963072f8c0
commit 087d24e587
15 changed files with 165 additions and 103 deletions

View File

@ -31,7 +31,7 @@ Here is an example:
```html ```html
<script src="https://unpkg.com/typebot-js@2.2"></script> <script src="https://unpkg.com/typebot-js@2.2"></script>
<script> <script>
var typebotCommands = Typebot.initPopup({ Typebot.initPopup({
url: 'https://viewer.typebot.io/my-typebot', url: 'https://viewer.typebot.io/my-typebot',
delay: 3000, delay: 3000,
}) })
@ -45,17 +45,21 @@ This code will automatically trigger the popup window after 3 seconds.
You can use these commands: You can use these commands:
```js ```js
Typebot.getPopupActions().open() Typebot.open()
``` ```
```js ```js
Typebot.getPopupActions().close() Typebot.close()
```
```js
Typebot.toggle()
``` ```
You can bind these commands on a button element, for example: You can bind these commands on a button element, for example:
```html ```html
<button onclick="Typebot.getPopupActions().open()">Open the popup</button> <button onclick="Typebot.open()">Contact us</button>
``` ```
## Bubble ## Bubble
@ -76,20 +80,22 @@ Here is an example:
This code will automatically trigger the popup window after 3 seconds. This code will automatically trigger the popup window after 3 seconds.
### Open or close the proactive message ### Open or close the preview message
You can use this command: You can use these commands:
```js ```js
Typebot.getBubbleActions().openProactiveMessage() Typebot.showMessage()
```
```js
Typebot.hideMessage()
``` ```
You can bind this command on a button element, for example: You can bind this command on a button element, for example:
```html ```html
<button onclick="Typebot.getBubbleActions().openProactiveMessage()"> <button onclick="Typebot.showMessage()">Open message</button>
Open proactive message
</button>
``` ```
### Open or close the typebot ### Open or close the typebot
@ -97,17 +103,21 @@ You can bind this command on a button element, for example:
You can use these commands: You can use these commands:
```js ```js
Typebot.getBubbleActions().open() Typebot.open()
``` ```
```js ```js
Typebot.getBubbleActions().close() Typebot.close()
```
```js
Typebot.toggle()
``` ```
You can bind these commands on a button element, for example: You can bind these commands on a button element, for example:
```html ```html
<button onclick="Typebot.getBubbleActions().open()">Open the chat</button> <button onclick="Typebot.open()">Contact us</button>
``` ```
## Additional configuration ## Additional configuration

View File

@ -1,6 +1,6 @@
{ {
"name": "typebot-js", "name": "typebot-js",
"version": "2.2.13", "version": "2.2.14",
"main": "dist/index.js", "main": "dist/index.js",
"unpkg": "dist/index.global.js", "unpkg": "dist/index.global.js",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",

View File

@ -0,0 +1,9 @@
import { closeIframe } from '../embedTypes/chat/iframe'
import { closePopup } from '../embedTypes/popup'
export const close = () => {
const existingPopup = document.querySelector('#typebot-popup')
if (existingPopup) closePopup(existingPopup)
const existingBubble = document.querySelector('#typebot-bubble')
if (existingBubble) closeIframe(existingBubble)
}

View File

@ -0,0 +1,6 @@
import { closeProactiveMessage } from '../embedTypes/chat/proactiveMessage'
export const hideMessage = () => {
const existingBubble = document.querySelector('#typebot-bubble')
if (existingBubble) closeProactiveMessage(existingBubble)
}

View File

@ -0,0 +1,5 @@
export * from './close'
export * from './hideMessage'
export * from './open'
export * from './showMessage'
export * from './toggle'

View File

@ -0,0 +1,9 @@
import { openIframe } from '../embedTypes/chat/iframe'
import { openPopup } from '../embedTypes/popup'
export const open = () => {
const existingPopup = document.querySelector('#typebot-popup')
if (existingPopup) openPopup(existingPopup)
const existingBubble = document.querySelector('#typebot-bubble')
if (existingBubble) openIframe(existingBubble)
}

View File

@ -0,0 +1,6 @@
import { openProactiveMessage } from '../embedTypes/chat/proactiveMessage'
export const showMessage = () => {
const existingBubble = document.querySelector('#typebot-bubble')
if (existingBubble) openProactiveMessage(existingBubble)
}

View File

@ -0,0 +1,20 @@
import {
closeIframe,
isIframeOpened,
openIframe,
} from '../embedTypes/chat/iframe'
import { closePopup, isPopupOpened, openPopup } from '../embedTypes/popup'
export const toggle = () => {
const existingPopup = document.querySelector('#typebot-popup')
if (existingPopup)
isPopupOpened(existingPopup)
? closePopup(existingPopup)
: openPopup(existingPopup)
const existingBubble = document.querySelector('#typebot-bubble')
console.log(existingBubble)
if (existingBubble)
isIframeOpened(existingBubble)
? closeIframe(existingBubble)
: openIframe(existingBubble)
}

View File

@ -9,20 +9,16 @@ export const createIframeContainer = (
return iframe return iframe
} }
export const openIframe = ( export const openIframe = (bubble: Element): void => {
bubble: HTMLDivElement, const iframe = bubble.querySelector('.typebot-iframe') as HTMLIFrameElement
iframe: HTMLIFrameElement
): void => {
loadTypebotIfFirstOpen(iframe) loadTypebotIfFirstOpen(iframe)
iframe.style.display = 'flex' iframe.style.display = 'flex'
setTimeout(() => bubble.classList.add('iframe-opened'), 50) setTimeout(() => bubble.classList.add('iframe-opened'), 50)
bubble.classList.remove('message-opened') bubble.classList.remove('message-opened')
} }
export const closeIframe = ( export const closeIframe = (bubble: Element): void => {
bubble: HTMLDivElement, const iframe = bubble.querySelector('.typebot-iframe') as HTMLIFrameElement
iframe: HTMLIFrameElement
): void => {
bubble.classList.remove('iframe-opened') bubble.classList.remove('iframe-opened')
setTimeout(() => (iframe.style.display = 'none'), 550) setTimeout(() => (iframe.style.display = 'none'), 550)
} }
@ -32,3 +28,6 @@ export const loadTypebotIfFirstOpen = (iframe: HTMLIFrameElement): void => {
iframe.src = iframe.dataset.src iframe.src = iframe.dataset.src
iframe.removeAttribute('data-src') iframe.removeAttribute('data-src')
} }
export const isIframeOpened = (bubble: Element): boolean =>
bubble.classList.contains('iframe-opened')

View File

@ -5,12 +5,7 @@ import {
ProactiveMessageParams, ProactiveMessageParams,
} from '../../types' } from '../../types'
import { createButton } from './button' import { createButton } from './button'
import { import { closeIframe, createIframeContainer, openIframe } from './iframe'
closeIframe,
createIframeContainer,
loadTypebotIfFirstOpen,
openIframe,
} from './iframe'
import { import {
createProactiveMessage, createProactiveMessage,
openProactiveMessage, openProactiveMessage,
@ -33,10 +28,7 @@ export const initBubble = (params: BubbleParams): BubbleActions => {
!hasBeenClosed() !hasBeenClosed()
) { ) {
setRememberCloseInStorage() setRememberCloseInStorage()
setTimeout( setTimeout(() => openIframe(bubbleElement), params.autoOpenDelay)
() => openIframe(bubbleElement, iframeElement),
params.autoOpenDelay
)
} }
!document.body !document.body
? (window.onload = () => document.body.appendChild(bubbleElement)) ? (window.onload = () => document.body.appendChild(bubbleElement))
@ -62,8 +54,8 @@ const createBubble = (
const iframeElement = createIframeContainer(params) const iframeElement = createIframeContainer(params)
buttonElement.addEventListener('click', () => { buttonElement.addEventListener('click', () => {
iframeElement.style.display === 'none' iframeElement.style.display === 'none'
? openIframe(bubbleElement, iframeElement) ? openIframe(bubbleElement)
: closeIframe(bubbleElement, iframeElement) : closeIframe(bubbleElement)
}) })
if (proactiveMessageElement) if (proactiveMessageElement)
proactiveMessageElement.addEventListener('click', () => proactiveMessageElement.addEventListener('click', () =>
@ -77,9 +69,7 @@ const onProactiveMessageClick = (
bubble: HTMLDivElement, bubble: HTMLDivElement,
iframe: HTMLIFrameElement iframe: HTMLIFrameElement
): void => { ): void => {
iframe.style.display === 'none' iframe.style.display === 'none' ? openIframe(bubble) : closeIframe(bubble)
? openIframe(bubble, iframe)
: closeIframe(bubble, iframe)
bubble.classList.remove('message-opened') bubble.classList.remove('message-opened')
} }
@ -107,10 +97,10 @@ export const getBubbleActions = (
} }
: undefined, : undefined,
open: () => { open: () => {
openIframe(existingBubbleElement, existingIframeElement) openIframe(existingBubbleElement)
}, },
close: () => { close: () => {
closeIframe(existingBubbleElement, existingIframeElement) closeIframe(existingBubbleElement)
}, },
} }
} }

View File

@ -44,7 +44,7 @@ const createCloseButton = (bubble: HTMLDivElement): HTMLButtonElement => {
return button return button
} }
const openProactiveMessage = (bubble: HTMLDivElement): void => { const openProactiveMessage = (bubble: Element): void => {
bubble.classList.add('message-opened') bubble.classList.add('message-opened')
} }
@ -56,7 +56,7 @@ const onCloseButtonClick = (
closeProactiveMessage(proactiveMessageElement) closeProactiveMessage(proactiveMessageElement)
} }
const closeProactiveMessage = (bubble: HTMLDivElement): void => { const closeProactiveMessage = (bubble: Element): void => {
setRememberCloseInStorage() setRememberCloseInStorage()
bubble.classList.remove('message-opened') bubble.classList.remove('message-opened')
} }

View File

@ -30,6 +30,10 @@
filter: brightness(0.75); filter: brightness(0.75);
} }
#typebot-bubble > button:focus {
outline: none;
}
#typebot-bubble > button > .icon { #typebot-bubble > button > .icon {
transition: opacity 500ms ease-out 0s, transform 500ms ease-out 0s; transition: opacity 500ms ease-out 0s, transform 500ms ease-out 0s;
} }

View File

@ -1,87 +1,89 @@
import { createIframe } from "../../iframe"; import { createIframe } from '../../iframe'
import { PopupActions, PopupParams } from "../../types"; import { PopupActions, PopupParams } from '../../types'
import "./style.css"; import './style.css'
export const initPopup = (params: PopupParams): PopupActions => { export const initPopup = (params: PopupParams): PopupActions => {
if (document.readyState !== "complete") { if (document.readyState !== 'complete') {
window.addEventListener("load", () => initPopup(params)); window.addEventListener('load', () => initPopup(params))
return { close: () => {}, open: () => {} }; return { close: () => {}, open: () => {} }
} }
const existingPopup = document.getElementById("typebot-popup"); const existingPopup = document.getElementById('typebot-popup')
if (existingPopup) existingPopup.remove(); if (existingPopup) existingPopup.remove()
const popupElement = createPopup(params); const popupElement = createPopup(params)
!document.body !document.body
? (window.onload = () => document.body.append(popupElement)) ? (window.onload = () => document.body.append(popupElement))
: document.body.append(popupElement); : document.body.append(popupElement)
return { return {
open: () => openPopup(popupElement), open: () => openPopup(popupElement),
close: () => closePopup(popupElement), close: () => closePopup(popupElement),
}; }
}; }
const createPopup = (params: PopupParams): HTMLElement => { const createPopup = (params: PopupParams): HTMLElement => {
const { delay } = params; const { delay } = params
const overlayElement = createOverlayElement(delay); const overlayElement = createOverlayElement(delay)
listenForOutsideClicks(overlayElement); listenForOutsideClicks(overlayElement)
const iframeElement = createIframe({ const iframeElement = createIframe({
...params, ...params,
loadWhenVisible: true, loadWhenVisible: true,
}); })
overlayElement.appendChild(iframeElement); overlayElement.appendChild(iframeElement)
return overlayElement; return overlayElement
}; }
const createOverlayElement = (delay: number | undefined) => { const createOverlayElement = (delay: number | undefined) => {
const overlayElement = document.createElement("div"); const overlayElement = document.createElement('div')
overlayElement.id = "typebot-popup"; overlayElement.id = 'typebot-popup'
if (delay !== undefined) setShowTimeout(overlayElement, delay); if (delay !== undefined) setShowTimeout(overlayElement, delay)
return overlayElement; return overlayElement
}; }
export const openPopup = (popupElement: HTMLElement): void => { export const openPopup = (popupElement: Element): void => {
const iframe = popupElement.children[0] as HTMLIFrameElement; const iframe = popupElement.children[0] as HTMLIFrameElement
if (iframe.dataset.src) lazyLoadSrc(iframe); if (iframe.dataset.src) lazyLoadSrc(iframe)
document.body.style.overflowY = "hidden"; document.body.style.overflowY = 'hidden'
popupElement.classList.add("opened"); popupElement.classList.add('opened')
}; }
export const closePopup = (popupElement: HTMLElement): void => { export const closePopup = (popupElement: Element): void => {
document.body.style.overflowY = "auto"; document.body.style.overflowY = 'auto'
popupElement.classList.remove("opened"); popupElement.classList.remove('opened')
}; }
export const isPopupOpened = (popupElement: Element): boolean =>
popupElement.classList.contains('opened')
const listenForOutsideClicks = (popupElement: HTMLDivElement) => const listenForOutsideClicks = (popupElement: HTMLDivElement) =>
popupElement.addEventListener("click", (e) => onPopupClick(e, popupElement)); popupElement.addEventListener('click', (e) => onPopupClick(e, popupElement))
const onPopupClick = (e: Event, popupElement: HTMLDivElement) => { const onPopupClick = (e: Event, popupElement: HTMLDivElement) => {
e.preventDefault(); e.preventDefault()
const clickedElement = e.target as HTMLElement; const clickedElement = e.target as HTMLElement
if (clickedElement.tagName !== "iframe") closePopup(popupElement); if (clickedElement.tagName !== 'iframe') closePopup(popupElement)
}; }
const setShowTimeout = (overlayElement: HTMLDivElement, delay: number) => { const setShowTimeout = (overlayElement: HTMLDivElement, delay: number) => {
setTimeout(() => { setTimeout(() => {
openPopup(overlayElement); openPopup(overlayElement)
}, delay); }, delay)
}; }
const lazyLoadSrc = (iframe: HTMLIFrameElement) => { const lazyLoadSrc = (iframe: HTMLIFrameElement) => {
iframe.src = iframe.dataset.src as string; iframe.src = iframe.dataset.src as string
iframe.removeAttribute("data-src"); iframe.removeAttribute('data-src')
}; }
export const getPopupActions = ( export const getPopupActions = (
popupElement?: HTMLDivElement popupElement?: HTMLDivElement
): PopupActions => { ): PopupActions => {
const existingPopupElement = const existingPopupElement =
popupElement ?? popupElement ?? (document.querySelector('#typebot-popup') as HTMLDivElement)
(document.querySelector("#typebot-popup") as HTMLDivElement);
return { return {
open: () => { open: () => {
openPopup(existingPopupElement); openPopup(existingPopupElement)
}, },
close: () => { close: () => {
closePopup(existingPopupElement); closePopup(existingPopupElement)
}, },
}; }
}; }

View File

@ -1,4 +1,4 @@
import { closeIframe } from '../embedTypes/chat/iframe' import { close } from '../commands'
import { TypebotPostMessageData, IframeCallbacks, IframeParams } from '../types' import { TypebotPostMessageData, IframeCallbacks, IframeParams } from '../types'
import './style.css' import './style.css'
@ -56,15 +56,6 @@ export const listenForTypebotMessages = (callbacks: IframeCallbacks) => {
}) })
} }
const closeChatBubbleIfExisting = () => {
const bubble = document.querySelector('#typebot-bubble') as
| HTMLDivElement
| undefined
if (!bubble) return
const iframe = bubble.querySelector('.typebot-iframe') as HTMLIFrameElement
closeIframe(bubble, iframe)
}
const processMessage = ( const processMessage = (
data: TypebotPostMessageData, data: TypebotPostMessageData,
callbacks: IframeCallbacks callbacks: IframeCallbacks
@ -73,5 +64,5 @@ const processMessage = (
if (data.newVariableValue && callbacks.onNewVariableValue) if (data.newVariableValue && callbacks.onNewVariableValue)
callbacks.onNewVariableValue(data.newVariableValue) callbacks.onNewVariableValue(data.newVariableValue)
if (data.codeToExecute) Function(data.codeToExecute)() if (data.codeToExecute) Function(data.codeToExecute)()
if (data.closeChatBubble) closeChatBubbleIfExisting() if (data.closeChatBubble) close()
} }

View File

@ -1,6 +1,7 @@
import { initContainer } from './embedTypes/container' import { initContainer } from './embedTypes/container'
import { initPopup, getPopupActions } from './embedTypes/popup' import { initPopup, getPopupActions } from './embedTypes/popup'
import { initBubble, getBubbleActions } from './embedTypes/chat' import { initBubble, getBubbleActions } from './embedTypes/chat'
import { open, close, toggle, showMessage, hideMessage } from './commands'
export { export {
initContainer, initContainer,
@ -8,6 +9,11 @@ export {
initBubble, initBubble,
getPopupActions, getPopupActions,
getBubbleActions, getBubbleActions,
open,
close,
toggle,
showMessage,
hideMessage,
} }
export default { export default {
@ -16,6 +22,11 @@ export default {
initBubble, initBubble,
getPopupActions, getPopupActions,
getBubbleActions, getBubbleActions,
open,
close,
toggle,
showMessage,
hideMessage,
} }
export * from './types' export * from './types'