2
0

fix(lib): 🐛 Safari context menu trap

This commit is contained in:
Baptiste Arnaud
2022-04-08 18:38:36 -05:00
parent fb3bba897f
commit 144ffaeb03
8 changed files with 41 additions and 41 deletions

View File

@@ -16,19 +16,19 @@
<body> <body>
<script> <script>
const { open } = Typebot.initBubble({ const { open } = Typebot.initBubble({
publishId: "feedback-form", url: 'https://typebot.io/typebot-support',
button: { button: {
color: "green", color: 'green',
iconUrl: "https://image.flaticon.com/icons/png/512/5138/5138352.png", iconUrl: 'https://image.flaticon.com/icons/png/512/5138/5138352.png',
}, },
proactiveMessage: { proactiveMessage: {
avatarUrl: avatarUrl:
"https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
textContent: "Hey what's up?", textContent: "Hey what's up?",
delay: 1000, delay: 1000,
rememberClose: true, rememberClose: true,
}, },
}); })
</script> </script>
<div style="width: 100%; height: 300vh; background-color: aquamarine"> <div style="width: 100%; height: 300vh; background-color: aquamarine">
<button onclick="(()=>{open()})()" style="width: 200px; height: 60px"> <button onclick="(()=>{open()})()" style="width: 200px; height: 60px">

View File

@@ -1,6 +1,6 @@
{ {
"name": "typebot-js", "name": "typebot-js",
"version": "2.2.2", "version": "2.2.3",
"main": "dist/index.js", "main": "dist/index.js",
"unpkg": "dist/index.umd.min.js", "unpkg": "dist/index.umd.min.js",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",

View File

@@ -1,28 +1,33 @@
import { createIframe } from "../../iframe"; import { createIframe } from '../../iframe'
import { IframeParams } from "../../types"; import { IframeParams } from '../../types'
export const createIframeContainer = ( export const createIframeContainer = (
params: IframeParams params: IframeParams
): HTMLIFrameElement => { ): HTMLIFrameElement => {
const iframe = createIframe({ ...params, loadWhenVisible: true }); const iframe = createIframe({ ...params, loadWhenVisible: true })
return iframe; return iframe
}; }
export const openIframe = ( export const openIframe = (
bubble: HTMLDivElement, bubble: HTMLDivElement,
iframe: HTMLIFrameElement iframe: HTMLIFrameElement
): void => { ): void => {
loadTypebotIfFirstOpen(iframe); loadTypebotIfFirstOpen(iframe)
bubble.classList.add("iframe-opened"); iframe.style.display = 'flex'
bubble.classList.remove("message-opened"); setTimeout(() => bubble.classList.add('iframe-opened'), 50)
}; bubble.classList.remove('message-opened')
}
export const closeIframe = (bubble: HTMLDivElement): void => { export const closeIframe = (
bubble.classList.remove("iframe-opened"); bubble: HTMLDivElement,
}; iframe: HTMLIFrameElement
): void => {
bubble.classList.remove('iframe-opened')
setTimeout(() => (iframe.style.display = 'none'), 550)
}
export const loadTypebotIfFirstOpen = (iframe: HTMLIFrameElement): void => { export const loadTypebotIfFirstOpen = (iframe: HTMLIFrameElement): void => {
if (!iframe.dataset.src) return; if (!iframe.dataset.src) return
iframe.src = iframe.dataset.src; iframe.src = iframe.dataset.src
iframe.removeAttribute("data-src"); iframe.removeAttribute('data-src')
}; }

View File

@@ -60,9 +60,11 @@ const createBubble = (
? addProactiveMessage(params.proactiveMessage, bubbleElement) ? addProactiveMessage(params.proactiveMessage, bubbleElement)
: undefined : undefined
const iframeElement = createIframeContainer(params) const iframeElement = createIframeContainer(params)
buttonElement.addEventListener('click', () => buttonElement.addEventListener('click', () => {
onBubbleButtonClick(bubbleElement, iframeElement) iframeElement.style.display === 'none'
) ? openIframe(bubbleElement, iframeElement)
: closeIframe(bubbleElement, iframeElement)
})
if (proactiveMessageElement) if (proactiveMessageElement)
proactiveMessageElement.addEventListener('click', () => proactiveMessageElement.addEventListener('click', () =>
onProactiveMessageClick(bubbleElement, iframeElement) onProactiveMessageClick(bubbleElement, iframeElement)
@@ -71,15 +73,6 @@ const createBubble = (
return { bubbleElement, proactiveMessageElement, iframeElement } return { bubbleElement, proactiveMessageElement, iframeElement }
} }
const onBubbleButtonClick = (
bubble: HTMLDivElement,
iframe: HTMLIFrameElement
): void => {
loadTypebotIfFirstOpen(iframe)
bubble.classList.toggle('iframe-opened')
bubble.classList.remove('message-opened')
}
const onProactiveMessageClick = ( const onProactiveMessageClick = (
bubble: HTMLDivElement, bubble: HTMLDivElement,
iframe: HTMLIFrameElement iframe: HTMLIFrameElement
@@ -115,7 +108,7 @@ export const getBubbleActions = (
openIframe(existingBubbleElement, existingIframeElement) openIframe(existingBubbleElement, existingIframeElement)
}, },
close: () => { close: () => {
closeIframe(existingBubbleElement) closeIframe(existingBubbleElement, existingIframeElement)
}, },
} }
} }

View File

@@ -61,7 +61,6 @@
} }
#typebot-bubble > iframe { #typebot-bubble > iframe {
visibility: hidden;
opacity: 0; opacity: 0;
display: flex; display: flex;
border-radius: 10px; border-radius: 10px;
@@ -83,7 +82,6 @@
#typebot-bubble.iframe-opened > iframe { #typebot-bubble.iframe-opened > iframe {
transform: translate(0, 0); transform: translate(0, 0);
visibility: visible;
opacity: 1; opacity: 1;
} }

