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

View File

@ -1,6 +1,6 @@
{
"name": "typebot-js",
"version": "2.2.13",
"version": "2.2.14",
"main": "dist/index.js",
"unpkg": "dist/index.global.js",
"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
}
export const openIframe = (
bubble: HTMLDivElement,
iframe: HTMLIFrameElement
): void => {
export const openIframe = (bubble: Element): void => {
const iframe = bubble.querySelector('.typebot-iframe') as HTMLIFrameElement
loadTypebotIfFirstOpen(iframe)
iframe.style.display = 'flex'
setTimeout(() => bubble.classList.add('iframe-opened'), 50)
bubble.classList.remove('message-opened')
}
export const closeIframe = (
bubble: HTMLDivElement,
iframe: HTMLIFrameElement
): void => {
export const closeIframe = (bubble: Element): void => {
const iframe = bubble.querySelector('.typebot-iframe') as HTMLIFrameElement
bubble.classList.remove('iframe-opened')
setTimeout(() => (iframe.style.display = 'none'), 550)
}
@ -32,3 +28,6 @@ export const loadTypebotIfFirstOpen = (iframe: HTMLIFrameElement): void => {
iframe.src = iframe.dataset.src
iframe.removeAttribute('data-src')
}
export const isIframeOpened = (bubble: Element): boolean =>
bubble.classList.contains('iframe-opened')

View File

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

View File

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

View File

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

View File

@ -1,87 +1,89 @@
import { createIframe } from "../../iframe";
import { PopupActions, PopupParams } from "../../types";
import "./style.css";
import { createIframe } from '../../iframe'
import { PopupActions, PopupParams } from '../../types'
import './style.css'
export const initPopup = (params: PopupParams): PopupActions => {
if (document.readyState !== "complete") {
window.addEventListener("load", () => initPopup(params));
return { close: () => {}, open: () => {} };
if (document.readyState !== 'complete') {
window.addEventListener('load', () => initPopup(params))
return { close: () => {}, open: () => {} }
}
const existingPopup = document.getElementById("typebot-popup");
if (existingPopup) existingPopup.remove();
const popupElement = createPopup(params);
const existingPopup = document.getElementById('typebot-popup')
if (existingPopup) existingPopup.remove()
const popupElement = createPopup(params)
!document.body
? (window.onload = () => document.body.append(popupElement))
: document.body.append(popupElement);
: document.body.append(popupElement)
return {
open: () => openPopup(popupElement),
close: () => closePopup(popupElement),
};
};
}
}
const createPopup = (params: PopupParams): HTMLElement => {
const { delay } = params;
const overlayElement = createOverlayElement(delay);
listenForOutsideClicks(overlayElement);
const { delay } = params
const overlayElement = createOverlayElement(delay)
listenForOutsideClicks(overlayElement)
const iframeElement = createIframe({
...params,
loadWhenVisible: true,
});
overlayElement.appendChild(iframeElement);
return overlayElement;
};
})
overlayElement.appendChild(iframeElement)
return overlayElement
}
const createOverlayElement = (delay: number | undefined) => {
const overlayElement = document.createElement("div");
overlayElement.id = "typebot-popup";
if (delay !== undefined) setShowTimeout(overlayElement, delay);
return overlayElement;
};
const overlayElement = document.createElement('div')
overlayElement.id = 'typebot-popup'
if (delay !== undefined) setShowTimeout(overlayElement, delay)
return overlayElement
}
export const openPopup = (popupElement: HTMLElement): void => {
const iframe = popupElement.children[0] as HTMLIFrameElement;
if (iframe.dataset.src) lazyLoadSrc(iframe);
document.body.style.overflowY = "hidden";
popupElement.classList.add("opened");
};
export const openPopup = (popupElement: Element): void => {
const iframe = popupElement.children[0] as HTMLIFrameElement
if (iframe.dataset.src) lazyLoadSrc(iframe)
document.body.style.overflowY = 'hidden'
popupElement.classList.add('opened')
}
export const closePopup = (popupElement: HTMLElement): void => {
document.body.style.overflowY = "auto";
popupElement.classList.remove("opened");
};
export const closePopup = (popupElement: Element): void => {
document.body.style.overflowY = 'auto'
popupElement.classList.remove('opened')
}
export const isPopupOpened = (popupElement: Element): boolean =>
popupElement.classList.contains('opened')
const listenForOutsideClicks = (popupElement: HTMLDivElement) =>
popupElement.addEventListener("click", (e) => onPopupClick(e, popupElement));
popupElement.addEventListener('click', (e) => onPopupClick(e, popupElement))
const onPopupClick = (e: Event, popupElement: HTMLDivElement) => {
e.preventDefault();
const clickedElement = e.target as HTMLElement;
if (clickedElement.tagName !== "iframe") closePopup(popupElement);
};
e.preventDefault()
const clickedElement = e.target as HTMLElement
if (clickedElement.tagName !== 'iframe') closePopup(popupElement)
}
const setShowTimeout = (overlayElement: HTMLDivElement, delay: number) => {
setTimeout(() => {
openPopup(overlayElement);
}, delay);
};
openPopup(overlayElement)
}, delay)
}
const lazyLoadSrc = (iframe: HTMLIFrameElement) => {
iframe.src = iframe.dataset.src as string;
iframe.removeAttribute("data-src");
};
iframe.src = iframe.dataset.src as string
iframe.removeAttribute('data-src')
}
export const getPopupActions = (
popupElement?: HTMLDivElement
): PopupActions => {
const existingPopupElement =
popupElement ??
(document.querySelector("#typebot-popup") as HTMLDivElement);
popupElement ?? (document.querySelector('#typebot-popup') as HTMLDivElement)
return {
open: () => {
openPopup(existingPopupElement);
openPopup(existingPopupElement)
},
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 './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 = (
data: TypebotPostMessageData,
callbacks: IframeCallbacks
@ -73,5 +64,5 @@ const processMessage = (
if (data.newVariableValue && callbacks.onNewVariableValue)
callbacks.onNewVariableValue(data.newVariableValue)
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 { initPopup, getPopupActions } from './embedTypes/popup'
import { initBubble, getBubbleActions } from './embedTypes/chat'
import { open, close, toggle, showMessage, hideMessage } from './commands'
export {
initContainer,
@ -8,6 +9,11 @@ export {
initBubble,
getPopupActions,
getBubbleActions,
open,
close,
toggle,
showMessage,
hideMessage,
}
export default {
@ -16,6 +22,11 @@ export default {
initBubble,
getPopupActions,
getBubbleActions,
open,
close,
toggle,
showMessage,
hideMessage,
}
export * from './types'