View File

@@ -18,6 +18,7 @@ export const createIframe = ({
iframe.classList.add('typebot-iframe') iframe.classList.add('typebot-iframe')
const { onNewVariableValue, onVideoPlayed } = iframeParams const { onNewVariableValue, onVideoPlayed } = iframeParams
listenForTypebotMessages({ onNewVariableValue, onVideoPlayed }) listenForTypebotMessages({ onNewVariableValue, onVideoPlayed })
iframe.style.display = 'none'
return iframe return iframe
} }

View File

@@ -5,7 +5,7 @@ beforeEach(() => {
}) })
describe('openBubble', () => { describe('openBubble', () => {
it('should add the opened bubble', () => { it('should add the opened bubble', async () => {
expect.assertions(3) expect.assertions(3)
const { open } = Typebot.initBubble({ const { open } = Typebot.initBubble({
url: 'https://typebot.io/typebot-id', url: 'https://typebot.io/typebot-id',
@@ -13,6 +13,7 @@ describe('openBubble', () => {
const bubble = document.getElementById('typebot-bubble') as HTMLDivElement const bubble = document.getElementById('typebot-bubble') as HTMLDivElement
expect(bubble.classList.contains('iframe-opened')).toBe(false) expect(bubble.classList.contains('iframe-opened')).toBe(false)
open() open()
await new Promise((resolve) => setTimeout(resolve, 50))
expect(bubble.classList.contains('iframe-opened')).toBe(true) expect(bubble.classList.contains('iframe-opened')).toBe(true)
expect(open).not.toThrow() expect(open).not.toThrow()
}) })
@@ -35,13 +36,14 @@ describe('openBubble', () => {
}) })
describe('closeBubble', () => { describe('closeBubble', () => {
it('should remove the corresponding class', () => { it('should remove the corresponding class', async () => {
expect.assertions(2) expect.assertions(2)
const { close, open } = Typebot.initBubble({ const { close, open } = Typebot.initBubble({
url: 'https://typebot.io/typebot-id', url: 'https://typebot.io/typebot-id',
}) })
open() open()
const bubble = document.getElementById('typebot-bubble') as HTMLDivElement const bubble = document.getElementById('typebot-bubble') as HTMLDivElement
await new Promise((resolve) => setTimeout(resolve, 50))
expect(bubble.classList.contains('iframe-opened')).toBe(true) expect(bubble.classList.contains('iframe-opened')).toBe(true)
close() close()
expect(bubble.classList.contains('iframe-opened')).toBe(false) expect(bubble.classList.contains('iframe-opened')).toBe(false)
@@ -72,7 +74,7 @@ describe('openProactiveMessage', () => {
}) })
describe('Request commands afterwards', () => { describe('Request commands afterwards', () => {
it('should return defined commands', () => { it('should return defined commands', async () => {
Typebot.initBubble({ Typebot.initBubble({
proactiveMessage: { proactiveMessage: {
textContent: 'Hi click here!', textContent: 'Hi click here!',
@@ -85,6 +87,7 @@ describe('Request commands afterwards', () => {
expect(open).toBeDefined() expect(open).toBeDefined()
expect(openProactiveMessage).toBeDefined() expect(openProactiveMessage).toBeDefined()
open() open()
await new Promise((resolve) => setTimeout(resolve, 50))
const bubble = document.getElementById('typebot-bubble') as HTMLDivElement const bubble = document.getElementById('typebot-bubble') as HTMLDivElement
expect(bubble.classList.contains('iframe-opened')).toBe(true) expect(bubble.classList.contains('iframe-opened')).toBe(true)
}) })

View File

@@ -36,7 +36,7 @@ describe('initBubble', () => {
}) })
const bubble = document.querySelector('#typebot-bubble') as HTMLDivElement const bubble = document.querySelector('#typebot-bubble') as HTMLDivElement
expect(bubble.classList.contains('iframe-opened')).toBe(false) expect(bubble.classList.contains('iframe-opened')).toBe(false)
await new Promise((r) => setTimeout(r, 1000)) await new Promise((r) => setTimeout(r, 1050))
expect(bubble.classList.contains('iframe-opened')).toBe(true) expect(bubble.classList.contains('iframe-opened')).toBe(true)
const rememberCloseDecisionFromStorage = localStorage.getItem( const rememberCloseDecisionFromStorage = localStorage.getItem(
Typebot.localStorageKeys.rememberClose Typebot.localStorageKeys.rememberClose