diff --git a/__tests__/api/webchats_test.js b/__tests__/api/webchats_test.js
new file mode 100644
index 0000000..bb94ba1
--- /dev/null
+++ b/__tests__/api/webchats_test.js
@@ -0,0 +1,98 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import WebchatsAPI from '../../src/api/webchats'
+import Hellotext from '../../src/hellotext'
+import { Configuration } from '../../src/core'
+
+describe('WebchatsAPI', () => {
+ beforeEach(() => {
+ Configuration.webchat.style = {}
+ Configuration.webchat.appearance = {}
+ Configuration.webchat.whatsapp = {}
+ Configuration.webchat.placement = 'bottom-right'
+
+ Hellotext.business = {
+ id: 'business-id',
+ data: {},
+ }
+
+ jest.spyOn(Hellotext, 'session', 'get').mockReturnValue('session-123')
+
+ global.fetch = jest.fn().mockResolvedValue({
+ json: jest.fn().mockResolvedValue({
+ business: {},
+ html: '',
+ locale: 'en',
+ }),
+ })
+ })
+
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
+
+ const requestedParams = async () => {
+ await WebchatsAPI.get('webchat-id')
+
+ return new URL(global.fetch.mock.calls[0][0]).searchParams
+ }
+
+ it('serializes appearance and WhatsApp overrides for the webchat request', async () => {
+ Configuration.webchat.appearance = {
+ header: {
+ name: 'Acme Support',
+ },
+ launcher: {
+ iconUrl: 'https://example.com/icon.png',
+ },
+ }
+ Configuration.webchat.whatsapp = {
+ number: '+15551234567',
+ restrictToChannel: true,
+ }
+
+ const params = await requestedParams()
+
+ expect(params.get('webchat[appearance][header][name]')).toBe('Acme Support')
+ expect(params.get('webchat[appearance][launcher][icon_url]')).toBe('https://example.com/icon.png')
+ expect(params.get('webchat[handoff][identifier]')).toBe('+15551234567')
+ expect(params.get('webchat[handoff][restrict_to_channel]')).toBe('true')
+ })
+
+ it('serializes restrictToChannel when the supplied value is false', async () => {
+ Configuration.webchat.whatsapp = {
+ restrictToChannel: false,
+ }
+
+ const params = await requestedParams()
+
+ expect(params.get('webchat[handoff][restrict_to_channel]')).toBe('false')
+ })
+
+ it('does not serialize absent appearance or WhatsApp overrides', async () => {
+ const params = await requestedParams()
+
+ expect(params.has('webchat[appearance][header][name]')).toBe(false)
+ expect(params.has('webchat[appearance][launcher][icon_url]')).toBe(false)
+ expect(params.has('webchat[handoff][identifier]')).toBe(false)
+ expect(params.has('webchat[handoff][restrict_to_channel]')).toBe(false)
+ })
+
+ it('keeps existing style, placement, session, and locale params', async () => {
+ Configuration.webchat.style = {
+ primaryColor: '#EEEEEE',
+ typography: 'inherit',
+ }
+ Configuration.webchat.placement = 'top-left'
+
+ const params = await requestedParams()
+
+ expect(params.get('style[primaryColor]')).toBe('#EEEEEE')
+ expect(params.get('style[typography]')).toBe('inherit')
+ expect(params.get('placement')).toBe('top-left')
+ expect(params.get('session')).toBe('session-123')
+ expect(params.get('locale')).toBe('en')
+ })
+})
diff --git a/__tests__/controllers/mixins/usePopover_test.js b/__tests__/controllers/mixins/usePopover_test.js
index 71be4cc..d3ad154 100644
--- a/__tests__/controllers/mixins/usePopover_test.js
+++ b/__tests__/controllers/mixins/usePopover_test.js
@@ -65,4 +65,25 @@ describe('usePopover', () => {
strategy: 'fixed'
})
})
+
+ it('cancels a pending behaviour open before showing the popover', () => {
+ controller.cancelBehaviourOpen = jest.fn()
+
+ usePopover(controller)
+ controller.show()
+
+ expect(controller.cancelBehaviourOpen).toHaveBeenCalledTimes(1)
+ expect(controller.openValue).toBe(true)
+ })
+
+ it('cancels a pending behaviour open before toggling the popover', () => {
+ controller.openValue = false
+ controller.cancelBehaviourOpen = jest.fn()
+
+ usePopover(controller)
+ controller.toggle()
+
+ expect(controller.cancelBehaviourOpen).toHaveBeenCalledTimes(1)
+ expect(controller.openValue).toBe(true)
+ })
})
diff --git a/__tests__/controllers/webchat/emoji_picker_controller_test.js b/__tests__/controllers/webchat/emoji_picker_controller_test.js
index 5460617..f64404b 100644
--- a/__tests__/controllers/webchat/emoji_picker_controller_test.js
+++ b/__tests__/controllers/webchat/emoji_picker_controller_test.js
@@ -22,11 +22,6 @@ describe('EmojiPickerController', () => {
controller.buttonTarget = button
controller.popoverTarget = popover
- Object.defineProperty(controller, 'pickerObject', {
- get: () => picker,
- configurable: true
- })
-
usePopover.mockImplementation(controller => {
controller.setupFloatingUI = jest.fn()
})
@@ -36,7 +31,9 @@ describe('EmojiPickerController', () => {
usePopover.mockReset()
})
- it('positions the picker absolutely against its button', () => {
+ it('positions the picker absolutely against its button without loading emoji assets immediately', () => {
+ controller.loadPickerDependencies = jest.fn()
+
controller.connect()
expect(controller.setupFloatingUI).toHaveBeenCalledWith({
@@ -44,6 +41,42 @@ describe('EmojiPickerController', () => {
popover: popover,
strategy: 'absolute'
})
+ expect(controller.loadPickerDependencies).not.toHaveBeenCalled()
+ expect(popover.contains(picker)).toBe(false)
+ })
+
+ it('loads and appends the picker when the popover opens', async () => {
+ const Picker = jest.fn(() => picker)
+
+ controller.loadPickerDependencies = jest.fn().mockResolvedValue({
+ Picker,
+ i18n: { search: 'Search' },
+ })
+
+ controller.connect()
+ await controller.onPopoverOpened()
+
+ expect(controller.loadPickerDependencies).toHaveBeenCalledTimes(1)
+ expect(Picker).toHaveBeenCalledWith(expect.objectContaining({
+ onEmojiSelect: controller.onEmojiSelect,
+ i18n: { search: 'Search' },
+ }))
expect(popover.contains(picker)).toBe(true)
})
+
+ it('loads the picker only once', async () => {
+ const Picker = jest.fn(() => picker)
+
+ controller.loadPickerDependencies = jest.fn().mockResolvedValue({
+ Picker,
+ i18n: { search: 'Search' },
+ })
+
+ controller.connect()
+ await controller.onPopoverOpened()
+ await controller.onPopoverOpened()
+
+ expect(controller.loadPickerDependencies).toHaveBeenCalledTimes(1)
+ expect(Picker).toHaveBeenCalledTimes(1)
+ })
})
diff --git a/__tests__/controllers/webchat_controller_test.js b/__tests__/controllers/webchat_controller_test.js
index aa2f65d..50c3da7 100644
--- a/__tests__/controllers/webchat_controller_test.js
+++ b/__tests__/controllers/webchat_controller_test.js
@@ -4,8 +4,10 @@
import WebchatController from '../../src/controllers/webchat_controller'
import Hellotext from '../../src/hellotext'
-import { Webchat as WebchatConfiguration } from '../../src/core/configuration/webchat'
+import { Webchat as WebchatConfiguration, modes } from '../../src/core/configuration/webchat'
import { usePopover } from '../../src/controllers/mixins/usePopover'
+import { useOpeningSequence } from '../../src/controllers/webchat/useOpeningSequence'
+import { useTeaser } from '../../src/controllers/webchat/useTeaser'
// Mock dependencies
jest.mock('../../src/api/webchat/messages')
@@ -34,24 +36,26 @@ describe('WebchatController', () => {
Object.defineProperty(controller, 'element', {
value: mockElement,
writable: false,
- configurable: true
+ configurable: true,
})
controller.messagesContainerTarget = mockMessagesContainer
controller.idValue = 'test-webchat-id'
controller.conversationIdValue = 'test-conversation-id'
+ controller.messageIds = new Set()
mockBroadcastChannel = {
postMessage: jest.fn(),
addEventListener: jest.fn(),
- removeEventListener: jest.fn()
+ removeEventListener: jest.fn(),
+ close: jest.fn(),
}
global.BroadcastChannel = jest.fn(() => mockBroadcastChannel)
global.DOMParser = jest.fn(() => ({
- parseFromString: jest.fn()
+ parseFromString: jest.fn(),
}))
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
@@ -77,7 +81,7 @@ describe('WebchatController', () => {
const mockDocument = { body: mockBody }
const mockDOMParser = {
- parseFromString: jest.fn().mockReturnValue(mockDocument)
+ parseFromString: jest.fn().mockReturnValue(mockDocument),
}
global.DOMParser = jest.fn(() => mockDOMParser)
@@ -86,8 +90,8 @@ describe('WebchatController', () => {
const event = {
data: {
type: 'message:sent',
- element: mockHtmlString
- }
+ element: mockHtmlString,
+ },
}
controller.onOutboundMessageSent(event)
@@ -116,7 +120,7 @@ describe('WebchatController', () => {
const mockDocument = { body: mockBody }
const mockDOMParser = {
- parseFromString: jest.fn().mockReturnValue(mockDocument)
+ parseFromString: jest.fn().mockReturnValue(mockDocument),
}
global.DOMParser = jest.fn(() => mockDOMParser)
@@ -125,8 +129,8 @@ describe('WebchatController', () => {
const event = {
data: {
type: 'message:sent',
- element: complexHtml
- }
+ element: complexHtml,
+ },
}
controller.onOutboundMessageSent(event)
@@ -148,8 +152,8 @@ describe('WebchatController', () => {
const event = {
data: {
type: 'message:failed',
- id: messageId
- }
+ id: messageId,
+ },
}
controller.onOutboundMessageSent(event)
@@ -162,8 +166,8 @@ describe('WebchatController', () => {
const event = {
data: {
type: 'message:failed',
- id: 'non-existent-message'
- }
+ id: 'non-existent-message',
+ },
}
expect(() => controller.onOutboundMessageSent(event)).not.toThrow()
@@ -189,8 +193,8 @@ describe('WebchatController', () => {
const event = {
data: {
type: 'message:failed',
- id: 'message-2'
- }
+ id: 'message-2',
+ },
}
controller.onOutboundMessageSent(event)
@@ -206,8 +210,8 @@ describe('WebchatController', () => {
const event = {
data: {
type: 'unknown:event',
- someData: 'test'
- }
+ someData: 'test',
+ },
}
controller.onOutboundMessageSent(event)
@@ -218,8 +222,8 @@ describe('WebchatController', () => {
it('handles missing type gracefully', () => {
const event = {
data: {
- someData: 'test'
- }
+ someData: 'test',
+ },
}
controller.onOutboundMessageSent(event)
@@ -247,18 +251,17 @@ describe('WebchatController', () => {
const mockDocument = { body: mockBody }
const mockDOMParser = {
- parseFromString: jest.fn().mockReturnValue(mockDocument)
+ parseFromString: jest.fn().mockReturnValue(mockDocument),
}
global.DOMParser = jest.fn(() => mockDOMParser)
const event = {
data: {
type: 'message:sent',
- element: ''
- }
+ element: '',
+ },
}
-
expect(() => controller.onOutboundMessageSent(event)).toThrow()
})
@@ -270,7 +273,7 @@ describe('WebchatController', () => {
const mockDocument = { body: mockBody }
const mockDOMParser = {
- parseFromString: jest.fn().mockReturnValue(mockDocument)
+ parseFromString: jest.fn().mockReturnValue(mockDocument),
}
global.DOMParser = jest.fn(() => mockDOMParser)
@@ -279,8 +282,8 @@ describe('WebchatController', () => {
const event = {
data: {
type: 'message:sent',
- element: malformedHtml
- }
+ element: malformedHtml,
+ },
}
controller.onOutboundMessageSent(event)
@@ -325,15 +328,15 @@ describe('WebchatController', () => {
// Mock messagesAPI
mockMessagesAPI = {
- markAsSeen: jest.fn()
+ markAsSeen: jest.fn(),
}
controller.messagesAPI = mockMessagesAPI
// Mock Hellotext
mockHellotext = {
eventEmitter: {
- dispatch: jest.fn()
- }
+ dispatch: jest.fn(),
+ },
}
// Mock scrollIntoView
@@ -348,7 +351,7 @@ describe('WebchatController', () => {
it('creates and appends a message element with body content', () => {
const message = {
body: '
Hello world!
',
- id: 'msg-123'
+ id: 'msg-123',
}
controller.onMessageReceived(message)
@@ -360,10 +363,49 @@ describe('WebchatController', () => {
expect(addedElement.getAttribute('data-hellotext--webchat-target')).toBe('message')
})
+ it('silently drops a duplicate message that was already claimed in memory', () => {
+ controller.openValue = false
+
+ const message = {
+ body: 'Hello once
',
+ id: 'duplicate-msg',
+ }
+
+ controller.onMessageReceived(message)
+ controller.onMessageReceived({
+ ...message,
+ body: 'Hello twice
',
+ })
+
+ expect(mockMessagesContainer.children).toHaveLength(1)
+ expect(mockMessagesContainer.children[0].querySelector('[data-body]').innerHTML).toBe(
+ 'Hello once
',
+ )
+ expect(mockHellotext.eventEmitter.dispatch).toHaveBeenCalledTimes(1)
+ })
+
+ it('silently drops a duplicate message that already has a rendered target', () => {
+ const renderedMessage = document.createElement('div')
+ renderedMessage.dataset.id = 'rendered-msg'
+
+ Object.defineProperty(controller, 'messageTargets', {
+ get: () => [renderedMessage],
+ configurable: true,
+ })
+
+ controller.onMessageReceived({
+ body: 'Already rendered
',
+ id: 'rendered-msg',
+ })
+
+ expect(mockMessagesContainer.children).toHaveLength(0)
+ expect(mockHellotext.eventEmitter.dispatch).not.toHaveBeenCalled()
+ })
+
it('handles plain text messages', () => {
const message = {
body: 'Simple text message',
- id: 'msg-456'
+ id: 'msg-456',
}
controller.onMessageReceived(message)
@@ -375,13 +417,15 @@ describe('WebchatController', () => {
it('handles HTML content in message body', () => {
const message = {
body: 'Bold text and italic text',
- id: 'msg-789'
+ id: 'msg-789',
}
controller.onMessageReceived(message)
const addedElement = mockMessagesContainer.children[0]
- expect(addedElement.querySelector('[data-body]').innerHTML).toBe('Bold text and italic text')
+ expect(addedElement.querySelector('[data-body]').innerHTML).toBe(
+ 'Bold text and italic text',
+ )
})
})
@@ -389,7 +433,7 @@ describe('WebchatController', () => {
it('processes single attachment correctly', () => {
const message = {
body: 'Message with attachment',
- attachments: ['https://example.com/image1.jpg']
+ attachments: ['https://example.com/image1.jpg'],
}
controller.onMessageReceived(message)
@@ -403,14 +447,32 @@ describe('WebchatController', () => {
expect(attachmentImage.style.display).toBe('block')
})
+ it('renders attachments into the plural attachments container', () => {
+ const attachmentContainer = mockMessageTemplate.querySelector('[data-attachment-container]')
+ attachmentContainer.removeAttribute('data-attachment-container')
+ attachmentContainer.setAttribute('data-attachments-container', '')
+
+ controller.onMessageReceived({
+ body: 'Message with attachment',
+ id: 'msg-plural-attachment-container',
+ attachments: ['https://example.com/image1.jpg'],
+ })
+
+ const addedElement = mockMessagesContainer.children[0]
+ const pluralAttachmentContainer = addedElement.querySelector('[data-attachments-container]')
+
+ expect(pluralAttachmentContainer.children).toHaveLength(1)
+ expect(pluralAttachmentContainer.children[0].src).toBe('https://example.com/image1.jpg')
+ })
+
it('processes multiple attachments correctly', () => {
const message = {
body: 'Message with multiple attachments',
attachments: [
'https://example.com/image1.jpg',
'https://example.com/image2.png',
- 'https://example.com/image3.gif'
- ]
+ 'https://example.com/image3.gif',
+ ],
}
controller.onMessageReceived(message)
@@ -426,7 +488,7 @@ describe('WebchatController', () => {
it('handles messages without attachments', () => {
const message = {
- body: 'Message without attachments'
+ body: 'Message without attachments',
}
controller.onMessageReceived(message)
@@ -439,7 +501,7 @@ describe('WebchatController', () => {
it('handles empty attachments array', () => {
const message = {
body: 'Message with empty attachments',
- attachments: []
+ attachments: [],
}
controller.onMessageReceived(message)
@@ -453,7 +515,7 @@ describe('WebchatController', () => {
describe('scroll behavior', () => {
it('scrolls new message into view smoothly', () => {
const message = {
- body: 'Scroll test message'
+ body: 'Scroll test message',
}
controller.onMessageReceived(message)
@@ -471,7 +533,7 @@ describe('WebchatController', () => {
it('marks message as seen when chat is open', () => {
const message = {
body: 'Open chat message',
- id: 'msg-open-chat'
+ id: 'msg-open-chat',
}
controller.onMessageReceived(message)
@@ -486,12 +548,109 @@ describe('WebchatController', () => {
it('does not mark message as seen when chat is closed', () => {
const message = {
- body: 'Closed chat message'
+ body: 'Closed chat message',
}
controller.onMessageReceived(message)
expect(mockMessagesAPI.markAsSeen).not.toHaveBeenCalled()
})
+
+ it('clamps the unread counter at 9 while chat is closed', () => {
+ mockUnreadCounter.innerText = '9'
+
+ controller.onMessageReceived({
+ body: 'Closed chat message',
+ id: 'msg-unread-cap',
+ })
+
+ expect(mockUnreadCounter.style.display).toBe('flex')
+ expect(mockUnreadCounter.innerText).toBe(9)
+ })
+ })
+
+ describe('message teaser handling', () => {
+ let mockTeaser
+ let mockSessionStorage
+
+ beforeEach(() => {
+ useTeaser(controller)
+ mockTeaser = document.createElement('div')
+ mockTeaser.innerHTML = 'Configured teaser'
+ controller.teaserTarget = mockTeaser
+ mockSessionStorage = {
+ getItem: jest.fn().mockReturnValue(null),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'sessionStorage', {
+ value: mockSessionStorage,
+ writable: true,
+ })
+
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+ })
+
+ it('overrides and shows the teaser when a closed chat receives a message teaser', () => {
+ controller.openValue = false
+ mockTeaser.classList.add('invisible')
+
+ controller.onMessageReceived({
+ body: 'Closed chat message',
+ id: 'msg-closed-teaser',
+ teaser: 'Message teaser',
+ })
+
+ expect(mockTeaser.innerHTML).toBe('Message teaser')
+ expect(mockTeaser.classList.contains('invisible')).toBe(false)
+ })
+
+ it('keeps incoming message teasers ephemeral without marking the session teaser seen', () => {
+ controller.openValue = false
+ mockTeaser.classList.add('invisible')
+
+ controller.onMessageReceived({
+ body: 'Closed chat message',
+ id: 'msg-ephemeral-teaser',
+ teaser: 'Ephemeral teaser',
+ })
+
+ expect(mockTeaser.innerHTML).toBe('Ephemeral teaser')
+ expect(mockTeaser.classList.contains('invisible')).toBe(false)
+ expect(mockSessionStorage.setItem).not.toHaveBeenCalledWith(
+ 'hellotext:webchat:test-webchat-id:teaser-seen',
+ 'true',
+ )
+ })
+
+ it('overrides and hides the teaser when an open chat receives a message teaser', () => {
+ controller.openValue = true
+
+ controller.onMessageReceived({
+ body: 'Open chat message',
+ id: 'msg-open-teaser',
+ teaser: 'Open message teaser',
+ })
+
+ expect(mockTeaser.innerHTML).toBe('Open message teaser')
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ expect(mockMessagesAPI.markAsSeen).toHaveBeenCalledWith('msg-open-teaser')
+ })
+
+ it('does not replace or show the teaser when the message has no teaser', () => {
+ controller.openValue = false
+ mockTeaser.classList.add('invisible')
+
+ controller.onMessageReceived({
+ body: 'Message without teaser',
+ id: 'msg-without-teaser',
+ })
+
+ expect(mockTeaser.innerHTML).toBe('Configured teaser')
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ })
})
describe('typing indicator timeout clearance', () => {
@@ -504,7 +663,7 @@ describe('WebchatController', () => {
controller.typingIndicatorTarget = mockTypingIndicatorTarget
// Mock hasTypingIndicatorTarget to return true when target exists
Object.defineProperty(controller, 'hasTypingIndicatorTarget', {
- get: () => !!controller.typingIndicatorTarget
+ get: () => !!controller.typingIndicatorTarget,
})
mockClearTimeout = jest.fn()
@@ -518,7 +677,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-typing-clear'
+ id: 'msg-typing-clear',
}
controller.onMessageReceived(message)
@@ -532,7 +691,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-typing-remove'
+ id: 'msg-typing-remove',
}
controller.onMessageReceived(message)
@@ -545,7 +704,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-typing-hide'
+ id: 'msg-typing-hide',
}
controller.onMessageReceived(message)
@@ -558,7 +717,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-no-typing'
+ id: 'msg-no-typing',
}
controller.onMessageReceived(message)
@@ -571,7 +730,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-no-typing-remove'
+ id: 'msg-no-typing-remove',
}
controller.onMessageReceived(message)
@@ -583,12 +742,12 @@ describe('WebchatController', () => {
const message1 = {
body: 'First message',
- id: 'msg-1'
+ id: 'msg-1',
}
const message2 = {
body: 'Second message',
- id: 'msg-2'
+ id: 'msg-2',
}
controller.onMessageReceived(message1)
@@ -610,7 +769,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-timeout-id'
+ id: 'msg-timeout-id',
}
controller.onMessageReceived(message)
@@ -623,7 +782,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-null-timeout'
+ id: 'msg-null-timeout',
}
controller.onMessageReceived(message)
@@ -636,7 +795,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-undefined-timeout'
+ id: 'msg-undefined-timeout',
}
controller.onMessageReceived(message)
@@ -649,7 +808,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-null-target'
+ id: 'msg-null-target',
}
expect(() => controller.onMessageReceived(message)).not.toThrow()
@@ -663,7 +822,7 @@ describe('WebchatController', () => {
const message = {
body: 'Test message',
- id: 'msg-undefined-target'
+ id: 'msg-undefined-target',
}
expect(() => controller.onMessageReceived(message)).not.toThrow()
@@ -675,7 +834,7 @@ describe('WebchatController', () => {
describe('edge cases and error handling', () => {
it('handles missing message body gracefully', () => {
const message = {
- id: 'msg-no-body'
+ id: 'msg-no-body',
}
expect(() => controller.onMessageReceived(message)).not.toThrow()
@@ -692,7 +851,7 @@ describe('WebchatController', () => {
it('handles message with null attachments', () => {
const message = {
body: 'Message with null attachments',
- attachments: null
+ attachments: null,
}
expect(() => controller.onMessageReceived(message)).not.toThrow()
@@ -704,7 +863,7 @@ describe('WebchatController', () => {
it('handles malformed HTML in message body', () => {
const message = {
- body: 'Unclosed tags and bold text'
+ body: 'Unclosed tags and bold text',
}
expect(() => controller.onMessageReceived(message)).not.toThrow()
@@ -727,7 +886,7 @@ describe('WebchatController', () => {
`,
attachments: ['https://example.com/file.pdf'],
id: 'complex-msg',
- metadata: { important: true }
+ metadata: { important: true },
}
controller.onMessageReceived(message)
@@ -745,7 +904,7 @@ describe('WebchatController', () => {
beforeEach(() => {
mockWebChatChannel = {
- startTypingIndicator: jest.fn()
+ startTypingIndicator: jest.fn(),
}
controller.webChatChannel = mockWebChatChannel
controller.hasSentTypingIndicator = false
@@ -754,7 +913,7 @@ describe('WebchatController', () => {
// Mock inputTarget for resizeInput method
controller.inputTarget = {
style: {},
- scrollHeight: 50
+ scrollHeight: 50,
}
})
@@ -822,6 +981,91 @@ describe('WebchatController', () => {
})
})
+ describe('onClickOutside', () => {
+ it('closes the popover when mode is popover and the click is outside the controller element', () => {
+ WebchatConfiguration.mode = modes.POPOVER
+ controller.openValue = true
+
+ controller.onClickOutside({ target: document.createElement('div') })
+
+ expect(controller.openValue).toBe(false)
+ })
+
+ it('does not close the popover when mode is modal', () => {
+ WebchatConfiguration.mode = modes.MODAL
+ controller.openValue = true
+
+ controller.onClickOutside({ target: document.createElement('div') })
+
+ expect(controller.openValue).toBe(true)
+ })
+ })
+
+ describe('onScroll', () => {
+ let mockMessagesAPI
+ let mockMessageTemplate
+ let mockAttachmentImage
+
+ beforeEach(() => {
+ mockMessageTemplate = document.createElement('div')
+ const bodyElement = document.createElement('div')
+ bodyElement.setAttribute('data-body', '')
+ const attachmentContainer = document.createElement('div')
+ attachmentContainer.setAttribute('data-attachments-container', '')
+ mockMessageTemplate.appendChild(bodyElement)
+ mockMessageTemplate.appendChild(attachmentContainer)
+
+ mockAttachmentImage = document.createElement('img')
+ mockAttachmentImage.style.display = 'none'
+
+ controller.messageTemplateTarget = mockMessageTemplate
+ controller.attachmentImageTarget = mockAttachmentImage
+ controller.nextPageValue = 2
+ controller.fetchingNextPage = false
+ mockMessagesContainer.scrollTop = 0
+ mockMessagesContainer.scroll = jest.fn()
+
+ Object.defineProperty(mockMessagesContainer, 'scrollHeight', {
+ value: 500,
+ configurable: true,
+ })
+
+ mockMessagesAPI = {
+ index: jest.fn().mockResolvedValue({
+ json: jest.fn().mockResolvedValue({
+ next: null,
+ messages: [
+ {
+ body: 'Older message
',
+ attachments: ['https://example.com/older.jpg'],
+ state: 'sent',
+ },
+ ],
+ }),
+ }),
+ }
+ controller.messagesAPI = mockMessagesAPI
+
+ Object.assign(Hellotext, {
+ session: 'test-session-123',
+ })
+ })
+
+ it('renders paginated attachments into the plural attachments container', async () => {
+ await controller.onScroll()
+
+ const addedElement = mockMessagesContainer.children[0]
+ const attachmentContainer = addedElement.querySelector('[data-attachments-container]')
+
+ expect(mockMessagesAPI.index).toHaveBeenCalledWith({
+ page: 2,
+ session: 'test-session-123',
+ })
+ expect(attachmentContainer.children).toHaveLength(1)
+ expect(attachmentContainer.children[0].src).toBe('https://example.com/older.jpg')
+ })
+ })
+
describe('connect', () => {
let mockTrigger
let mockPopover
@@ -829,6 +1073,7 @@ describe('WebchatController', () => {
let mockToolbar
let mockWebChatChannel
let mockLocalStorage
+ let mockSessionStorage
beforeEach(() => {
mockTrigger = document.createElement('button')
@@ -845,34 +1090,40 @@ describe('WebchatController', () => {
mockWebChatChannel = {
onMessage: jest.fn(),
onTypingStart: jest.fn(),
- onReaction: jest.fn()
+ onReaction: jest.fn(),
}
controller.webChatChannel = mockWebChatChannel
mockLocalStorage = {
getItem: jest.fn().mockReturnValue(null),
setItem: jest.fn(),
- removeItem: jest.fn()
+ removeItem: jest.fn(),
}
Object.defineProperty(window, 'localStorage', {
value: mockLocalStorage,
- writable: true
+ writable: true,
+ })
+ mockSessionStorage = {
+ getItem: jest.fn().mockReturnValue(null),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'sessionStorage', {
+ value: mockSessionStorage,
+ writable: true,
})
Object.assign(Hellotext, {
business: {
features: {
- white_label: true
- }
+ white_label: true,
+ },
},
eventEmitter: {
- dispatch: jest.fn()
- }
+ dispatch: jest.fn(),
+ },
})
- WebchatConfiguration.classes = []
- WebchatConfiguration.triggerClasses = []
-
usePopover.mockImplementation(controller => {
controller.setupFloatingUI = jest.fn()
})
@@ -880,187 +1131,1281 @@ describe('WebchatController', () => {
afterEach(() => {
usePopover.mockReset()
+ jest.useRealTimers()
})
- it('sets up floating UI for the teaser with absolute positioning', () => {
+ const setHasTeaserTarget = value => {
Object.defineProperty(controller, 'hasTeaserTarget', {
- get: () => true,
- configurable: true
+ get: () => value,
+ configurable: true,
+ })
+ }
+
+ const setTeaserMessages = delays => {
+ mockTeaser.innerHTML = ''
+
+ const messages = delays.map(delay => {
+ const message = document.createElement('section')
+ message.setAttribute('data-teaser-message', 'true')
+
+ if (delay !== undefined) {
+ message.dataset.delaySeconds = String(delay)
+ }
+
+ mockTeaser.appendChild(message)
+
+ return message
+ })
+
+ return messages
+ }
+
+ const allowPreConversationTeaser = () => {
+ controller.conversationIdValue = ''
+ controller.openValue = false
+
+ Object.defineProperty(controller, 'messageTargets', {
+ get: () => [],
+ configurable: true,
})
+ }
+
+ const teaserSeenKey = () => `hellotext:webchat:${controller.idValue}:teaser-seen`
+
+ it('sets up floating UI for the teaser with absolute positioning', () => {
+ setHasTeaserTarget(true)
controller.connect()
expect(controller.setupFloatingUI).toHaveBeenNthCalledWith(1, {
trigger: mockTrigger,
- popover: mockPopover
+ popover: mockPopover,
})
expect(controller.setupFloatingUI).toHaveBeenNthCalledWith(2, {
trigger: mockTrigger,
popover: mockTeaser,
- strategy: 'absolute'
+ strategy: 'absolute',
})
})
it('does not set up teaser positioning without a teaser target', () => {
- Object.defineProperty(controller, 'hasTeaserTarget', {
- get: () => false,
- configurable: true
- })
+ setHasTeaserTarget(false)
controller.connect()
expect(controller.setupFloatingUI).toHaveBeenCalledTimes(1)
expect(controller.setupFloatingUI).toHaveBeenCalledWith({
trigger: mockTrigger,
- popover: mockPopover
+ popover: mockPopover,
})
})
- })
-
- describe('onPopoverOpened', () => {
- let mockHellotext
- let mockLocalStorage
- let mockTeaser
- let mockUnreadCounter
- beforeEach(() => {
- mockTeaser = document.createElement('div')
- mockUnreadCounter = document.createElement('div')
- mockUnreadCounter.style.display = 'none'
+ it('does not start teaser cycling without a teaser target', () => {
+ allowPreConversationTeaser()
+ setHasTeaserTarget(false)
+ const messages = setTeaserMessages([2])
- controller.popoverTarget = document.createElement('div')
- controller.fadeOutClasses = ['fade-out']
- controller.scrolled = true
- controller.teaserTarget = mockTeaser
- controller.unreadCounterTarget = mockUnreadCounter
- controller.messagesAPI = {
- markAsSeen: jest.fn()
- }
+ controller.connect()
- Object.defineProperty(controller, 'onMobile', {
- get: () => true,
- configurable: true
- })
+ expect(messages[0].classList.contains('hidden')).toBe(false)
+ expect(controller.teaserCycleTimeout).toBe(null)
+ })
- mockHellotext = {
- eventEmitter: {
- dispatch: jest.fn()
- }
- }
+ it('does not start teaser presentation when the session has already seen it', () => {
+ allowPreConversationTeaser()
+ mockSessionStorage.getItem.mockImplementation(key =>
+ key === teaserSeenKey() ? 'true' : null,
+ )
+ setHasTeaserTarget(true)
+ const messages = setTeaserMessages([2])
- mockLocalStorage = {
- setItem: jest.fn(),
- getItem: jest.fn(),
- removeItem: jest.fn()
- }
- Object.defineProperty(window, 'localStorage', {
- value: mockLocalStorage,
- writable: true
- })
+ controller.connect()
- Object.assign(Hellotext, mockHellotext)
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ expect(messages[0].classList.contains('hidden')).toBe(false)
+ expect(controller.teaserCycleTimeout).toBe(null)
})
- it('hides the teaser when the popover opens', () => {
- Object.defineProperty(controller, 'hasTeaserTarget', {
- get: () => true,
- configurable: true
- })
+ it('hides the teaser without teaser messages', () => {
+ allowPreConversationTeaser()
+ setHasTeaserTarget(true)
+ setTeaserMessages([])
- controller.onPopoverOpened()
+ controller.connect()
- expect(mockTeaser.classList.contains('hidden')).toBe(true)
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ expect(controller.teaserCycleTimeout).toBe(null)
})
- })
-
- describe('onPopoverClosed', () => {
- let mockHellotext
- let mockLocalStorage
- beforeEach(() => {
- mockHellotext = {
- eventEmitter: {
- dispatch: jest.fn()
- }
- }
+ it('shows the first teaser message and hides later messages on connect', () => {
+ jest.useFakeTimers()
+ allowPreConversationTeaser()
+ mockTeaser.classList.add('invisible')
+ setHasTeaserTarget(true)
+ const messages = setTeaserMessages([2, 4, 6])
- // Mock localStorage
- mockLocalStorage = {
- setItem: jest.fn(),
- getItem: jest.fn(),
- removeItem: jest.fn()
- }
- Object.defineProperty(window, 'localStorage', {
- value: mockLocalStorage,
- writable: true
- })
+ controller.connect()
- // Import and setup Hellotext mock
- const Hellotext = require('../../src/hellotext').default
- Object.assign(Hellotext, mockHellotext)
+ expect(mockTeaser.classList.contains('invisible')).toBe(false)
+ expect(messages[0].classList.contains('hidden')).toBe(false)
+ expect(messages[1].classList.contains('hidden')).toBe(true)
+ expect(messages[2].classList.contains('hidden')).toBe(true)
})
- it('dispatches webchat:closed event', () => {
- controller.onPopoverClosed()
+ it('does not schedule timers for a single teaser message', () => {
+ jest.useFakeTimers()
+ allowPreConversationTeaser()
+ setHasTeaserTarget(true)
+ setTeaserMessages([2])
- expect(mockHellotext.eventEmitter.dispatch).toHaveBeenCalledWith('webchat:closed')
+ controller.connect()
+
+ expect(controller.teaserCycleTimeout).toBe(null)
})
- it('sets closed state in localStorage with correct key', () => {
- controller.idValue = 'test-webchat-123'
+ it('uses a 250ms minimum before advancing zero or blank teaser delays', () => {
+ jest.useFakeTimers()
+ allowPreConversationTeaser()
+ setHasTeaserTarget(true)
+ const messages = setTeaserMessages([0, undefined, 1])
- controller.onPopoverClosed()
+ controller.connect()
+
+ jest.advanceTimersByTime(249)
+ expect(messages[0].classList.contains('hidden')).toBe(false)
+ expect(messages[1].classList.contains('hidden')).toBe(true)
+ expect(messages[2].classList.contains('hidden')).toBe(true)
- expect(mockLocalStorage.setItem).toHaveBeenCalledWith('hellotext--webchat--test-webchat-123', 'closed')
+ jest.advanceTimersByTime(1)
+ expect(messages[0].classList.contains('hidden')).toBe(true)
+ expect(messages[1].classList.contains('hidden')).toBe(false)
+ expect(messages[2].classList.contains('hidden')).toBe(true)
+
+ jest.advanceTimersByTime(249)
+ expect(messages[1].classList.contains('hidden')).toBe(false)
+
+ jest.advanceTimersByTime(1)
+ expect(messages[1].classList.contains('hidden')).toBe(true)
+ expect(messages[2].classList.contains('hidden')).toBe(false)
})
- it('shows the teaser again when the teaser has body content', () => {
- const teaser = document.createElement('div')
- teaser.classList.add('hidden')
+ it('advances teaser messages using the current message delay and stops after the last', () => {
+ jest.useFakeTimers()
+ allowPreConversationTeaser()
+ setHasTeaserTarget(true)
+ const messages = setTeaserMessages([1, 2, 3])
- controller.teaserTarget = teaser
- controller.teaserValue = { body: 'Need help?' }
- Object.defineProperty(controller, 'hasTeaserTarget', {
- get: () => true,
- configurable: true
- })
+ controller.connect()
- controller.onPopoverClosed()
+ expect(messages[0].classList.contains('hidden')).toBe(false)
- expect(teaser.classList.contains('hidden')).toBe(false)
- })
+ jest.advanceTimersByTime(999)
+ expect(messages[0].classList.contains('hidden')).toBe(false)
- it('keeps the teaser hidden when the teaser has no body content', () => {
- const teaser = document.createElement('div')
- teaser.classList.add('hidden')
+ jest.advanceTimersByTime(1)
+ expect(messages[0].classList.contains('hidden')).toBe(true)
+ expect(messages[1].classList.contains('hidden')).toBe(false)
- controller.teaserTarget = teaser
- controller.teaserValue = {}
- Object.defineProperty(controller, 'hasTeaserTarget', {
- get: () => true,
- configurable: true
- })
+ jest.advanceTimersByTime(1999)
+ expect(messages[1].classList.contains('hidden')).toBe(false)
- controller.onPopoverClosed()
+ jest.advanceTimersByTime(1)
+ expect(messages[1].classList.contains('hidden')).toBe(true)
+ expect(messages[2].classList.contains('hidden')).toBe(false)
- expect(teaser.classList.contains('hidden')).toBe(true)
+ jest.advanceTimersByTime(10000)
+ expect(messages[0].classList.contains('hidden')).toBe(true)
+ expect(messages[1].classList.contains('hidden')).toBe(true)
+ expect(messages[2].classList.contains('hidden')).toBe(false)
+ expect(controller.teaserCycleTimeout).toBe(null)
})
- })
- describe('sendQuickReplyMessage', () => {
- let mockMessageTemplate
- let mockMessagesAPI
- let mockHellotext
- let mockLocale
- let mockCardElement
- let mockImageElement
- let mockBroadcastChannel
- let mockClearTimeout
+ it('clears pending teaser cycling on disconnect', () => {
+ jest.useFakeTimers()
+ allowPreConversationTeaser()
+ setHasTeaserTarget(true)
+ const messages = setTeaserMessages([1, 1])
+ controller.floatingUICleanup = jest.fn()
- beforeEach(() => {
- // Set up message template mock
- mockMessageTemplate = document.createElement('div')
- mockMessageTemplate.id = 'template'
+ controller.connect()
+ controller.disconnect()
+ jest.advanceTimersByTime(1000)
+
+ expect(messages[0].classList.contains('hidden')).toBe(false)
+ expect(messages[1].classList.contains('hidden')).toBe(true)
+ })
+
+ it('marks the teaser seen and hides it when the webchat is already open', () => {
+ allowPreConversationTeaser()
+ controller.openValue = true
+ setHasTeaserTarget(true)
+ setTeaserMessages([1])
+
+ controller.connect()
+
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(teaserSeenKey(), 'true')
+ })
+
+ it('marks the teaser seen and hides it when a conversation already exists', () => {
+ controller.conversationIdValue = 'existing-conversation'
+ controller.openValue = false
+ setHasTeaserTarget(true)
+ setTeaserMessages([1])
+
+ controller.connect()
+
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(teaserSeenKey(), 'true')
+ })
+
+ it('marks the teaser seen and hides it when rendered messages already exist', () => {
+ const renderedMessage = document.createElement('article')
+ controller.conversationIdValue = ''
+ controller.openValue = false
+ setHasTeaserTarget(true)
+ setTeaserMessages([1])
+ Object.defineProperty(controller, 'messageTargets', {
+ get: () => [renderedMessage],
+ configurable: true,
+ })
+
+ controller.connect()
+
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(teaserSeenKey(), 'true')
+ })
+
+ it('does not schedule an automatic open for click-triggered behaviour', () => {
+ jest.useFakeTimers()
+
+ controller.openValue = false
+ controller.behaviourValue = {
+ trigger: 'on_click',
+ delay_seconds: 0,
+ first_visit_only: true,
+ once_per_session: true,
+ }
+
+ controller.connect()
+ jest.runOnlyPendingTimers()
+
+ expect(controller.openValue).toBe(false)
+ expect(mockLocalStorage.setItem).not.toHaveBeenCalledWith(controller.firstVisitKey(), '1')
+ expect(mockSessionStorage.setItem).not.toHaveBeenCalledWith(controller.sessionKey(), '1')
+ })
+
+ it('opens after the configured on-load delay and marks enabled gates', () => {
+ jest.useFakeTimers()
+
+ controller.openValue = false
+ controller.behaviourValue = {
+ trigger: 'on_load',
+ delay_seconds: 5,
+ first_visit_only: true,
+ once_per_session: true,
+ }
+
+ controller.connect()
+
+ jest.advanceTimersByTime(4999)
+ expect(controller.openValue).toBe(false)
+
+ jest.advanceTimersByTime(1)
+
+ expect(controller.openValue).toBe(true)
+ expect(mockLocalStorage.setItem).toHaveBeenCalledWith(controller.firstVisitKey(), '1')
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(controller.sessionKey(), '1')
+ })
+
+ it('opens immediately after connect when the on-load delay is zero', () => {
+ jest.useFakeTimers()
+
+ controller.openValue = false
+ controller.behaviourValue = {
+ trigger: 'on_load',
+ delay_seconds: 0,
+ first_visit_only: false,
+ once_per_session: false,
+ }
+
+ controller.connect()
+ expect(controller.openValue).toBe(false)
+
+ jest.runOnlyPendingTimers()
+
+ expect(controller.openValue).toBe(true)
+ expect(mockLocalStorage.setItem).not.toHaveBeenCalledWith(controller.firstVisitKey(), '1')
+ expect(mockSessionStorage.setItem).not.toHaveBeenCalledWith(controller.sessionKey(), '1')
+ })
+
+ it('does not auto-open when the first visit gate has already been marked', () => {
+ jest.useFakeTimers()
+ mockLocalStorage.getItem.mockImplementation(key =>
+ key === controller.firstVisitKey() ? '1' : null,
+ )
+
+ controller.openValue = false
+ controller.behaviourValue = {
+ trigger: 'on_load',
+ delay_seconds: 0,
+ first_visit_only: true,
+ once_per_session: false,
+ }
+
+ controller.connect()
+ jest.runOnlyPendingTimers()
+
+ expect(controller.openValue).toBe(false)
+ })
+
+ it('does not auto-open when the session gate has already been marked', () => {
+ jest.useFakeTimers()
+ mockSessionStorage.getItem.mockImplementation(key =>
+ key === controller.sessionKey() ? '1' : null,
+ )
+
+ controller.openValue = false
+ controller.behaviourValue = {
+ trigger: 'on_load',
+ delay_seconds: 0,
+ first_visit_only: false,
+ once_per_session: true,
+ }
+
+ controller.connect()
+ jest.runOnlyPendingTimers()
+
+ expect(controller.openValue).toBe(false)
+ })
+
+ it('does not mark gates when the widget is already open before the timer fires', () => {
+ jest.useFakeTimers()
+
+ controller.openValue = true
+ controller.behaviourValue = {
+ trigger: 'on_load',
+ delay_seconds: 5,
+ first_visit_only: true,
+ once_per_session: true,
+ }
+
+ controller.connect()
+ jest.advanceTimersByTime(5000)
+
+ expect(mockLocalStorage.setItem).not.toHaveBeenCalledWith(controller.firstVisitKey(), '1')
+ expect(mockSessionStorage.setItem).not.toHaveBeenCalledWith(controller.sessionKey(), '1')
+ })
+
+ it('clears pending behaviour opens on disconnect', () => {
+ jest.useFakeTimers()
+
+ controller.openValue = false
+ controller.behaviourValue = {
+ trigger: 'on_load',
+ delay_seconds: 5,
+ first_visit_only: false,
+ once_per_session: false,
+ }
+ controller.floatingUICleanup = jest.fn()
+
+ controller.connect()
+ controller.disconnect()
+ jest.advanceTimersByTime(5000)
+
+ expect(controller.openValue).toBe(false)
+ })
+ })
+
+ describe('onPopoverOpened', () => {
+ let mockHellotext
+ let mockLocalStorage
+ let mockSessionStorage
+ let mockTeaser
+ let mockUnreadCounter
+
+ beforeEach(() => {
+ useTeaser(controller)
+ useOpeningSequence(controller)
+ controller.setupOpeningSequence()
+ mockTeaser = document.createElement('div')
+ mockUnreadCounter = document.createElement('div')
+ mockUnreadCounter.style.display = 'none'
+
+ controller.popoverTarget = document.createElement('div')
+ controller.fadeOutClasses = ['fade-out']
+ controller.scrolled = true
+ controller.teaserTarget = mockTeaser
+ controller.unreadCounterTarget = mockUnreadCounter
+ controller.messagesAPI = {
+ markAsSeen: jest.fn(),
+ }
+
+ Object.defineProperty(controller, 'onMobile', {
+ get: () => true,
+ configurable: true,
+ })
+
+ mockHellotext = {
+ eventEmitter: {
+ dispatch: jest.fn(),
+ },
+ }
+
+ mockLocalStorage = {
+ setItem: jest.fn(),
+ getItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'localStorage', {
+ value: mockLocalStorage,
+ writable: true,
+ })
+
+ mockSessionStorage = {
+ getItem: jest.fn().mockReturnValue(null),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'sessionStorage', {
+ value: mockSessionStorage,
+ writable: true,
+ })
+
+ Object.assign(Hellotext, mockHellotext)
+ })
+
+ it('hides the teaser when the popover opens', () => {
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+ controller.idValue = 'test-webchat-id'
+
+ controller.onPopoverOpened()
+
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
+ 'hellotext:webchat:test-webchat-id:teaser-seen',
+ 'true',
+ )
+ })
+
+ it('clears the transient message teaser when the popover opens', () => {
+ controller.messageTeaserValue = 'Incoming message teaser'
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+
+ controller.onPopoverOpened()
+
+ expect(controller.messageTeaserValue).toBe(null)
+ expect(mockTeaser.classList.contains('invisible')).toBe(true)
+ })
+ })
+
+ describe('opening sequence', () => {
+ let mockHellotext
+ let mockLocalStorage
+ let mockMessagesAPI
+ let mockMessageTemplate
+ let mockOpeningSequence
+ let mockUnreadCounter
+ let mockWebChatChannel
+ let mockSessionStorage
+
+ beforeEach(() => {
+ jest.useFakeTimers()
+ useTeaser(controller)
+ useOpeningSequence(controller)
+ controller.setupOpeningSequence()
+
+ mockMessageTemplate = document.createElement('div')
+ mockMessageTemplate.style.display = 'none'
+ const bodyElement = document.createElement('div')
+ bodyElement.setAttribute('data-body', '')
+ const attachmentContainer = document.createElement('div')
+ attachmentContainer.setAttribute('data-attachment-container', '')
+ mockMessageTemplate.appendChild(bodyElement)
+ mockMessageTemplate.appendChild(attachmentContainer)
+ mockMessagesContainer.appendChild(mockMessageTemplate)
+
+ mockOpeningSequence = document.createElement('section')
+ mockUnreadCounter = document.createElement('div')
+ mockUnreadCounter.style.display = 'none'
+ mockUnreadCounter.innerText = '0'
+
+ mockMessagesAPI = {
+ create: jest.fn(),
+ markAsSeen: jest.fn(),
+ }
+
+ mockWebChatChannel = {
+ updateSubscriptionWith: jest.fn(),
+ }
+
+ mockHellotext = {
+ session: 'test-session-123',
+ eventEmitter: {
+ dispatch: jest.fn(),
+ },
+ }
+
+ mockLocalStorage = {
+ setItem: jest.fn(),
+ getItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+
+ mockSessionStorage = {
+ getItem: jest.fn().mockReturnValue(null),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+
+ controller.conversationIdValue = ''
+ controller.messageTemplateTarget = mockMessageTemplate
+ controller.messagesContainerTarget = mockMessagesContainer
+ controller.openingSequenceTarget = mockOpeningSequence
+ controller.popoverTarget = document.createElement('div')
+ controller.fadeOutClasses = ['fade-out']
+ controller.scrolled = true
+ controller.unreadCounterTarget = mockUnreadCounter
+ controller.messagesAPI = mockMessagesAPI
+ controller.webChatChannel = mockWebChatChannel
+ controller.broadcastChannel = mockBroadcastChannel
+ controller.files = []
+ controller.resizeInput = jest.fn()
+ controller.show = jest.fn()
+ controller.showOptimisticTypingIndicator = jest.fn()
+ controller.resetTypingIndicatorTimer = jest.fn()
+ controller.typingIndicatorVisible = true
+
+ Object.defineProperty(controller, 'onMobile', {
+ get: () => true,
+ configurable: true,
+ })
+ Object.defineProperty(controller, 'hasOpeningSequenceTarget', {
+ get: () => true,
+ configurable: true,
+ })
+ Object.defineProperty(controller, 'hasTypingIndicatorTarget', {
+ get: () => false,
+ configurable: true,
+ })
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => false,
+ configurable: true,
+ })
+ Object.defineProperty(mockMessagesContainer, 'scrollHeight', {
+ value: 640,
+ configurable: true,
+ })
+
+ mockMessagesContainer.scroll = jest.fn()
+ Element.prototype.scrollIntoView = jest.fn()
+
+ Object.defineProperty(window, 'localStorage', {
+ value: mockLocalStorage,
+ writable: true,
+ })
+ Object.defineProperty(window, 'sessionStorage', {
+ value: mockSessionStorage,
+ writable: true,
+ })
+
+ Object.assign(Hellotext, mockHellotext)
+
+ const { Locale } = require('../../src/core/configuration/locale')
+ Object.assign(Locale, {
+ toString: jest.fn().mockReturnValue('en'),
+ })
+ })
+
+ afterEach(() => {
+ jest.useRealTimers()
+ })
+
+ const setOpeningSequenceMessages = configs => {
+ const messages = configs.map(({ id, delay }) => {
+ const message = document.createElement('article')
+ message.hidden = true
+ message.dataset.openingSequenceMessageId = id
+ message.dataset.delaySeconds = String(delay)
+ mockOpeningSequence.appendChild(message)
+
+ return message
+ })
+
+ Object.defineProperty(controller, 'openingSequenceMessageTargets', {
+ get: () => messages,
+ configurable: true,
+ })
+
+ return messages
+ }
+
+ const setupSuccessfulMessageResponse = (response = {}) => {
+ mockMessagesAPI.create.mockResolvedValue({
+ failed: false,
+ json: jest.fn().mockResolvedValue({
+ id: 'server-message-123',
+ conversation: 'current-conversation',
+ ...response,
+ }),
+ })
+ }
+
+ const setupFailedMessageResponse = () => {
+ mockMessagesAPI.create.mockResolvedValue({ failed: true })
+ }
+
+ const setupComposeTargets = () => {
+ controller.inputTarget = document.createElement('textarea')
+ controller.inputTarget.value = 'hello'
+ controller.attachmentInputTarget = document.createElement('input')
+ controller.attachmentContainerTarget = document.createElement('section')
+ controller.errorMessageContainerTarget = document.createElement('section')
+ }
+
+ it('does nothing without an opening sequence target', () => {
+ const [message] = setOpeningSequenceMessages([{ id: 'shown', delay: 1 }])
+ Object.defineProperty(controller, 'hasOpeningSequenceTarget', {
+ get: () => false,
+ configurable: true,
+ })
+
+ controller.startOpeningSequence()
+ jest.advanceTimersByTime(1000)
+
+ expect(controller.openingSequenceStarted).toBe(false)
+ expect(message.hidden).toBe(true)
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual([])
+ })
+
+ it('does nothing when a conversation already exists', () => {
+ const [message] = setOpeningSequenceMessages([{ id: 'shown', delay: 1 }])
+ controller.conversationIdValue = 'existing-conversation'
+
+ controller.startOpeningSequence()
+ jest.advanceTimersByTime(1000)
+
+ expect(controller.openingSequenceStarted).toBe(false)
+ expect(message.hidden).toBe(true)
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual([])
+ })
+
+ it('does nothing without opening sequence messages', () => {
+ setOpeningSequenceMessages([])
+
+ controller.startOpeningSequence()
+
+ expect(controller.openingSequenceStarted).toBe(false)
+ expect(controller.openingSequenceTimeout).toBe(null)
+ })
+
+ it('starts once when the webchat opens without a conversation', () => {
+ setOpeningSequenceMessages([{ id: 'shown', delay: 1 }])
+
+ controller.onPopoverOpened()
+ const firstTimeout = controller.openingSequenceTimeout
+ controller.onPopoverOpened()
+
+ expect(controller.openingSequenceStarted).toBe(true)
+ expect(controller.openingSequenceTimeout).toBe(firstTimeout)
+ expect(mockMessagesAPI.markAsSeen).not.toHaveBeenCalled()
+ })
+
+ it('reveals staged messages using each message delay', () => {
+ const messages = setOpeningSequenceMessages([
+ { id: 'first', delay: 2 },
+ { id: 'second', delay: 3 },
+ ])
+
+ controller.startOpeningSequence()
+
+ jest.advanceTimersByTime(1999)
+ expect(messages[0].hidden).toBe(true)
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual([])
+
+ jest.advanceTimersByTime(1)
+ expect(messages[0].parentNode).toBe(mockMessagesContainer)
+ expect(messages[0].hidden).toBe(false)
+ expect(Array.from(mockMessagesContainer.children)).toEqual([messages[0], mockMessageTemplate])
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual(['first'])
+ expect(mockMessagesContainer.scroll).toHaveBeenCalledWith({
+ top: 640,
+ behavior: 'smooth',
+ })
+
+ jest.advanceTimersByTime(2999)
+ expect(messages[1].hidden).toBe(true)
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual(['first'])
+
+ jest.advanceTimersByTime(1)
+ expect(messages[1].parentNode).toBe(mockMessagesContainer)
+ expect(messages[1].hidden).toBe(false)
+ expect(Array.from(mockMessagesContainer.children)).toEqual([
+ messages[0],
+ messages[1],
+ mockMessageTemplate,
+ ])
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual(['first', 'second'])
+ })
+
+ it('reveals zero-delay messages through the timer path', () => {
+ const [message] = setOpeningSequenceMessages([{ id: 'immediate', delay: 0 }])
+
+ controller.startOpeningSequence()
+ expect(message.hidden).toBe(true)
+
+ jest.advanceTimersByTime(0)
+
+ expect(message.hidden).toBe(false)
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual(['immediate'])
+ })
+
+ it('clears pending opening sequence timers on teardown', () => {
+ const [message] = setOpeningSequenceMessages([{ id: 'late', delay: 5 }])
+
+ controller.startOpeningSequence()
+ controller.teardownOpeningSequence()
+ jest.advanceTimersByTime(5000)
+
+ expect(message.hidden).toBe(true)
+ expect(controller.openingSequenceTimeout).toBe(null)
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual([])
+ })
+
+ it('cancels pending messages and sends only revealed ids with a compose message', async () => {
+ const messages = setOpeningSequenceMessages([
+ { id: 'shown', delay: 0 },
+ { id: 'unrevealed', delay: 5 },
+ ])
+ setupComposeTargets()
+ setupSuccessfulMessageResponse()
+
+ controller.startOpeningSequence()
+ jest.advanceTimersByTime(0)
+
+ await controller.sendMessage({ target: controller.inputTarget })
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.getAll('message[opening_sequence_message_ids][]')).toEqual(['shown'])
+ expect(messages[1].hidden).toBe(true)
+ expect(controller.openingSequenceCancelled).toBe(true)
+ expect(controller.openingSequenceTimeout).toBe(null)
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual([])
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
+ 'hellotext:webchat:test-webchat-id:teaser-seen',
+ 'true',
+ )
+ })
+
+ it('keeps revealed ids when the first compose message fails', async () => {
+ setOpeningSequenceMessages([{ id: 'shown', delay: 0 }])
+ setupComposeTargets()
+ setupFailedMessageResponse()
+
+ controller.startOpeningSequence()
+ jest.advanceTimersByTime(0)
+
+ await controller.sendMessage({ target: controller.inputTarget })
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.getAll('message[opening_sequence_message_ids][]')).toEqual(['shown'])
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual(['shown'])
+ })
+
+ it('renders composed attachments into the plural attachments container', async () => {
+ const attachmentContainer = mockMessageTemplate.querySelector('[data-attachment-container]')
+ attachmentContainer.removeAttribute('data-attachment-container')
+ attachmentContainer.setAttribute('data-attachments-container', '')
+ setupComposeTargets()
+ setupSuccessfulMessageResponse()
+
+ const attachment = document.createElement('img')
+ attachment.src = 'https://example.com/composed.jpg'
+ controller.attachmentContainerTarget.appendChild(attachment)
+
+ await controller.sendMessage({ target: controller.inputTarget })
+
+ const addedElement = mockMessagesContainer.lastElementChild
+ const pluralAttachmentContainer = addedElement.querySelector('[data-attachments-container]')
+
+ expect(pluralAttachmentContainer.children).toHaveLength(1)
+ expect(pluralAttachmentContainer.children[0].src).toBe('https://example.com/composed.jpg')
+ })
+
+ it('sends revealed ids with a teaser quick reply', async () => {
+ setOpeningSequenceMessages([{ id: 'shown', delay: 0 }])
+ setupSuccessfulMessageResponse()
+ const button = document.createElement('button')
+ button.dataset.text = 'I need help'
+
+ controller.startOpeningSequence()
+ jest.advanceTimersByTime(0)
+
+ await controller.sendTeaserQuickReply({
+ currentTarget: button,
+ preventDefault: jest.fn(),
+ stopPropagation: jest.fn(),
+ })
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.getAll('message[opening_sequence_message_ids][]')).toEqual(['shown'])
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual([])
+ })
+
+ it('sends revealed ids with a message quick reply', async () => {
+ setOpeningSequenceMessages([{ id: 'shown', delay: 0 }])
+ setupSuccessfulMessageResponse()
+ controller.dispatch = jest.fn()
+ const cardElement = document.createElement('section')
+
+ controller.startOpeningSequence()
+ jest.advanceTimersByTime(0)
+
+ await controller.sendQuickReplyMessage({
+ detail: {
+ id: 'opening-message',
+ product: 'product-1',
+ buttonId: 'button-1',
+ body: 'Quick reply',
+ cardElement,
+ },
+ })
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.getAll('message[opening_sequence_message_ids][]')).toEqual(['shown'])
+ expect(controller.revealedOpeningSequenceMessageIds).toEqual([])
+ })
+ })
+
+ describe('onPopoverClosed', () => {
+ let mockHellotext
+ let mockLocalStorage
+
+ beforeEach(() => {
+ useTeaser(controller)
+ mockHellotext = {
+ eventEmitter: {
+ dispatch: jest.fn(),
+ },
+ }
+
+ // Mock localStorage
+ mockLocalStorage = {
+ setItem: jest.fn(),
+ getItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'localStorage', {
+ value: mockLocalStorage,
+ writable: true,
+ })
+
+ // Import and setup Hellotext mock
+ const Hellotext = require('../../src/hellotext').default
+ Object.assign(Hellotext, mockHellotext)
+ })
+
+ it('dispatches webchat:closed event', () => {
+ controller.onPopoverClosed()
+
+ expect(mockHellotext.eventEmitter.dispatch).toHaveBeenCalledWith('webchat:closed')
+ })
+
+ it('sets closed state in localStorage with correct key', () => {
+ controller.idValue = 'test-webchat-123'
+
+ controller.onPopoverClosed()
+
+ expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
+ 'hellotext--webchat--test-webchat-123',
+ 'closed',
+ )
+ })
+
+ it('does not restore the pre-conversation teaser after close', () => {
+ const teaser = document.createElement('div')
+ teaser.classList.add('invisible')
+ teaser.innerHTML = ''
+
+ controller.teaserTarget = teaser
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+ controller.startTeaserPresentation = jest.fn()
+
+ controller.onPopoverClosed()
+
+ expect(teaser.classList.contains('invisible')).toBe(true)
+ expect(controller.startTeaserPresentation).not.toHaveBeenCalled()
+ })
+
+ it('keeps the teaser hidden when closing after the session flag is set', () => {
+ const teaser = document.createElement('div')
+ teaser.classList.add('invisible')
+
+ controller.teaserTarget = teaser
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+
+ controller.onPopoverClosed()
+
+ expect(teaser.classList.contains('invisible')).toBe(true)
+ })
+ })
+
+ describe('onTeaserClick', () => {
+ let mockSessionStorage
+
+ beforeEach(() => {
+ useOpeningSequence(controller)
+ controller.setupOpeningSequence()
+ useTeaser(controller)
+ controller.teaserTarget = document.createElement('section')
+ controller.show = jest.fn()
+ mockSessionStorage = {
+ getItem: jest.fn().mockReturnValue(null),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'sessionStorage', {
+ value: mockSessionStorage,
+ writable: true,
+ })
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+ })
+
+ it('opens the webchat when the teaser surface is clicked', () => {
+ const teaser = document.createElement('section')
+
+ controller.onTeaserClick({ target: teaser })
+
+ expect(controller.show).toHaveBeenCalled()
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
+ 'hellotext:webchat:test-webchat-id:teaser-seen',
+ 'true',
+ )
+ })
+
+ it('lets anchor clicks behave normally', () => {
+ const anchor = document.createElement('a')
+ const label = document.createElement('span')
+ anchor.appendChild(label)
+
+ controller.onTeaserClick({ target: label })
+
+ expect(controller.show).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('sendTeaserQuickReply', () => {
+ let mockMessageTemplate
+ let mockMessagesAPI
+ let mockHellotext
+ let mockLocale
+ let mockBroadcastChannel
+ let mockWebChatChannel
+ let mockSessionStorage
+
+ beforeEach(() => {
+ useOpeningSequence(controller)
+ controller.setupOpeningSequence()
+ useTeaser(controller)
+ mockMessageTemplate = document.createElement('div')
+ mockMessageTemplate.style.display = 'none'
+ const bodyElement = document.createElement('div')
+ bodyElement.setAttribute('data-body', '')
+ mockMessageTemplate.appendChild(bodyElement)
+
+ controller.messageTemplateTarget = mockMessageTemplate
+ controller.messagesContainerTarget = mockMessagesContainer
+ controller.conversationIdValue = 'current-conversation'
+ controller.teaserTarget = document.createElement('section')
+ controller.show = jest.fn()
+ controller.resetTypingIndicatorTimer = jest.fn()
+ controller.typingIndicatorVisible = true
+
+ Object.defineProperty(controller, 'hasTypingIndicatorTarget', {
+ get: () => false,
+ configurable: true,
+ })
+
+ mockMessagesAPI = {
+ create: jest.fn(),
+ }
+ controller.messagesAPI = mockMessagesAPI
+
+ mockBroadcastChannel = {
+ postMessage: jest.fn(),
+ }
+ controller.broadcastChannel = mockBroadcastChannel
+
+ mockWebChatChannel = {
+ updateSubscriptionWith: jest.fn(),
+ }
+ controller.webChatChannel = mockWebChatChannel
+
+ mockSessionStorage = {
+ getItem: jest.fn().mockReturnValue(null),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'sessionStorage', {
+ value: mockSessionStorage,
+ writable: true,
+ })
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+
+ mockHellotext = {
+ session: 'test-session-123',
+ eventEmitter: {
+ dispatch: jest.fn(),
+ },
+ }
+ Object.assign(Hellotext, mockHellotext)
+
+ mockLocale = {
+ toString: jest.fn().mockReturnValue('en'),
+ }
+ const { Locale } = require('../../src/core/configuration/locale')
+ Object.assign(Locale, mockLocale)
+
+ Element.prototype.scrollIntoView = jest.fn()
+
+ mockMessagesAPI.create.mockResolvedValue({
+ failed: false,
+ json: jest.fn().mockResolvedValue({
+ id: 'server-message-123',
+ conversation: 'current-conversation',
+ }),
+ })
+ })
+
+ const buildTeaserButton = attributes => {
+ const button = document.createElement('button')
+ button.textContent = attributes.textContent || ''
+
+ Object.entries(attributes.dataset || {}).forEach(([key, value]) => {
+ button.dataset[key] = value
+ })
+
+ return button
+ }
+
+ const buildTeaserQuickReplyEvent = button => ({
+ currentTarget: button,
+ preventDefault: jest.fn(),
+ stopPropagation: jest.fn(),
+ })
+
+ it('prevents the teaser button click from submitting or opening twice', async () => {
+ const button = buildTeaserButton({ dataset: { text: 'I need help' } })
+ const event = buildTeaserQuickReplyEvent(button)
+
+ await controller.sendTeaserQuickReply(event)
+
+ expect(event.preventDefault).toHaveBeenCalled()
+ expect(event.stopPropagation).toHaveBeenCalled()
+ })
+
+ it('uses data-value for the sent body before other button content', async () => {
+ const button = buildTeaserButton({
+ textContent: 'Visible fallback',
+ dataset: {
+ text: 'Data text',
+ value: 'Data value',
+ },
+ })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.get('message[body]')).toBe('Data value')
+ })
+
+ it('falls back to data-text for the sent body', async () => {
+ const button = buildTeaserButton({
+ textContent: 'Visible fallback',
+ dataset: {
+ text: 'Data text',
+ },
+ })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.get('message[body]')).toBe('Data text')
+ })
+
+ it('ignores blank data-value and falls back to data-text', async () => {
+ const button = buildTeaserButton({
+ textContent: 'Visible fallback',
+ dataset: {
+ value: ' ',
+ text: 'Data text',
+ },
+ })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.get('message[body]')).toBe('Data text')
+ })
+
+ it('falls back to trimmed button text for the sent body', async () => {
+ const button = buildTeaserButton({ textContent: ' Visible fallback ' })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.get('message[body]')).toBe('Visible fallback')
+ })
+
+ it('does not open or send when the teaser button text is blank', async () => {
+ const button = buildTeaserButton({ textContent: ' ' })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ expect(controller.show).not.toHaveBeenCalled()
+ expect(mockMessagesAPI.create).not.toHaveBeenCalled()
+ expect(mockMessagesContainer.children).toHaveLength(0)
+ })
+
+ it('opens the webchat and creates form data for a customer message', async () => {
+ const button = buildTeaserButton({ dataset: { text: 'I need help' } })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ expect(controller.show).toHaveBeenCalled()
+ expect(mockMessagesAPI.create).toHaveBeenCalledWith(expect.any(FormData))
+
+ const formData = mockMessagesAPI.create.mock.calls[0][0]
+ expect(formData.get('message[body]')).toBe('I need help')
+ expect(formData.get('session')).toBe('test-session-123')
+ expect(formData.get('locale')).toBe('en')
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
+ 'hellotext:webchat:test-webchat-id:teaser-seen',
+ 'true',
+ )
+ expect(controller.teaserTarget.classList.contains('invisible')).toBe(true)
+ })
+
+ it('appends an optimistic customer bubble and broadcasts the sent message', async () => {
+ const button = buildTeaserButton({ dataset: { text: 'I need help' } })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ expect(mockMessagesContainer.children).toHaveLength(1)
+ const addedElement = mockMessagesContainer.children[0]
+
+ expect(addedElement.querySelector('[data-body]').innerText).toBe('I need help')
+ expect(addedElement.getAttribute('data-hellotext--webchat-target')).toBe('message')
+ expect(addedElement.scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' })
+ expect(mockBroadcastChannel.postMessage).toHaveBeenCalledWith({
+ type: 'message:sent',
+ element: expect.any(String),
+ })
+ })
+
+ it('sets the server id and dispatches a quick reply event without card reply fields', async () => {
+ const button = buildTeaserButton({
+ dataset: {
+ text: 'I need help',
+ value: 'need_help',
+ type: 'quick_reply',
+ },
+ })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ const addedElement = mockMessagesContainer.children[0]
+ expect(addedElement.getAttribute('data-id')).toBe('server-message-123')
+
+ expect(mockHellotext.eventEmitter.dispatch).toHaveBeenCalledWith('webchat:message:sent', {
+ id: 'server-message-123',
+ body: 'need_help',
+ attachments: [],
+ type: 'quick_reply',
+ teaser: {
+ text: 'I need help',
+ value: 'need_help',
+ type: 'quick_reply',
+ },
+ })
+
+ const message = mockHellotext.eventEmitter.dispatch.mock.calls[0][1]
+ expect(message).not.toHaveProperty('replied_to')
+ expect(message).not.toHaveProperty('product')
+ expect(message).not.toHaveProperty('button')
+ })
+
+ it('updates the webchat channel when the response changes conversation', async () => {
+ mockMessagesAPI.create.mockResolvedValue({
+ failed: false,
+ json: jest.fn().mockResolvedValue({
+ id: 'server-message-123',
+ conversation: 'new-conversation',
+ }),
+ })
+ const button = buildTeaserButton({ dataset: { text: 'I need help' } })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ expect(controller.conversationIdValue).toBe('new-conversation')
+ expect(mockWebChatChannel.updateSubscriptionWith).toHaveBeenCalledWith('new-conversation')
+ })
+
+ it('broadcasts and marks the optimistic bubble failed when sending fails', async () => {
+ mockMessagesAPI.create.mockResolvedValue({ failed: true })
+ const button = buildTeaserButton({ dataset: { text: 'I need help' } })
+
+ await controller.sendTeaserQuickReply(buildTeaserQuickReplyEvent(button))
+
+ const addedElement = mockMessagesContainer.children[0]
+ expect(mockBroadcastChannel.postMessage).toHaveBeenLastCalledWith({
+ type: 'message:failed',
+ id: addedElement.id,
+ })
+ expect(addedElement.classList.contains('failed')).toBe(true)
+ expect(mockHellotext.eventEmitter.dispatch).not.toHaveBeenCalledWith(
+ 'webchat:message:sent',
+ expect.anything(),
+ )
+ })
+ })
+
+ describe('sendQuickReplyMessage', () => {
+ let mockMessageTemplate
+ let mockMessagesAPI
+ let mockHellotext
+ let mockLocale
+ let mockCardElement
+ let mockImageElement
+ let mockBroadcastChannel
+ let mockClearTimeout
+ let mockSessionStorage
+
+ beforeEach(() => {
+ useOpeningSequence(controller)
+ controller.setupOpeningSequence()
+ useTeaser(controller)
+ // Set up message template mock
+ mockMessageTemplate = document.createElement('div')
+ mockMessageTemplate.id = 'template'
mockMessageTemplate.style.display = 'none'
const bodyElement = document.createElement('div')
bodyElement.setAttribute('data-body', '')
@@ -1082,10 +2427,11 @@ describe('WebchatController', () => {
// Mock the targets
controller.messageTemplateTarget = mockMessageTemplate
controller.messagesContainerTarget = mockMessagesContainer
+ controller.teaserTarget = document.createElement('section')
// Mock messagesAPI
mockMessagesAPI = {
- create: jest.fn()
+ create: jest.fn(),
}
controller.messagesAPI = mockMessagesAPI
@@ -1093,21 +2439,35 @@ describe('WebchatController', () => {
mockHellotext = {
session: 'test-session-123',
eventEmitter: {
- dispatch: jest.fn()
- }
+ dispatch: jest.fn(),
+ },
}
// Mock Locale
mockLocale = {
- toString: jest.fn().mockReturnValue('en')
+ toString: jest.fn().mockReturnValue('en'),
}
// Mock broadcast channel
mockBroadcastChannel = {
- postMessage: jest.fn()
+ postMessage: jest.fn(),
}
controller.broadcastChannel = mockBroadcastChannel
+ mockSessionStorage = {
+ getItem: jest.fn().mockReturnValue(null),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ }
+ Object.defineProperty(window, 'sessionStorage', {
+ value: mockSessionStorage,
+ writable: true,
+ })
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+
// Mock clearTimeout
mockClearTimeout = jest.fn()
global.clearTimeout = mockClearTimeout
@@ -1131,8 +2491,8 @@ describe('WebchatController', () => {
const mockResponse = {
failed: false,
json: jest.fn().mockResolvedValue({
- id: 'server-message-123'
- })
+ id: 'server-message-123',
+ }),
}
mockMessagesAPI.create.mockResolvedValue(mockResponse)
})
@@ -1143,7 +2503,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Quick reply message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1157,6 +2517,11 @@ describe('WebchatController', () => {
expect(formData.get('message[button]')).toBe('btn-789')
expect(formData.get('session')).toBe('test-session-123')
expect(formData.get('locale')).toBe('en')
+ expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
+ 'hellotext:webchat:test-webchat-id:teaser-seen',
+ 'true',
+ )
+ expect(controller.teaserTarget.classList.contains('invisible')).toBe(true)
})
it('builds and appends message element to container', async () => {
@@ -1165,7 +2530,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Test message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1182,7 +2547,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Message with image',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1198,13 +2563,35 @@ describe('WebchatController', () => {
expect(clonedImage.hasAttribute('height')).toBe(false)
})
+ it('clones attachment images into the plural attachments container', async () => {
+ const attachmentContainer = mockMessageTemplate.querySelector('[data-attachment-container]')
+ attachmentContainer.removeAttribute('data-attachment-container')
+ attachmentContainer.setAttribute('data-attachments-container', '')
+
+ await controller.sendQuickReplyMessage({
+ detail: {
+ id: 'msg-123',
+ product: 'product-456',
+ buttonId: 'btn-789',
+ body: 'Message with image',
+ cardElement: mockCardElement,
+ },
+ })
+
+ const addedElement = mockMessagesContainer.children[0]
+ const pluralAttachmentContainer = addedElement.querySelector('[data-attachments-container]')
+
+ expect(pluralAttachmentContainer.children).toHaveLength(1)
+ expect(pluralAttachmentContainer.children[0].src).toBe('https://example.com/product.jpg')
+ })
+
it('scrolls message into view smoothly', async () => {
const eventDetail = {
id: 'msg-123',
product: 'product-456',
buttonId: 'btn-789',
body: 'Scroll test',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1219,14 +2606,14 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Broadcast test',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
expect(mockBroadcastChannel.postMessage).toHaveBeenCalledWith({
type: 'message:sent',
- element: expect.any(String)
+ element: expect.any(String),
})
const broadcastCall = mockBroadcastChannel.postMessage.mock.calls[0][0]
@@ -1241,14 +2628,14 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'ID dispatch test',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
expect(controller.dispatch).toHaveBeenCalledWith('set:id', {
target: expect.any(Element),
- detail: 'server-message-123'
+ detail: 'server-message-123',
})
})
@@ -1258,7 +2645,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Event dispatch test',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1270,7 +2657,7 @@ describe('WebchatController', () => {
replied_to: 'original-123',
product: 'product-456',
button: 'btn-789',
- type: 'quick_reply'
+ type: 'quick_reply',
})
})
})
@@ -1278,7 +2665,7 @@ describe('WebchatController', () => {
describe('failed message sending', () => {
beforeEach(() => {
const mockFailedResponse = {
- failed: true
+ failed: true,
}
mockMessagesAPI.create.mockResolvedValue(mockFailedResponse)
controller.optimisticTypingTimeout = 'mock-timeout-id'
@@ -1290,7 +2677,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Failed message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1304,14 +2691,14 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Failed message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
expect(mockBroadcastChannel.postMessage).toHaveBeenCalledWith({
type: 'message:failed',
- id: expect.any(String)
+ id: expect.any(String),
})
})
@@ -1321,7 +2708,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Failed message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1336,7 +2723,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Failed message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1350,12 +2737,15 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Failed message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
- expect(mockHellotext.eventEmitter.dispatch).not.toHaveBeenCalledWith('webchat:message:sent', expect.anything())
+ expect(mockHellotext.eventEmitter.dispatch).not.toHaveBeenCalledWith(
+ 'webchat:message:sent',
+ expect.anything(),
+ )
})
})
@@ -1364,8 +2754,8 @@ describe('WebchatController', () => {
const mockResponse = {
failed: false,
json: jest.fn().mockResolvedValue({
- id: 'server-message-123'
- })
+ id: 'server-message-123',
+ }),
}
mockMessagesAPI.create.mockResolvedValue(mockResponse)
})
@@ -1379,7 +2769,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'No attachment message',
- cardElement: mockCardElementNoImage
+ cardElement: mockCardElementNoImage,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1399,7 +2789,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Null attachment message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1418,7 +2808,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: '',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1430,7 +2820,7 @@ describe('WebchatController', () => {
it('handles missing event detail properties', async () => {
const eventDetail = {
body: 'Minimal message',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1444,7 +2834,7 @@ describe('WebchatController', () => {
it('handles API response without id', async () => {
const mockResponse = {
failed: false,
- json: jest.fn().mockResolvedValue({})
+ json: jest.fn().mockResolvedValue({}),
}
mockMessagesAPI.create.mockResolvedValue(mockResponse)
@@ -1453,14 +2843,14 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'No ID response',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
expect(controller.dispatch).toHaveBeenCalledWith('set:id', {
target: expect.any(Element),
- detail: undefined
+ detail: undefined,
})
})
@@ -1472,18 +2862,20 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Network error test',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
- await expect(controller.sendQuickReplyMessage({ detail: eventDetail })).rejects.toThrow('Network error')
+ await expect(controller.sendQuickReplyMessage({ detail: eventDetail })).rejects.toThrow(
+ 'Network error',
+ )
})
it('inserts message before typing indicator when visible', async () => {
const mockResponse = {
failed: false,
json: jest.fn().mockResolvedValue({
- id: 'server-message-123'
- })
+ id: 'server-message-123',
+ }),
}
mockMessagesAPI.create.mockResolvedValue(mockResponse)
@@ -1495,7 +2887,7 @@ describe('WebchatController', () => {
controller.typingIndicatorTarget = mockTypingIndicator
controller.typingIndicatorVisible = true
Object.defineProperty(controller, 'hasTypingIndicatorTarget', {
- get: () => true
+ get: () => true,
})
const eventDetail = {
@@ -1503,7 +2895,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Insert before typing',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1518,14 +2910,14 @@ describe('WebchatController', () => {
const mockResponse = {
failed: false,
json: jest.fn().mockResolvedValue({
- id: 'server-message-123'
- })
+ id: 'server-message-123',
+ }),
}
mockMessagesAPI.create.mockResolvedValue(mockResponse)
controller.typingIndicatorVisible = false
Object.defineProperty(controller, 'hasTypingIndicatorTarget', {
- get: () => false
+ get: () => false,
})
const eventDetail = {
@@ -1533,7 +2925,7 @@ describe('WebchatController', () => {
product: 'product-456',
buttonId: 'btn-789',
body: 'Normal append',
- cardElement: mockCardElement
+ cardElement: mockCardElement,
}
await controller.sendQuickReplyMessage({ detail: eventDetail })
@@ -1549,18 +2941,18 @@ describe('WebchatController', () => {
beforeEach(() => {
// Mock DOMParser for carousel tests
global.DOMParser = jest.fn(() => ({
- parseFromString: jest.fn((html) => ({
+ parseFromString: jest.fn(html => ({
body: {
- firstElementChild: document.createElement('div')
- }
- }))
+ firstElementChild: document.createElement('div'),
+ },
+ })),
}))
// Mock Hellotext
mockHellotext = {
eventEmitter: {
- dispatch: jest.fn()
- }
+ dispatch: jest.fn(),
+ },
}
const Hellotext = require('../../src/hellotext').default
@@ -1570,14 +2962,14 @@ describe('WebchatController', () => {
it('handles carousel messages correctly', () => {
controller.openValue = true // Set to open to avoid unread counter issues
const mockMessagesAPI = {
- markAsSeen: jest.fn()
+ markAsSeen: jest.fn(),
}
controller.messagesAPI = mockMessagesAPI
const message = {
id: 'carousel-123',
html: 'Test Carousel
',
- carousel: { title: 'Test' }
+ carousel: { title: 'Test' },
}
controller.onMessageReceived(message)
@@ -1589,17 +2981,38 @@ describe('WebchatController', () => {
})
})
+ it('silently drops a duplicate carousel message that was already claimed in memory', () => {
+ controller.openValue = true
+ const mockMessagesAPI = {
+ markAsSeen: jest.fn(),
+ }
+ controller.messagesAPI = mockMessagesAPI
+
+ const message = {
+ id: 'carousel-duplicate',
+ html: 'Carousel
',
+ carousel: {},
+ }
+
+ controller.onMessageReceived(message)
+ controller.onMessageReceived(message)
+
+ expect(mockMessagesContainer.children).toHaveLength(1)
+ expect(mockHellotext.eventEmitter.dispatch).toHaveBeenCalledTimes(1)
+ expect(mockMessagesAPI.markAsSeen).toHaveBeenCalledTimes(1)
+ })
+
it('marks carousel as seen when chat is open', () => {
controller.openValue = true
const mockMessagesAPI = {
- markAsSeen: jest.fn()
+ markAsSeen: jest.fn(),
}
controller.messagesAPI = mockMessagesAPI
const message = {
id: 'carousel-read',
html: 'Carousel
',
- carousel: {}
+ carousel: {},
}
controller.onMessageReceived(message)
@@ -1616,7 +3029,7 @@ describe('WebchatController', () => {
const message = {
id: 'carousel-unread',
html: 'Carousel
',
- carousel: {}
+ carousel: {},
}
controller.onMessageReceived(message)
@@ -1624,5 +3037,77 @@ describe('WebchatController', () => {
expect(mockUnreadCounter.style.display).toBe('flex')
expect(mockUnreadCounter.innerText).toBe(3)
})
+
+ it('clamps the unread counter at 9 for carousel messages', () => {
+ controller.openValue = false
+ const mockUnreadCounter = document.createElement('div')
+ mockUnreadCounter.innerText = '9'
+ controller.unreadCounterTarget = mockUnreadCounter
+
+ controller.onMessageReceived({
+ id: 'carousel-unread-cap',
+ html: 'Carousel
',
+ carousel: {},
+ })
+
+ expect(mockUnreadCounter.style.display).toBe('flex')
+ expect(mockUnreadCounter.innerText).toBe(9)
+ })
+
+ it('shows the message teaser for carousel messages when chat is closed', () => {
+ controller.openValue = false
+ const mockUnreadCounter = document.createElement('div')
+ mockUnreadCounter.innerText = '0'
+ controller.unreadCounterTarget = mockUnreadCounter
+
+ const teaser = document.createElement('section')
+ teaser.classList.add('invisible')
+ controller.teaserTarget = teaser
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+
+ const message = {
+ id: 'carousel-teaser-closed',
+ html: 'Carousel
',
+ carousel: {},
+ teaser: 'Carousel teaser',
+ }
+
+ controller.onMessageReceived(message)
+
+ expect(mockUnreadCounter.style.display).toBe('flex')
+ expect(teaser.innerHTML).toBe('Carousel teaser')
+ expect(teaser.classList.contains('invisible')).toBe(false)
+ })
+
+ it('hides the message teaser for carousel messages when chat is open', () => {
+ controller.openValue = true
+ const mockMessagesAPI = {
+ markAsSeen: jest.fn(),
+ }
+ controller.messagesAPI = mockMessagesAPI
+
+ const teaser = document.createElement('section')
+ controller.teaserTarget = teaser
+ Object.defineProperty(controller, 'hasTeaserTarget', {
+ get: () => true,
+ configurable: true,
+ })
+
+ const message = {
+ id: 'carousel-teaser-open',
+ html: 'Carousel
',
+ carousel: {},
+ teaser: 'Carousel teaser',
+ }
+
+ controller.onMessageReceived(message)
+
+ expect(teaser.innerHTML).toBe('Carousel teaser')
+ expect(teaser.classList.contains('invisible')).toBe(true)
+ expect(mockMessagesAPI.markAsSeen).toHaveBeenCalledWith('carousel-teaser-open')
+ })
})
})
diff --git a/__tests__/core/configuration/webchat_test.js b/__tests__/core/configuration/webchat_test.js
index d6d6a1b..112db01 100644
--- a/__tests__/core/configuration/webchat_test.js
+++ b/__tests__/core/configuration/webchat_test.js
@@ -2,23 +2,59 @@ import { Webchat } from '../../../src/core/configuration/webchat';
beforeEach(() => {
Webchat.strategy = null
+ Webchat.mode = 'popover'
+ Webchat.behaviour = null
+ Webchat.behaviourOverride = false
+ Webchat.appearance = {}
+ Webchat.whatsapp = {}
})
describe('Webchat', () => {
- describe('behaviour', () => {
+ describe('mode', () => {
it('is POPOVER by default', () => {
- expect(Webchat.behaviour).toEqual('popover')
+ expect(Webchat.mode).toEqual('popover')
});
it('can be set to modal', () => {
- Webchat.behaviour = 'modal'
- expect(Webchat.behaviour).toEqual('modal')
+ Webchat.mode = 'modal'
+ expect(Webchat.mode).toEqual('modal')
});
it('throws an exception when an invalid value is supplied', () => {
expect(() => {
- Webchat.behaviour = 'invalid'
- }).toThrowError('Invalid behaviour value: invalid')
+ Webchat.mode = 'invalid'
+ }).toThrowError('Invalid mode value: invalid')
+ });
+ })
+
+ describe('behaviour', () => {
+ it('is null by default', () => {
+ expect(Webchat.behaviour).toBe(null)
+ });
+
+ it('can be set to an auto-open behaviour object', () => {
+ const behaviour = {
+ trigger: 'onLoad',
+ delaySeconds: 5,
+ firstVisitOnly: true,
+ oncePerSession: true
+ }
+
+ Webchat.behaviour = behaviour
+
+ expect(Webchat.behaviour).toEqual(behaviour)
+ });
+
+ it('throws an exception when the old modal/popover string is supplied', () => {
+ expect(() => {
+ Webchat.behaviour = 'modal'
+ }).toThrowError('Invalid behaviour value: modal')
+ });
+
+ it('tracks whether an explicit behaviour override was supplied', () => {
+ Webchat.behaviourOverride = true
+
+ expect(Webchat.hasBehaviourOverride).toBe(true)
});
})
@@ -84,68 +120,6 @@ describe('Webchat', () => {
});
})
- describe('classes', () => {
- it('is an empty array by default', () => {
- expect(Webchat.classes).toEqual([])
- });
-
- describe('setting value to a String', () => {
- it('can be set to a string value', () => {
- Webchat.classes = 'custom-class'
- });
-
- it('returns an array of the values', () => {
- Webchat.classes = 'custom-class, another-class'
- expect(Webchat.classes).toEqual(['custom-class', 'another-class'])
- });
- });
-
- it('can be set to an Array', () => {
- Webchat.classes = ['custom-class']
- expect(Webchat.classes).toEqual(['custom-class'])
- });
-
- it('throws an exception when an invalid value is supplied', () => {
- expect(() => {
- Webchat.classes = { invalid: 'value' }
- }).toThrowError('classes must be an array or a string')
- });
- })
-
- describe('triggerClasses', () => {
- it('is an empty array by default', () => {
- expect(Webchat.triggerClasses).toEqual([undefined])
- });
-
- describe('setting value to a String', () => {
- it('can be set to a string value', () => {
- Webchat.triggerClasses = 'custom-class'
- });
-
- it('returns an array of the values', () => {
- Webchat.triggerClasses = 'custom-class, another-class'
- expect(Webchat.triggerClasses).toEqual(['custom-class', 'another-class'])
- });
- });
-
- describe('when setting value to an Array', () => {
- it('can be set', () => {
- Webchat.triggerClasses = ['custom-class']
- });
-
- it('returns the value', () => {
- Webchat.triggerClasses = ['custom-class', 'another-class']
- expect(Webchat.triggerClasses).toEqual(['custom-class', 'another-class'])
- });
- })
-
- it('throws an exception when an invalid value is supplied', () => {
- expect(() => {
- Webchat.triggerClasses = { invalid: 'value' }
- }).toThrowError('triggerClasses must be an array or a string')
- });
- })
-
describe('styles', () => {
it('raises an exception when an invalid style is set', () => {
expect(() => {
@@ -198,4 +172,116 @@ describe('Webchat', () => {
});
})
})
+
+ describe('appearance', () => {
+ it('is an empty object by default', () => {
+ expect(Webchat.appearance).toEqual({})
+ })
+
+ it('accepts header and launcher overrides', () => {
+ Webchat.appearance = {
+ header: {
+ name: 'Acme Support'
+ },
+ launcher: {
+ iconUrl: 'https://example.com/icon.png'
+ }
+ }
+
+ expect(Webchat.appearance).toEqual({
+ header: {
+ name: 'Acme Support'
+ },
+ launcher: {
+ iconUrl: 'https://example.com/icon.png'
+ }
+ })
+ })
+
+ it('accepts null optional leaf values from dashboard defaults', () => {
+ Webchat.appearance = {
+ header: {
+ name: null
+ },
+ launcher: {
+ iconUrl: null
+ }
+ }
+
+ expect(Webchat.appearance).toEqual({
+ header: {
+ name: null
+ },
+ launcher: {
+ iconUrl: null
+ }
+ })
+ })
+
+ it('throws an exception when the value is not an object', () => {
+ expect(() => {
+ Webchat.appearance = 'invalid'
+ }).toThrowError('Appearance must be an object')
+ })
+
+ it('throws an exception when a nested value is not an object', () => {
+ expect(() => {
+ Webchat.appearance = { header: 'Acme Support' }
+ }).toThrowError('Appearance header must be an object')
+ })
+
+ it('throws an exception when a nested string value is invalid', () => {
+ expect(() => {
+ Webchat.appearance = { header: { name: 123 } }
+ }).toThrowError('Invalid appearance header.name value: 123')
+ })
+ })
+
+ describe('whatsapp', () => {
+ it('is an empty object by default', () => {
+ expect(Webchat.whatsapp).toEqual({})
+ })
+
+ it('accepts number and restrictToChannel overrides', () => {
+ Webchat.whatsapp = {
+ number: '+15551234567',
+ restrictToChannel: false
+ }
+
+ expect(Webchat.whatsapp).toEqual({
+ number: '+15551234567',
+ restrictToChannel: false
+ })
+ })
+
+ it('accepts a null optional number from dashboard defaults', () => {
+ Webchat.whatsapp = {
+ number: null,
+ restrictToChannel: false
+ }
+
+ expect(Webchat.whatsapp).toEqual({
+ number: null,
+ restrictToChannel: false
+ })
+ })
+
+ it('throws an exception when the value is not an object', () => {
+ expect(() => {
+ Webchat.whatsapp = 'invalid'
+ }).toThrowError('WhatsApp must be an object')
+ })
+
+ it('throws an exception when number is not a string', () => {
+ expect(() => {
+ Webchat.whatsapp = { number: 123 }
+ }).toThrowError('Invalid WhatsApp number value: 123')
+ })
+
+ it('throws an exception when restrictToChannel is not a boolean', () => {
+ expect(() => {
+ Webchat.whatsapp = { restrictToChannel: 'false' }
+ }).toThrowError('Invalid WhatsApp restrictToChannel value: false')
+ })
+ })
})
diff --git a/__tests__/hellotext_test.js b/__tests__/hellotext_test.js
index 1f04fb1..98b76cc 100644
--- a/__tests__/hellotext_test.js
+++ b/__tests__/hellotext_test.js
@@ -55,6 +55,10 @@ describe("when initializing business metadata", () => {
afterEach(() => {
loadWebchat.mockRestore()
+ Configuration.webchat.behaviour = null
+ Configuration.webchat.behaviourOverride = false
+ Configuration.webchat.appearance = {}
+ Configuration.webchat.whatsapp = {}
})
it("fetches public business data by default and stores it", async () => {
@@ -90,6 +94,49 @@ describe("when initializing business metadata", () => {
expect(Configuration.webchat.placement).toEqual("top-left")
})
+ it("tracks explicit local webchat behaviour overrides", async () => {
+ mockBusinessFetch(defaultBusiness({ webchat: { id: "dashboard-webchat" } }))
+
+ await Hellotext.initialize("xy76ks", {
+ webchat: {
+ behaviour: {
+ trigger: "onLoad",
+ delaySeconds: 5,
+ firstVisitOnly: true,
+ oncePerSession: true,
+ },
+ },
+ })
+
+ expect(loadWebchat).toHaveBeenCalledWith("dashboard-webchat")
+ expect(Configuration.webchat.hasBehaviourOverride).toBe(true)
+ expect(Configuration.webchat.behaviour).toEqual({
+ trigger: "onLoad",
+ delaySeconds: 5,
+ firstVisitOnly: true,
+ oncePerSession: true,
+ })
+ })
+
+ it("does not treat dashboard webchat behaviour as an explicit local override", async () => {
+ mockBusinessFetch(defaultBusiness({
+ webchat: {
+ id: "dashboard-webchat",
+ behaviour: {
+ trigger: "onLoad",
+ delaySeconds: 10,
+ firstVisitOnly: false,
+ oncePerSession: true,
+ },
+ },
+ }))
+
+ await Hellotext.initialize("xy76ks")
+
+ expect(loadWebchat).toHaveBeenCalledWith("dashboard-webchat")
+ expect(Configuration.webchat.hasBehaviourOverride).toBe(false)
+ })
+
it("lets an explicit webchat id override the dashboard webchat id", async () => {
mockBusinessFetch(defaultBusiness({ webchat: { id: "dashboard-webchat" } }))
@@ -102,6 +149,53 @@ describe("when initializing business metadata", () => {
expect(loadWebchat).toHaveBeenCalledWith("explicit-webchat")
})
+ it("deep merges explicit local webchat appearance and WhatsApp overrides with dashboard defaults", async () => {
+ mockBusinessFetch(defaultBusiness({
+ webchat: {
+ id: "dashboard-webchat",
+ appearance: {
+ header: {
+ name: "Dashboard Support",
+ },
+ launcher: {
+ iconUrl: "https://example.com/dashboard-icon.png",
+ },
+ },
+ whatsapp: {
+ number: "+15550000000",
+ restrictToChannel: true,
+ },
+ },
+ }))
+
+ await Hellotext.initialize("xy76ks", {
+ webchat: {
+ appearance: {
+ header: {
+ name: "Local Support",
+ },
+ },
+ whatsapp: {
+ restrictToChannel: false,
+ },
+ },
+ })
+
+ expect(loadWebchat).toHaveBeenCalledWith("dashboard-webchat")
+ expect(Configuration.webchat.appearance).toEqual({
+ header: {
+ name: "Local Support",
+ },
+ launcher: {
+ iconUrl: "https://example.com/dashboard-icon.png",
+ },
+ })
+ expect(Configuration.webchat.whatsapp).toEqual({
+ number: "+15550000000",
+ restrictToChannel: false,
+ })
+ })
+
it("skips webchat loading when webchat is false", async () => {
mockBusinessFetch(defaultBusiness({ webchat: { id: "dashboard-webchat" } }))
diff --git a/__tests__/models/webchat_test.js b/__tests__/models/webchat_test.js
new file mode 100644
index 0000000..29615f7
--- /dev/null
+++ b/__tests__/models/webchat_test.js
@@ -0,0 +1,76 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import API from '../../src/api'
+import { Configuration } from '../../src/core'
+import { Webchat } from '../../src/models'
+
+describe('Webchat', () => {
+ beforeEach(() => {
+ document.body.innerHTML = ''
+
+ Configuration.webchat.container = '#webchat-container'
+ Configuration.webchat.behaviour = null
+ Configuration.webchat.behaviourOverride = false
+
+ jest.spyOn(API.webchats, 'get')
+ })
+
+ afterEach(() => {
+ jest.restoreAllMocks()
+ document.body.innerHTML = ''
+
+ Configuration.webchat.container = 'body'
+ Configuration.webchat.behaviour = null
+ Configuration.webchat.behaviourOverride = false
+ })
+
+ it('serializes an explicit camelCase behaviour override onto the Stimulus value', async () => {
+ const article = document.createElement('article')
+ API.webchats.get.mockResolvedValue(article)
+
+ Configuration.webchat.behaviour = {
+ trigger: 'onLoad',
+ delaySeconds: 5,
+ firstVisitOnly: true,
+ oncePerSession: true
+ }
+ Configuration.webchat.behaviourOverride = true
+
+ await Webchat.load('webchat-id')
+
+ expect(API.webchats.get).toHaveBeenCalledWith('webchat-id')
+ expect(document.querySelector('#webchat-container article')).toBe(article)
+ expect(JSON.parse(article.getAttribute('data-hellotext--webchat-behaviour-value'))).toEqual({
+ trigger: 'on_load',
+ delay_seconds: 5,
+ first_visit_only: true,
+ once_per_session: true
+ })
+ })
+
+ it('preserves the rendered behaviour value when no explicit JS override exists', async () => {
+ const article = document.createElement('article')
+ const renderedBehaviour = JSON.stringify({
+ trigger: 'on_load',
+ delay_seconds: 10,
+ first_visit_only: false,
+ once_per_session: true
+ })
+ article.setAttribute('data-hellotext--webchat-behaviour-value', renderedBehaviour)
+ API.webchats.get.mockResolvedValue(article)
+
+ Configuration.webchat.behaviour = {
+ trigger: 'onLoad',
+ delaySeconds: 5,
+ firstVisitOnly: true,
+ oncePerSession: true
+ }
+ Configuration.webchat.behaviourOverride = false
+
+ await Webchat.load('webchat-id')
+
+ expect(article.getAttribute('data-hellotext--webchat-behaviour-value')).toBe(renderedBehaviour)
+ })
+})
diff --git a/dist/hellotext.js b/dist/hellotext.js
index 2fe96f2..cffab6f 100644
--- a/dist/hellotext.js
+++ b/dist/hellotext.js
@@ -1 +1 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Hellotext=t():e.Hellotext=t()}("undefined"!=typeof self?self:this,(()=>(()=>{"use strict";var e={599:(e,t,n)=>{n.d(t,{Mx:()=>J,Qr:()=>ie});class r{constructor(e,t,n){this.eventTarget=e,this.eventName=t,this.eventOptions=n,this.unorderedBindings=new Set}connect(){this.eventTarget.addEventListener(this.eventName,this,this.eventOptions)}disconnect(){this.eventTarget.removeEventListener(this.eventName,this,this.eventOptions)}bindingConnected(e){this.unorderedBindings.add(e)}bindingDisconnected(e){this.unorderedBindings.delete(e)}handleEvent(e){const t=function(e){if("immediatePropagationStopped"in e)return e;{const{stopImmediatePropagation:t}=e;return Object.assign(e,{immediatePropagationStopped:!1,stopImmediatePropagation(){this.immediatePropagationStopped=!0,t.call(this)}})}}(e);for(const e of this.bindings){if(t.immediatePropagationStopped)break;e.handleEvent(t)}}hasBindings(){return this.unorderedBindings.size>0}get bindings(){return Array.from(this.unorderedBindings).sort(((e,t)=>{const n=e.index,r=t.index;return nr?1:0}))}}class i{constructor(e){this.application=e,this.eventListenerMaps=new Map,this.started=!1}start(){this.started||(this.started=!0,this.eventListeners.forEach((e=>e.connect())))}stop(){this.started&&(this.started=!1,this.eventListeners.forEach((e=>e.disconnect())))}get eventListeners(){return Array.from(this.eventListenerMaps.values()).reduce(((e,t)=>e.concat(Array.from(t.values()))),[])}bindingConnected(e){this.fetchEventListenerForBinding(e).bindingConnected(e)}bindingDisconnected(e,t=!1){this.fetchEventListenerForBinding(e).bindingDisconnected(e),t&&this.clearEventListenersForBinding(e)}handleError(e,t,n={}){this.application.handleError(e,`Error ${t}`,n)}clearEventListenersForBinding(e){const t=this.fetchEventListenerForBinding(e);t.hasBindings()||(t.disconnect(),this.removeMappedEventListenerFor(e))}removeMappedEventListenerFor(e){const{eventTarget:t,eventName:n,eventOptions:r}=e,i=this.fetchEventListenerMapForEventTarget(t),o=this.cacheKey(n,r);i.delete(o),0==i.size&&this.eventListenerMaps.delete(t)}fetchEventListenerForBinding(e){const{eventTarget:t,eventName:n,eventOptions:r}=e;return this.fetchEventListener(t,n,r)}fetchEventListener(e,t,n){const r=this.fetchEventListenerMapForEventTarget(e),i=this.cacheKey(t,n);let o=r.get(i);return o||(o=this.createEventListener(e,t,n),r.set(i,o)),o}createEventListener(e,t,n){const i=new r(e,t,n);return this.started&&i.connect(),i}fetchEventListenerMapForEventTarget(e){let t=this.eventListenerMaps.get(e);return t||(t=new Map,this.eventListenerMaps.set(e,t)),t}cacheKey(e,t){const n=[e];return Object.keys(t).sort().forEach((e=>{n.push(`${t[e]?"":"!"}${e}`)})),n.join(":")}}const o={stop:({event:e,value:t})=>(t&&e.stopPropagation(),!0),prevent:({event:e,value:t})=>(t&&e.preventDefault(),!0),self:({event:e,value:t,element:n})=>!t||n===e.target},s=/^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;function a(e){return e.replace(/(?:[_-])([a-z0-9])/g,((e,t)=>t.toUpperCase()))}function l(e){return a(e.replace(/--/g,"-").replace(/__/g,"_"))}function c(e){return e.charAt(0).toUpperCase()+e.slice(1)}function u(e){return e.replace(/([A-Z])/g,((e,t)=>`-${t.toLowerCase()}`))}function h(e){return null!=e}function d(e,t){return Object.prototype.hasOwnProperty.call(e,t)}const f=["meta","ctrl","alt","shift"];class p{constructor(e,t,n,r){this.element=e,this.index=t,this.eventTarget=n.eventTarget||e,this.eventName=n.eventName||function(e){const t=e.tagName.toLowerCase();if(t in m)return m[t](e)}(e)||g("missing event name"),this.eventOptions=n.eventOptions||{},this.identifier=n.identifier||g("missing identifier"),this.methodName=n.methodName||g("missing method name"),this.keyFilter=n.keyFilter||"",this.schema=r}static forToken(e,t){return new this(e.element,e.index,function(e){const t=e.trim().match(s)||[];let n=t[2],r=t[3];return r&&!["keydown","keyup","keypress"].includes(n)&&(n+=`.${r}`,r=""),{eventTarget:(i=t[4],"window"==i?window:"document"==i?document:void 0),eventName:n,eventOptions:t[7]?(o=t[7],o.split(":").reduce(((e,t)=>Object.assign(e,{[t.replace(/^!/,"")]:!/^!/.test(t)})),{})):{},identifier:t[5],methodName:t[6],keyFilter:t[1]||r};var i,o}(e.content),t)}toString(){const e=this.keyFilter?`.${this.keyFilter}`:"",t=this.eventTargetName?`@${this.eventTargetName}`:"";return`${this.eventName}${e}${t}->${this.identifier}#${this.methodName}`}shouldIgnoreKeyboardEvent(e){if(!this.keyFilter)return!1;const t=this.keyFilter.split("+");if(this.keyFilterDissatisfied(e,t))return!0;const n=t.filter((e=>!f.includes(e)))[0];return!!n&&(d(this.keyMappings,n)||g(`contains unknown key filter: ${this.keyFilter}`),this.keyMappings[n].toLowerCase()!==e.key.toLowerCase())}shouldIgnoreMouseEvent(e){if(!this.keyFilter)return!1;const t=[this.keyFilter];return!!this.keyFilterDissatisfied(e,t)}get params(){const e={},t=new RegExp(`^data-${this.identifier}-(.+)-param$`,"i");for(const{name:n,value:r}of Array.from(this.element.attributes)){const i=n.match(t),o=i&&i[1];o&&(e[a(o)]=v(r))}return e}get eventTargetName(){return(e=this.eventTarget)==window?"window":e==document?"document":void 0;var e}get keyMappings(){return this.schema.keyMappings}keyFilterDissatisfied(e,t){const[n,r,i,o]=f.map((e=>t.includes(e)));return e.metaKey!==n||e.ctrlKey!==r||e.altKey!==i||e.shiftKey!==o}}const m={a:()=>"click",button:()=>"click",form:()=>"submit",details:()=>"toggle",input:e=>"submit"==e.getAttribute("type")?"click":"input",select:()=>"change",textarea:()=>"input"};function g(e){throw new Error(e)}function v(e){try{return JSON.parse(e)}catch(t){return e}}class y{constructor(e,t){this.context=e,this.action=t}get index(){return this.action.index}get eventTarget(){return this.action.eventTarget}get eventOptions(){return this.action.eventOptions}get identifier(){return this.context.identifier}handleEvent(e){const t=this.prepareActionEvent(e);this.willBeInvokedByEvent(e)&&this.applyEventModifiers(t)&&this.invokeWithEvent(t)}get eventName(){return this.action.eventName}get method(){const e=this.controller[this.methodName];if("function"==typeof e)return e;throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`)}applyEventModifiers(e){const{element:t}=this.action,{actionDescriptorFilters:n}=this.context.application,{controller:r}=this.context;let i=!0;for(const[o,s]of Object.entries(this.eventOptions))if(o in n){const a=n[o];i=i&&a({name:o,value:s,event:e,element:t,controller:r})}return i}prepareActionEvent(e){return Object.assign(e,{params:this.action.params})}invokeWithEvent(e){const{target:t,currentTarget:n}=e;try{this.method.call(this.controller,e),this.context.logDebugActivity(this.methodName,{event:e,target:t,currentTarget:n,action:this.methodName})}catch(t){const{identifier:n,controller:r,element:i,index:o}=this,s={identifier:n,controller:r,element:i,index:o,event:e};this.context.handleError(t,`invoking action "${this.action}"`,s)}}willBeInvokedByEvent(e){const t=e.target;return!(e instanceof KeyboardEvent&&this.action.shouldIgnoreKeyboardEvent(e))&&!(e instanceof MouseEvent&&this.action.shouldIgnoreMouseEvent(e))&&(this.element===t||(t instanceof Element&&this.element.contains(t)?this.scope.containsElement(t):this.scope.containsElement(this.action.element)))}get controller(){return this.context.controller}get methodName(){return this.action.methodName}get element(){return this.scope.element}get scope(){return this.context.scope}}class b{constructor(e,t){this.mutationObserverInit={attributes:!0,childList:!0,subtree:!0},this.element=e,this.started=!1,this.delegate=t,this.elements=new Set,this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,this.mutationObserverInit),this.refresh())}pause(e){this.started&&(this.mutationObserver.disconnect(),this.started=!1),e(),this.started||(this.mutationObserver.observe(this.element,this.mutationObserverInit),this.started=!0)}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started){const e=new Set(this.matchElementsInTree());for(const t of Array.from(this.elements))e.has(t)||this.removeElement(t);for(const t of Array.from(e))this.addElement(t)}}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){"attributes"==e.type?this.processAttributeChange(e.target,e.attributeName):"childList"==e.type&&(this.processRemovedNodes(e.removedNodes),this.processAddedNodes(e.addedNodes))}processAttributeChange(e,t){this.elements.has(e)?this.delegate.elementAttributeChanged&&this.matchElement(e)?this.delegate.elementAttributeChanged(e,t):this.removeElement(e):this.matchElement(e)&&this.addElement(e)}processRemovedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.processTree(e,this.removeElement)}}processAddedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.elementIsActive(e)&&this.processTree(e,this.addElement)}}matchElement(e){return this.delegate.matchElement(e)}matchElementsInTree(e=this.element){return this.delegate.matchElementsInTree(e)}processTree(e,t){for(const n of this.matchElementsInTree(e))t.call(this,n)}elementFromNode(e){if(e.nodeType==Node.ELEMENT_NODE)return e}elementIsActive(e){return e.isConnected==this.element.isConnected&&this.element.contains(e)}addElement(e){this.elements.has(e)||this.elementIsActive(e)&&(this.elements.add(e),this.delegate.elementMatched&&this.delegate.elementMatched(e))}removeElement(e){this.elements.has(e)&&(this.elements.delete(e),this.delegate.elementUnmatched&&this.delegate.elementUnmatched(e))}}class w{constructor(e,t,n){this.attributeName=t,this.delegate=n,this.elementObserver=new b(e,this)}get element(){return this.elementObserver.element}get selector(){return`[${this.attributeName}]`}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get started(){return this.elementObserver.started}matchElement(e){return e.hasAttribute(this.attributeName)}matchElementsInTree(e){const t=this.matchElement(e)?[e]:[],n=Array.from(e.querySelectorAll(this.selector));return t.concat(n)}elementMatched(e){this.delegate.elementMatchedAttribute&&this.delegate.elementMatchedAttribute(e,this.attributeName)}elementUnmatched(e){this.delegate.elementUnmatchedAttribute&&this.delegate.elementUnmatchedAttribute(e,this.attributeName)}elementAttributeChanged(e,t){this.delegate.elementAttributeValueChanged&&this.attributeName==t&&this.delegate.elementAttributeValueChanged(e,t)}}function _(e,t){let n=e.get(t);return n||(n=new Set,e.set(t,n)),n}class k{constructor(){this.valuesByKey=new Map}get keys(){return Array.from(this.valuesByKey.keys())}get values(){return Array.from(this.valuesByKey.values()).reduce(((e,t)=>e.concat(Array.from(t))),[])}get size(){return Array.from(this.valuesByKey.values()).reduce(((e,t)=>e+t.size),0)}add(e,t){!function(e,t,n){_(e,t).add(n)}(this.valuesByKey,e,t)}delete(e,t){!function(e,t,n){_(e,t).delete(n),function(e,t){const n=e.get(t);null!=n&&0==n.size&&e.delete(t)}(e,t)}(this.valuesByKey,e,t)}has(e,t){const n=this.valuesByKey.get(e);return null!=n&&n.has(t)}hasKey(e){return this.valuesByKey.has(e)}hasValue(e){return Array.from(this.valuesByKey.values()).some((t=>t.has(e)))}getValuesForKey(e){const t=this.valuesByKey.get(e);return t?Array.from(t):[]}getKeysForValue(e){return Array.from(this.valuesByKey).filter((([t,n])=>n.has(e))).map((([e,t])=>e))}}class O{constructor(e,t,n,r){this._selector=t,this.details=r,this.elementObserver=new b(e,this),this.delegate=n,this.matchesByElement=new k}get started(){return this.elementObserver.started}get selector(){return this._selector}set selector(e){this._selector=e,this.refresh()}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get element(){return this.elementObserver.element}matchElement(e){const{selector:t}=this;if(t){const n=e.matches(t);return this.delegate.selectorMatchElement?n&&this.delegate.selectorMatchElement(e,this.details):n}return!1}matchElementsInTree(e){const{selector:t}=this;if(t){const n=this.matchElement(e)?[e]:[],r=Array.from(e.querySelectorAll(t)).filter((e=>this.matchElement(e)));return n.concat(r)}return[]}elementMatched(e){const{selector:t}=this;t&&this.selectorMatched(e,t)}elementUnmatched(e){const t=this.matchesByElement.getKeysForValue(e);for(const n of t)this.selectorUnmatched(e,n)}elementAttributeChanged(e,t){const{selector:n}=this;if(n){const t=this.matchElement(e),r=this.matchesByElement.has(n,e);t&&!r?this.selectorMatched(e,n):!t&&r&&this.selectorUnmatched(e,n)}}selectorMatched(e,t){this.delegate.selectorMatched(e,t,this.details),this.matchesByElement.add(t,e)}selectorUnmatched(e,t){this.delegate.selectorUnmatched(e,t,this.details),this.matchesByElement.delete(t,e)}}class C{constructor(e,t){this.element=e,this.delegate=t,this.started=!1,this.stringMap=new Map,this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,{attributes:!0,attributeOldValue:!0}),this.refresh())}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started)for(const e of this.knownAttributeNames)this.refreshAttribute(e,null)}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){const t=e.attributeName;t&&this.refreshAttribute(t,e.oldValue)}refreshAttribute(e,t){const n=this.delegate.getStringMapKeyForAttribute(e);if(null!=n){this.stringMap.has(e)||this.stringMapKeyAdded(n,e);const r=this.element.getAttribute(e);if(this.stringMap.get(e)!=r&&this.stringMapValueChanged(r,n,t),null==r){const t=this.stringMap.get(e);this.stringMap.delete(e),t&&this.stringMapKeyRemoved(n,e,t)}else this.stringMap.set(e,r)}}stringMapKeyAdded(e,t){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(e,t)}stringMapValueChanged(e,t,n){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(e,t,n)}stringMapKeyRemoved(e,t,n){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(e,t,n)}get knownAttributeNames(){return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)))}get currentAttributeNames(){return Array.from(this.element.attributes).map((e=>e.name))}get recordedAttributeNames(){return Array.from(this.stringMap.keys())}}class x{constructor(e,t,n){this.attributeObserver=new w(e,t,this),this.delegate=n,this.tokensByElement=new k}get started(){return this.attributeObserver.started}start(){this.attributeObserver.start()}pause(e){this.attributeObserver.pause(e)}stop(){this.attributeObserver.stop()}refresh(){this.attributeObserver.refresh()}get element(){return this.attributeObserver.element}get attributeName(){return this.attributeObserver.attributeName}elementMatchedAttribute(e){this.tokensMatched(this.readTokensForElement(e))}elementAttributeValueChanged(e){const[t,n]=this.refreshTokensForElement(e);this.tokensUnmatched(t),this.tokensMatched(n)}elementUnmatchedAttribute(e){this.tokensUnmatched(this.tokensByElement.getValuesForKey(e))}tokensMatched(e){e.forEach((e=>this.tokenMatched(e)))}tokensUnmatched(e){e.forEach((e=>this.tokenUnmatched(e)))}tokenMatched(e){this.delegate.tokenMatched(e),this.tokensByElement.add(e.element,e)}tokenUnmatched(e){this.delegate.tokenUnmatched(e),this.tokensByElement.delete(e.element,e)}refreshTokensForElement(e){const t=this.tokensByElement.getValuesForKey(e),n=this.readTokensForElement(e),r=function(e,t){const n=Math.max(e.length,t.length);return Array.from({length:n},((n,r)=>[e[r],t[r]]))}(t,n).findIndex((([e,t])=>{return r=t,!((n=e)&&r&&n.index==r.index&&n.content==r.content);var n,r}));return-1==r?[[],[]]:[t.slice(r),n.slice(r)]}readTokensForElement(e){const t=this.attributeName;return function(e,t,n){return e.trim().split(/\s+/).filter((e=>e.length)).map(((e,r)=>({element:t,attributeName:n,content:e,index:r})))}(e.getAttribute(t)||"",e,t)}}class S{constructor(e,t,n){this.tokenListObserver=new x(e,t,this),this.delegate=n,this.parseResultsByToken=new WeakMap,this.valuesByTokenByElement=new WeakMap}get started(){return this.tokenListObserver.started}start(){this.tokenListObserver.start()}stop(){this.tokenListObserver.stop()}refresh(){this.tokenListObserver.refresh()}get element(){return this.tokenListObserver.element}get attributeName(){return this.tokenListObserver.attributeName}tokenMatched(e){const{element:t}=e,{value:n}=this.fetchParseResultForToken(e);n&&(this.fetchValuesByTokenForElement(t).set(e,n),this.delegate.elementMatchedValue(t,n))}tokenUnmatched(e){const{element:t}=e,{value:n}=this.fetchParseResultForToken(e);n&&(this.fetchValuesByTokenForElement(t).delete(e),this.delegate.elementUnmatchedValue(t,n))}fetchParseResultForToken(e){let t=this.parseResultsByToken.get(e);return t||(t=this.parseToken(e),this.parseResultsByToken.set(e,t)),t}fetchValuesByTokenForElement(e){let t=this.valuesByTokenByElement.get(e);return t||(t=new Map,this.valuesByTokenByElement.set(e,t)),t}parseToken(e){try{return{value:this.delegate.parseValueForToken(e)}}catch(e){return{error:e}}}}class j{constructor(e,t){this.context=e,this.delegate=t,this.bindingsByAction=new Map}start(){this.valueListObserver||(this.valueListObserver=new S(this.element,this.actionAttribute,this),this.valueListObserver.start())}stop(){this.valueListObserver&&(this.valueListObserver.stop(),delete this.valueListObserver,this.disconnectAllActions())}get element(){return this.context.element}get identifier(){return this.context.identifier}get actionAttribute(){return this.schema.actionAttribute}get schema(){return this.context.schema}get bindings(){return Array.from(this.bindingsByAction.values())}connectAction(e){const t=new y(this.context,e);this.bindingsByAction.set(e,t),this.delegate.bindingConnected(t)}disconnectAction(e){const t=this.bindingsByAction.get(e);t&&(this.bindingsByAction.delete(e),this.delegate.bindingDisconnected(t))}disconnectAllActions(){this.bindings.forEach((e=>this.delegate.bindingDisconnected(e,!0))),this.bindingsByAction.clear()}parseValueForToken(e){const t=p.forToken(e,this.schema);if(t.identifier==this.identifier)return t}elementMatchedValue(e,t){this.connectAction(t)}elementUnmatchedValue(e,t){this.disconnectAction(t)}}class T{constructor(e,t){this.context=e,this.receiver=t,this.stringMapObserver=new C(this.element,this),this.valueDescriptorMap=this.controller.valueDescriptorMap}start(){this.stringMapObserver.start(),this.invokeChangedCallbacksForDefaultValues()}stop(){this.stringMapObserver.stop()}get element(){return this.context.element}get controller(){return this.context.controller}getStringMapKeyForAttribute(e){if(e in this.valueDescriptorMap)return this.valueDescriptorMap[e].name}stringMapKeyAdded(e,t){const n=this.valueDescriptorMap[t];this.hasValue(e)||this.invokeChangedCallback(e,n.writer(this.receiver[e]),n.writer(n.defaultValue))}stringMapValueChanged(e,t,n){const r=this.valueDescriptorNameMap[t];null!==e&&(null===n&&(n=r.writer(r.defaultValue)),this.invokeChangedCallback(t,e,n))}stringMapKeyRemoved(e,t,n){const r=this.valueDescriptorNameMap[e];this.hasValue(e)?this.invokeChangedCallback(e,r.writer(this.receiver[e]),n):this.invokeChangedCallback(e,r.writer(r.defaultValue),n)}invokeChangedCallbacksForDefaultValues(){for(const{key:e,name:t,defaultValue:n,writer:r}of this.valueDescriptors)null==n||this.controller.data.has(e)||this.invokeChangedCallback(t,r(n),void 0)}invokeChangedCallback(e,t,n){const r=`${e}Changed`,i=this.receiver[r];if("function"==typeof i){const r=this.valueDescriptorNameMap[e];try{const e=r.reader(t);let o=n;n&&(o=r.reader(n)),i.call(this.receiver,e,o)}catch(e){throw e instanceof TypeError&&(e.message=`Stimulus Value "${this.context.identifier}.${r.name}" - ${e.message}`),e}}}get valueDescriptors(){const{valueDescriptorMap:e}=this;return Object.keys(e).map((t=>e[t]))}get valueDescriptorNameMap(){const e={};return Object.keys(this.valueDescriptorMap).forEach((t=>{const n=this.valueDescriptorMap[t];e[n.name]=n})),e}hasValue(e){const t=`has${c(this.valueDescriptorNameMap[e].name)}`;return this.receiver[t]}}class E{constructor(e,t){this.context=e,this.delegate=t,this.targetsByName=new k}start(){this.tokenListObserver||(this.tokenListObserver=new x(this.element,this.attributeName,this),this.tokenListObserver.start())}stop(){this.tokenListObserver&&(this.disconnectAllTargets(),this.tokenListObserver.stop(),delete this.tokenListObserver)}tokenMatched({element:e,content:t}){this.scope.containsElement(e)&&this.connectTarget(e,t)}tokenUnmatched({element:e,content:t}){this.disconnectTarget(e,t)}connectTarget(e,t){var n;this.targetsByName.has(t,e)||(this.targetsByName.add(t,e),null===(n=this.tokenListObserver)||void 0===n||n.pause((()=>this.delegate.targetConnected(e,t))))}disconnectTarget(e,t){var n;this.targetsByName.has(t,e)&&(this.targetsByName.delete(t,e),null===(n=this.tokenListObserver)||void 0===n||n.pause((()=>this.delegate.targetDisconnected(e,t))))}disconnectAllTargets(){for(const e of this.targetsByName.keys)for(const t of this.targetsByName.getValuesForKey(e))this.disconnectTarget(t,e)}get attributeName(){return`data-${this.context.identifier}-target`}get element(){return this.context.element}get scope(){return this.context.scope}}function P(e,t){const n=M(e);return Array.from(n.reduce(((e,n)=>(function(e,t){const n=e[t];return Array.isArray(n)?n:[]}(n,t).forEach((t=>e.add(t))),e)),new Set))}function M(e){const t=[];for(;e;)t.push(e),e=Object.getPrototypeOf(e);return t.reverse()}class A{constructor(e,t){this.started=!1,this.context=e,this.delegate=t,this.outletsByName=new k,this.outletElementsByName=new k,this.selectorObserverMap=new Map,this.attributeObserverMap=new Map}start(){this.started||(this.outletDefinitions.forEach((e=>{this.setupSelectorObserverForOutlet(e),this.setupAttributeObserverForOutlet(e)})),this.started=!0,this.dependentContexts.forEach((e=>e.refresh())))}refresh(){this.selectorObserverMap.forEach((e=>e.refresh())),this.attributeObserverMap.forEach((e=>e.refresh()))}stop(){this.started&&(this.started=!1,this.disconnectAllOutlets(),this.stopSelectorObservers(),this.stopAttributeObservers())}stopSelectorObservers(){this.selectorObserverMap.size>0&&(this.selectorObserverMap.forEach((e=>e.stop())),this.selectorObserverMap.clear())}stopAttributeObservers(){this.attributeObserverMap.size>0&&(this.attributeObserverMap.forEach((e=>e.stop())),this.attributeObserverMap.clear())}selectorMatched(e,t,{outletName:n}){const r=this.getOutlet(e,n);r&&this.connectOutlet(r,e,n)}selectorUnmatched(e,t,{outletName:n}){const r=this.getOutletFromMap(e,n);r&&this.disconnectOutlet(r,e,n)}selectorMatchElement(e,{outletName:t}){const n=this.selector(t),r=this.hasOutlet(e,t),i=e.matches(`[${this.schema.controllerAttribute}~=${t}]`);return!!n&&r&&i&&e.matches(n)}elementMatchedAttribute(e,t){const n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}elementAttributeValueChanged(e,t){const n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}elementUnmatchedAttribute(e,t){const n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}connectOutlet(e,t,n){var r;this.outletElementsByName.has(n,t)||(this.outletsByName.add(n,e),this.outletElementsByName.add(n,t),null===(r=this.selectorObserverMap.get(n))||void 0===r||r.pause((()=>this.delegate.outletConnected(e,t,n))))}disconnectOutlet(e,t,n){var r;this.outletElementsByName.has(n,t)&&(this.outletsByName.delete(n,e),this.outletElementsByName.delete(n,t),null===(r=this.selectorObserverMap.get(n))||void 0===r||r.pause((()=>this.delegate.outletDisconnected(e,t,n))))}disconnectAllOutlets(){for(const e of this.outletElementsByName.keys)for(const t of this.outletElementsByName.getValuesForKey(e))for(const n of this.outletsByName.getValuesForKey(e))this.disconnectOutlet(n,t,e)}updateSelectorObserverForOutlet(e){const t=this.selectorObserverMap.get(e);t&&(t.selector=this.selector(e))}setupSelectorObserverForOutlet(e){const t=this.selector(e),n=new O(document.body,t,this,{outletName:e});this.selectorObserverMap.set(e,n),n.start()}setupAttributeObserverForOutlet(e){const t=this.attributeNameForOutletName(e),n=new w(this.scope.element,t,this);this.attributeObserverMap.set(e,n),n.start()}selector(e){return this.scope.outlets.getSelectorForOutletName(e)}attributeNameForOutletName(e){return this.scope.schema.outletAttributeForScope(this.identifier,e)}getOutletNameFromOutletAttributeName(e){return this.outletDefinitions.find((t=>this.attributeNameForOutletName(t)===e))}get outletDependencies(){const e=new k;return this.router.modules.forEach((t=>{P(t.definition.controllerConstructor,"outlets").forEach((n=>e.add(n,t.identifier)))})),e}get outletDefinitions(){return this.outletDependencies.getKeysForValue(this.identifier)}get dependentControllerIdentifiers(){return this.outletDependencies.getValuesForKey(this.identifier)}get dependentContexts(){const e=this.dependentControllerIdentifiers;return this.router.contexts.filter((t=>e.includes(t.identifier)))}hasOutlet(e,t){return!!this.getOutlet(e,t)||!!this.getOutletFromMap(e,t)}getOutlet(e,t){return this.application.getControllerForElementAndIdentifier(e,t)}getOutletFromMap(e,t){return this.outletsByName.getValuesForKey(t).find((t=>t.element===e))}get scope(){return this.context.scope}get schema(){return this.context.schema}get identifier(){return this.context.identifier}get application(){return this.context.application}get router(){return this.application.router}}class L{constructor(e,t){this.logDebugActivity=(e,t={})=>{const{identifier:n,controller:r,element:i}=this;t=Object.assign({identifier:n,controller:r,element:i},t),this.application.logDebugActivity(this.identifier,e,t)},this.module=e,this.scope=t,this.controller=new e.controllerConstructor(this),this.bindingObserver=new j(this,this.dispatcher),this.valueObserver=new T(this,this.controller),this.targetObserver=new E(this,this),this.outletObserver=new A(this,this);try{this.controller.initialize(),this.logDebugActivity("initialize")}catch(e){this.handleError(e,"initializing controller")}}connect(){this.bindingObserver.start(),this.valueObserver.start(),this.targetObserver.start(),this.outletObserver.start();try{this.controller.connect(),this.logDebugActivity("connect")}catch(e){this.handleError(e,"connecting controller")}}refresh(){this.outletObserver.refresh()}disconnect(){try{this.controller.disconnect(),this.logDebugActivity("disconnect")}catch(e){this.handleError(e,"disconnecting controller")}this.outletObserver.stop(),this.targetObserver.stop(),this.valueObserver.stop(),this.bindingObserver.stop()}get application(){return this.module.application}get identifier(){return this.module.identifier}get schema(){return this.application.schema}get dispatcher(){return this.application.dispatcher}get element(){return this.scope.element}get parentElement(){return this.element.parentElement}handleError(e,t,n={}){const{identifier:r,controller:i,element:o}=this;n=Object.assign({identifier:r,controller:i,element:o},n),this.application.handleError(e,`Error ${t}`,n)}targetConnected(e,t){this.invokeControllerMethod(`${t}TargetConnected`,e)}targetDisconnected(e,t){this.invokeControllerMethod(`${t}TargetDisconnected`,e)}outletConnected(e,t,n){this.invokeControllerMethod(`${l(n)}OutletConnected`,e,t)}outletDisconnected(e,t,n){this.invokeControllerMethod(`${l(n)}OutletDisconnected`,e,t)}invokeControllerMethod(e,...t){const n=this.controller;"function"==typeof n[e]&&n[e](...t)}}const I="function"==typeof Object.getOwnPropertySymbols?e=>[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)]:Object.getOwnPropertyNames,R=(()=>{function e(e){function t(){return Reflect.construct(e,arguments,new.target)}return t.prototype=Object.create(e.prototype,{constructor:{value:t}}),Reflect.setPrototypeOf(t,e),t}try{return function(){const t=e((function(){this.a.call(this)}));t.prototype.a=function(){},new t}(),e}catch(e){return e=>class extends e{}}})();class B{constructor(e,t){this.application=e,this.definition=function(e){return{identifier:e.identifier,controllerConstructor:(t=e.controllerConstructor,function(e,t){const n=R(e),r=function(e,t){return I(t).reduce(((n,r)=>{const i=function(e,t,n){const r=Object.getOwnPropertyDescriptor(e,n);if(!r||!("value"in r)){const e=Object.getOwnPropertyDescriptor(t,n).value;return r&&(e.get=r.get||e.get,e.set=r.set||e.set),e}}(e,t,r);return i&&Object.assign(n,{[r]:i}),n}),{})}(e.prototype,t);return Object.defineProperties(n.prototype,r),n}(t,function(e){return P(e,"blessings").reduce(((t,n)=>{const r=n(e);for(const e in r){const n=t[e]||{};t[e]=Object.assign(n,r[e])}return t}),{})}(t)))};var t}(t),this.contextsByScope=new WeakMap,this.connectedContexts=new Set}get identifier(){return this.definition.identifier}get controllerConstructor(){return this.definition.controllerConstructor}get contexts(){return Array.from(this.connectedContexts)}connectContextForScope(e){const t=this.fetchContextForScope(e);this.connectedContexts.add(t),t.connect()}disconnectContextForScope(e){const t=this.contextsByScope.get(e);t&&(this.connectedContexts.delete(t),t.disconnect())}fetchContextForScope(e){let t=this.contextsByScope.get(e);return t||(t=new L(this,e),this.contextsByScope.set(e,t)),t}}class F{constructor(e){this.scope=e}has(e){return this.data.has(this.getDataKey(e))}get(e){return this.getAll(e)[0]}getAll(e){return(this.data.get(this.getDataKey(e))||"").match(/[^\s]+/g)||[]}getAttributeName(e){return this.data.getAttributeNameForKey(this.getDataKey(e))}getDataKey(e){return`${e}-class`}get data(){return this.scope.data}}class D{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get(e){const t=this.getAttributeNameForKey(e);return this.element.getAttribute(t)}set(e,t){const n=this.getAttributeNameForKey(e);return this.element.setAttribute(n,t),this.get(e)}has(e){const t=this.getAttributeNameForKey(e);return this.element.hasAttribute(t)}delete(e){if(this.has(e)){const t=this.getAttributeNameForKey(e);return this.element.removeAttribute(t),!0}return!1}getAttributeNameForKey(e){return`data-${this.identifier}-${u(e)}`}}class N{constructor(e){this.warnedKeysByObject=new WeakMap,this.logger=e}warn(e,t,n){let r=this.warnedKeysByObject.get(e);r||(r=new Set,this.warnedKeysByObject.set(e,r)),r.has(t)||(r.add(t),this.logger.warn(n,e))}}function z(e,t){return`[${e}~="${t}"]`}class V{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findTarget(t)||this.findLegacyTarget(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllTargets(t),...this.findAllLegacyTargets(t)]),[])}findTarget(e){const t=this.getSelectorForTargetName(e);return this.scope.findElement(t)}findAllTargets(e){const t=this.getSelectorForTargetName(e);return this.scope.findAllElements(t)}getSelectorForTargetName(e){return z(this.schema.targetAttributeForScope(this.identifier),e)}findLegacyTarget(e){const t=this.getLegacySelectorForTargetName(e);return this.deprecate(this.scope.findElement(t),e)}findAllLegacyTargets(e){const t=this.getLegacySelectorForTargetName(e);return this.scope.findAllElements(t).map((t=>this.deprecate(t,e)))}getLegacySelectorForTargetName(e){const t=`${this.identifier}.${e}`;return z(this.schema.targetAttribute,t)}deprecate(e,t){if(e){const{identifier:n}=this,r=this.schema.targetAttribute,i=this.schema.targetAttributeForScope(n);this.guide.warn(e,`target:${t}`,`Please replace ${r}="${n}.${t}" with ${i}="${t}". The ${r} attribute is deprecated and will be removed in a future version of Stimulus.`)}return e}get guide(){return this.scope.guide}}class ${constructor(e,t){this.scope=e,this.controllerElement=t}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findOutlet(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllOutlets(t)]),[])}getSelectorForOutletName(e){const t=this.schema.outletAttributeForScope(this.identifier,e);return this.controllerElement.getAttribute(t)}findOutlet(e){const t=this.getSelectorForOutletName(e);if(t)return this.findElement(t,e)}findAllOutlets(e){const t=this.getSelectorForOutletName(e);return t?this.findAllElements(t,e):[]}findElement(e,t){return this.scope.queryElements(e).filter((n=>this.matchesElement(n,e,t)))[0]}findAllElements(e,t){return this.scope.queryElements(e).filter((n=>this.matchesElement(n,e,t)))}matchesElement(e,t,n){const r=e.getAttribute(this.scope.schema.controllerAttribute)||"";return e.matches(t)&&r.split(" ").includes(n)}}class H{constructor(e,t,n,r){this.targets=new V(this),this.classes=new F(this),this.data=new D(this),this.containsElement=e=>e.closest(this.controllerSelector)===this.element,this.schema=e,this.element=t,this.identifier=n,this.guide=new N(r),this.outlets=new $(this.documentScope,t)}findElement(e){return this.element.matches(e)?this.element:this.queryElements(e).find(this.containsElement)}findAllElements(e){return[...this.element.matches(e)?[this.element]:[],...this.queryElements(e).filter(this.containsElement)]}queryElements(e){return Array.from(this.element.querySelectorAll(e))}get controllerSelector(){return z(this.schema.controllerAttribute,this.identifier)}get isDocumentScope(){return this.element===document.documentElement}get documentScope(){return this.isDocumentScope?this:new H(this.schema,document.documentElement,this.identifier,this.guide.logger)}}class q{constructor(e,t,n){this.element=e,this.schema=t,this.delegate=n,this.valueListObserver=new S(this.element,this.controllerAttribute,this),this.scopesByIdentifierByElement=new WeakMap,this.scopeReferenceCounts=new WeakMap}start(){this.valueListObserver.start()}stop(){this.valueListObserver.stop()}get controllerAttribute(){return this.schema.controllerAttribute}parseValueForToken(e){const{element:t,content:n}=e;return this.parseValueForElementAndIdentifier(t,n)}parseValueForElementAndIdentifier(e,t){const n=this.fetchScopesByIdentifierForElement(e);let r=n.get(t);return r||(r=this.delegate.createScopeForElementAndIdentifier(e,t),n.set(t,r)),r}elementMatchedValue(e,t){const n=(this.scopeReferenceCounts.get(t)||0)+1;this.scopeReferenceCounts.set(t,n),1==n&&this.delegate.scopeConnected(t)}elementUnmatchedValue(e,t){const n=this.scopeReferenceCounts.get(t);n&&(this.scopeReferenceCounts.set(t,n-1),1==n&&this.delegate.scopeDisconnected(t))}fetchScopesByIdentifierForElement(e){let t=this.scopesByIdentifierByElement.get(e);return t||(t=new Map,this.scopesByIdentifierByElement.set(e,t)),t}}class U{constructor(e){this.application=e,this.scopeObserver=new q(this.element,this.schema,this),this.scopesByIdentifier=new k,this.modulesByIdentifier=new Map}get element(){return this.application.element}get schema(){return this.application.schema}get logger(){return this.application.logger}get controllerAttribute(){return this.schema.controllerAttribute}get modules(){return Array.from(this.modulesByIdentifier.values())}get contexts(){return this.modules.reduce(((e,t)=>e.concat(t.contexts)),[])}start(){this.scopeObserver.start()}stop(){this.scopeObserver.stop()}loadDefinition(e){this.unloadIdentifier(e.identifier);const t=new B(this.application,e);this.connectModule(t);const n=e.controllerConstructor.afterLoad;n&&n.call(e.controllerConstructor,e.identifier,this.application)}unloadIdentifier(e){const t=this.modulesByIdentifier.get(e);t&&this.disconnectModule(t)}getContextForElementAndIdentifier(e,t){const n=this.modulesByIdentifier.get(t);if(n)return n.contexts.find((t=>t.element==e))}proposeToConnectScopeForElementAndIdentifier(e,t){const n=this.scopeObserver.parseValueForElementAndIdentifier(e,t);n?this.scopeObserver.elementMatchedValue(n.element,n):console.error(`Couldn't find or create scope for identifier: "${t}" and element:`,e)}handleError(e,t,n){this.application.handleError(e,t,n)}createScopeForElementAndIdentifier(e,t){return new H(this.schema,e,t,this.logger)}scopeConnected(e){this.scopesByIdentifier.add(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.connectContextForScope(e)}scopeDisconnected(e){this.scopesByIdentifier.delete(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.disconnectContextForScope(e)}connectModule(e){this.modulesByIdentifier.set(e.identifier,e),this.scopesByIdentifier.getValuesForKey(e.identifier).forEach((t=>e.connectContextForScope(t)))}disconnectModule(e){this.modulesByIdentifier.delete(e.identifier),this.scopesByIdentifier.getValuesForKey(e.identifier).forEach((t=>e.disconnectContextForScope(t)))}}const K={controllerAttribute:"data-controller",actionAttribute:"data-action",targetAttribute:"data-target",targetAttributeForScope:e=>`data-${e}-target`,outletAttributeForScope:(e,t)=>`data-${e}-${t}-outlet`,keyMappings:Object.assign(Object.assign({enter:"Enter",tab:"Tab",esc:"Escape",space:" ",up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",page_up:"PageUp",page_down:"PageDown"},W("abcdefghijklmnopqrstuvwxyz".split("").map((e=>[e,e])))),W("0123456789".split("").map((e=>[e,e]))))};function W(e){return e.reduce(((e,[t,n])=>Object.assign(Object.assign({},e),{[t]:n})),{})}class J{constructor(e=document.documentElement,t=K){this.logger=console,this.debug=!1,this.logDebugActivity=(e,t,n={})=>{this.debug&&this.logFormattedMessage(e,t,n)},this.element=e,this.schema=t,this.dispatcher=new i(this),this.router=new U(this),this.actionDescriptorFilters=Object.assign({},o)}static start(e,t){const n=new this(e,t);return n.start(),n}async start(){await new Promise((e=>{"loading"==document.readyState?document.addEventListener("DOMContentLoaded",(()=>e())):e()})),this.logDebugActivity("application","starting"),this.dispatcher.start(),this.router.start(),this.logDebugActivity("application","start")}stop(){this.logDebugActivity("application","stopping"),this.dispatcher.stop(),this.router.stop(),this.logDebugActivity("application","stop")}register(e,t){this.load({identifier:e,controllerConstructor:t})}registerActionOption(e,t){this.actionDescriptorFilters[e]=t}load(e,...t){(Array.isArray(e)?e:[e,...t]).forEach((e=>{e.controllerConstructor.shouldLoad&&this.router.loadDefinition(e)}))}unload(e,...t){(Array.isArray(e)?e:[e,...t]).forEach((e=>this.router.unloadIdentifier(e)))}get controllers(){return this.router.contexts.map((e=>e.controller))}getControllerForElementAndIdentifier(e,t){const n=this.router.getContextForElementAndIdentifier(e,t);return n?n.controller:null}handleError(e,t,n){var r;this.logger.error("%s\n\n%o\n\n%o",t,e,n),null===(r=window.onerror)||void 0===r||r.call(window,t,"",0,0,e)}logFormattedMessage(e,t,n={}){n=Object.assign({application:this},n),this.logger.groupCollapsed(`${e} #${t}`),this.logger.log("details:",Object.assign({},n)),this.logger.groupEnd()}}function Z(e,t,n){return e.application.getControllerForElementAndIdentifier(t,n)}function G(e,t,n){let r=Z(e,t,n);return r||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,n),r=Z(e,t,n),r||void 0)}function X([e,t],n){return function(e){const{token:t,typeDefinition:n}=e,r=`${u(t)}-value`,i=function(e){const{controller:t,token:n,typeDefinition:r}=e,i=function(e){const{controller:t,token:n,typeObject:r}=e,i=h(r.type),o=h(r.default),s=i&&o,a=i&&!o,l=!i&&o,c=Y(r.type),u=Q(e.typeObject.default);if(a)return c;if(l)return u;if(c!==u)throw new Error(`The specified default value for the Stimulus Value "${t?`${t}.${n}`:n}" must match the defined type "${c}". The provided default value of "${r.default}" is of type "${u}".`);return s?c:void 0}({controller:t,token:n,typeObject:r}),o=Q(r),s=Y(r),a=i||o||s;if(a)return a;throw new Error(`Unknown value type "${t?`${t}.${r}`:n}" for "${n}" value`)}(e);return{type:i,key:r,name:a(r),get defaultValue(){return function(e){const t=Y(e);if(t)return ee[t];const n=d(e,"default"),r=d(e,"type"),i=e;if(n)return i.default;if(r){const{type:e}=i,t=Y(e);if(t)return ee[t]}return e}(n)},get hasCustomDefaultValue(){return void 0!==Q(n)},reader:te[i],writer:ne[i]||ne.default}}({controller:n,token:e,typeDefinition:t})}function Y(e){switch(e){case Array:return"array";case Boolean:return"boolean";case Number:return"number";case Object:return"object";case String:return"string"}}function Q(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string"}return Array.isArray(e)?"array":"[object Object]"===Object.prototype.toString.call(e)?"object":void 0}const ee={get array(){return[]},boolean:!1,number:0,get object(){return{}},string:""},te={array(e){const t=JSON.parse(e);if(!Array.isArray(t))throw new TypeError(`expected value of type "array" but instead got value "${e}" of type "${Q(t)}"`);return t},boolean:e=>!("0"==e||"false"==String(e).toLowerCase()),number:e=>Number(e.replace(/_/g,"")),object(e){const t=JSON.parse(e);if(null===t||"object"!=typeof t||Array.isArray(t))throw new TypeError(`expected value of type "object" but instead got value "${e}" of type "${Q(t)}"`);return t},string:e=>e},ne={default:function(e){return`${e}`},array:re,object:re};function re(e){return JSON.stringify(e)}class ie{constructor(e){this.context=e}static get shouldLoad(){return!0}static afterLoad(e,t){}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get outlets(){return this.scope.outlets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(e,{target:t=this.element,detail:n={},prefix:r=this.identifier,bubbles:i=!0,cancelable:o=!0}={}){const s=new CustomEvent(r?`${r}:${e}`:e,{detail:n,bubbles:i,cancelable:o});return t.dispatchEvent(s),s}}ie.blessings=[function(e){return P(e,"classes").reduce(((e,t)=>{return Object.assign(e,{[`${n=t}Class`]:{get(){const{classes:e}=this;if(e.has(n))return e.get(n);{const t=e.getAttributeName(n);throw new Error(`Missing attribute "${t}"`)}}},[`${n}Classes`]:{get(){return this.classes.getAll(n)}},[`has${c(n)}Class`]:{get(){return this.classes.has(n)}}});var n}),{})},function(e){return P(e,"targets").reduce(((e,t)=>{return Object.assign(e,{[`${n=t}Target`]:{get(){const e=this.targets.find(n);if(e)return e;throw new Error(`Missing target element "${n}" for "${this.identifier}" controller`)}},[`${n}Targets`]:{get(){return this.targets.findAll(n)}},[`has${c(n)}Target`]:{get(){return this.targets.has(n)}}});var n}),{})},function(e){const t=function(e,t){return M(e).reduce(((e,n)=>(e.push(...function(e,t){const n=e[t];return n?Object.keys(n).map((e=>[e,n[e]])):[]}(n,t)),e)),[])}(e,"values"),n={valueDescriptorMap:{get(){return t.reduce(((e,t)=>{const n=X(t,this.identifier),r=this.data.getAttributeNameForKey(n.key);return Object.assign(e,{[r]:n})}),{})}}};return t.reduce(((e,t)=>Object.assign(e,function(e,t){const n=X(e,void 0),{key:r,name:i,reader:o,writer:s}=n;return{[i]:{get(){const e=this.data.get(r);return null!==e?o(e):n.defaultValue},set(e){void 0===e?this.data.delete(r):this.data.set(r,s(e))}},[`has${c(i)}`]:{get(){return this.data.has(r)||n.hasCustomDefaultValue}}}}(t))),n)},function(e){return P(e,"outlets").reduce(((e,t)=>Object.assign(e,function(e){const t=l(e);return{[`${t}Outlet`]:{get(){const t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t){const n=G(this,t,e);if(n)return n;throw new Error(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`)}throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${n}".`)}},[`${t}Outlets`]:{get(){const t=this.outlets.findAll(e);return t.length>0?t.map((t=>{const n=G(this,t,e);if(n)return n;console.warn(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`,t)})).filter((e=>e)):[]}},[`${t}OutletElement`]:{get(){const t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t)return t;throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${n}".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${c(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}(t))),{})}],ie.targets=[],ie.outlets=[],ie.values={}},184:(e,t,n)=>{n.d(t,{default:()=>as});var r=n(379),i=n.n(r),o=n(795),s=n.n(o),a=n(569),l=n.n(a),c=n(565),u=n.n(c),h=n(216),d=n.n(h),f=n(589),p=n.n(f),m=n(989),g={};g.styleTagTransform=p(),g.setAttributes=u(),g.insert=l().bind(null,"head"),g.domAPI=s(),g.insertStyleElement=d(),i()(m.Z,g),m.Z&&m.Z.locals&&m.Z.locals;var v=n(599);function y(e,t){for(var n=0;n{var[t,n]=e;this[t]=n})),this}},{key:"shouldShowSuccessMessage",get:function(){return this.successMessage}}],null&&y(t.prototype,null),n&&y(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function w(e,t){for(var n=0;ne.trim())):this._classes},set:function(e){if(!Array.isArray(e)&&"string"!=typeof e)throw new Error("classes must be an array or a string");this._classes=e}},{key:"triggerClasses",get:function(){return"string"==typeof this._triggerClasses?this._triggerClasses.split(",").map((e=>e.trim())):this._triggerClasses},set:function(e){if(!Array.isArray(e)&&"string"!=typeof e)throw new Error("triggerClasses must be an array or a string");this._triggerClasses=e}},{key:"id",get:function(){return this._id},set:function(e){this._id=e}},{key:"isSet",get:function(){return!!this._id}},{key:"style",get:function(){return this._style},set:function(e){if("object"!=typeof e)throw new Error("Style must be an object");Object.entries(e).forEach((e=>{var[t,n]=e;if(!["primaryColor","secondaryColor","typography"].includes(t))throw new Error("Invalid style property: ".concat(t));if("typography"!==t&&!this.isHexOrRgba(n))throw new Error("Invalid color value: ".concat(n," for ").concat(t,". Colors must be hex or rgb/a."))})),this._style=e}},{key:"behaviour",get:function(){return this._behaviour},set:function(e){if(!Object.values(M).includes(e))throw new Error("Invalid behaviour value: ".concat(e));this._behaviour=e}},{key:"strategy",get:function(){return this._strategy?this._strategy:"body"==this.container?P.FIXED:P.ABSOLUTE},set:function(e){if(e&&!Object.values(P).includes(e))throw new Error("Invalid strategy value: ".concat(e));this._strategy=e}},{key:"assign",value:function(e){return e&&Object.entries(e).forEach((e=>{var[t,n]=e;this[t]=n})),this}},{key:"isHexOrRgba",value:function(e){return/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(e)||/^rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3},?\s*(0|1|0?\.\d+)?\s*\)$/.test(e)}}],null&&T(t.prototype,null),n&&T(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function L(e,t){for(var n=0;n{var[t,n]=e;"forms"===t?this.forms=b.assign(n):"webchat"===t?this.webchat=A.assign(n):this[t]=n})),this}},{key:"locale",get:function(){return j.toString()},set:function(e){j.identifier=e}},{key:"endpoint",value:function(e){return"".concat(this.apiRoot,"/").concat(e)}}],null&&L(t.prototype,null),n&&L(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function R(e){var t="function"==typeof Map?new Map:void 0;return R=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return B(e,arguments,N(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),D(r,e)},R(e)}function B(e,t,n){return B=F()?Reflect.construct.bind():function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&D(i,n.prototype),i},B.apply(null,arguments)}function F(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function D(e,t){return D=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},D(e,t)}function N(e){return N=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},N(e)}I.apiRoot="https://api.hellotext.com/v1",I.actionCableUrl="wss://www.hellotext.com/cable",I.autoGenerateSession=!0,I.session=null,I.forms=b,I.webchat=A;var z=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&D(e,t)}(o,e);var t,n,r,i=(n=o,r=F(),function(){var e,t=N(n);if(r){var i=N(this).constructor;e=Reflect.construct(t,arguments,i)}else e=t.apply(this,arguments);return function(e,t){if(t&&("object"==typeof t||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(this,e)});function o(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(t=i.call(this,"".concat(e," is not valid. Please provide a valid event name"))).name="InvalidEvent",t}return t=o,Object.defineProperty(t,"prototype",{writable:!1}),t}(R(Error));function V(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function $(e){for(var t=1;tt===e))}}],(n=[{key:"addSubscriber",value:function(t,n){if(e.invalid(t))throw new z(t);this.subscribers=$($({},this.subscribers),{},{[t]:this.subscribers[t]?[...this.subscribers[t],n]:[n]})}},{key:"removeSubscriber",value:function(t,n){if(e.invalid(t))throw new z(t);this.subscribers[t]&&(this.subscribers[t]=this.subscribers[t].filter((e=>e!==n)))}},{key:"dispatch",value:function(e,t){var n;null===(n=this.subscribers[e])||void 0===n||n.forEach((e=>{e(t)}))}},{key:"listeners",get:function(){return 0!==Object.keys(this.subscribers).length}}])&&q(t.prototype,n),r&&q(t,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function W(e,t,n,r,i,o,s){try{var a=e[o](s),l=a.value}catch(e){return void n(e)}a.done?t(l):Promise.resolve(l).then(r,i)}function J(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{},t=yield fetch(this.endpoint,{method:"POST",headers:Zt.headers,body:JSON.stringify(ve({session:Zt.session},e))});return new ie(t.ok,t)},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){be(o,n,i,s,a,"next",e)}function a(e){be(o,n,i,s,a,"throw",e)}s(void 0)}))},function(){return i.apply(this,arguments)})}],null&&we(t.prototype,null),n&&we(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Oe(e,t,n,r,i,o,s){try{var a=e[o](s),l=a.value}catch(e){return void n(e)}a.done?t(l):Promise.resolve(l).then(r,i)}function Ce(e,t){for(var n=0;n{var[n,r]=e;t.searchParams.append("style[".concat(n,"]"),r)})),t.searchParams.append("placement",I.webchat.placement);var n=yield fetch(t,{method:"GET",headers:Zt.headers}),r=yield n.json();return Zt.business.data||(Zt.business.setData(r.business),Zt.business.setLocale(r.locale)),(new DOMParser).parseFromString(r.html,"text/html").querySelector("article")},function(){var t=this,n=arguments;return new Promise((function(r,i){var o=e.apply(t,n);function s(e){Oe(o,r,i,s,a,"next",e)}function a(e){Oe(o,r,i,s,a,"throw",e)}s(void 0)}))});return function(e){return t.apply(this,arguments)}}()}],n&&Ce(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();const Se=xe;function je(e,t,n,r,i,o,s){try{var a=e[o](s),l=a.value}catch(e){return void n(e)}a.done?t(l):Promise.resolve(l).then(r,i)}function Te(e,t){for(var n=0;n{var[t,n]=e;return n})));Ve.set("hello_utm",JSON.stringify(r))}}var t,n;return t=e,(n=[{key:"current",get:function(){try{return JSON.parse(Ve.get("hello_utm"))||{}}catch(e){return{}}}}])&&Be(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function De(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:null;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.utm=new Fe,this._url=t}var t,n,r;return t=e,r=[{key:"getRootDomain",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;try{if(!e){var t;if("undefined"==typeof window||null===(t=window.location)||void 0===t||!t.hostname)return null;e=window.location.hostname}var n=e.split(".");if(n.length<=1)return e;for(var r of["myshopify.com","vtexcommercestable.com.br","myvtex.com","wixsite.com"]){var i=r.split(".");if(n.slice(-i.length).join(".")===r&&n.length>i.length)return".".concat(n.slice(-(i.length+1)).join("."))}var o=n[n.length-1],s=n[n.length-2];return n.length>2&&2===o.length&&s.length<=3?".".concat(n.slice(-3).join(".")):".".concat(n.slice(-2).join("."))}catch(e){return null}}}],(n=[{key:"url",get:function(){return null!==this._url&&void 0!==this._url?this._url:window.location.href}},{key:"title",get:function(){return document.title}},{key:"path",get:function(){if(this._url)try{return new URL(this._url).pathname}catch(e){return"/"}return window.location.pathname}},{key:"utmParams",get:function(){return this.utm.current}},{key:"trackingData",get:function(){return{page:{url:this.url,title:this.title,path:this.path},utm_params:this.utmParams}}},{key:"domain",get:function(){try{var t=this.url;if(!t)return null;var n=new URL(t).hostname;return e.getRootDomain(n)}catch(e){return null}}}])&&De(t.prototype,n),r&&De(t,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function ze(e,t){for(var n=0;n\n ".concat(Zt.business.locale.white_label.powered_by,'\n\n \n \n \n \n ')}});var lt=0;function ct(e){return"__private_"+lt+++"_"+e}var ut=ct("findOrCreateComponent"),ht=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),Object.defineProperty(this,ut,{value:dt}),this.data=t,this.element=n||document.querySelector('[data-hello-form="'.concat(this.id,'"]'))||document.createElement("form")}var t,n,r,i;return t=e,n=[{key:"mount",value:(r=function*(){var e,{ifCompleted:t=!0}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(t&&this.hasBeenCompleted)return null===(e=this.element)||void 0===e||e.remove(),Zt.eventEmitter.dispatch("form:completed",function(e){for(var t=1;t{this.element.setAttribute(e.name,e.value)})),document.contains(this.element)||document.body.appendChild(this.element),Zt.business.features.white_label||this.element.prepend(tt.build())},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){it(o,n,i,s,a,"next",e)}function a(e){it(o,n,i,s,a,"throw",e)}s(void 0)}))},function(){return i.apply(this,arguments)})},{key:"buildHeader",value:function(e){var t=at(this,ut)[ut]("[data-form-header]","header");t.innerHTML=e.content,this.element.querySelector("[data-form-header]")?this.element.querySelector("[data-form-header]").replaceWith(t):this.element.prepend(t)}},{key:"buildInputs",value:function(e){var t=at(this,ut)[ut]("[data-form-inputs]","main");e.map((e=>Ge.build(e))).forEach((e=>t.appendChild(e))),this.element.querySelector("[data-form-inputs]")?this.element.querySelector("[data-form-inputs]").replaceWith(t):this.element.querySelector("[data-form-header]").insertAdjacentHTML("afterend",t.outerHTML)}},{key:"buildButton",value:function(e){var t=at(this,ut)[ut]("[data-form-button]","button");t.innerText=e.text,t.setAttribute("data-action","click->hellotext--form#submit"),t.setAttribute("data-hellotext--form-target","button"),this.element.querySelector("[data-form-button]")?this.element.querySelector("[data-form-button]").replaceWith(t):this.element.querySelector("[data-form-inputs]").insertAdjacentHTML("afterend",t.outerHTML)}},{key:"buildFooter",value:function(e){var t=at(this,ut)[ut]("[data-form-footer]","footer");t.innerHTML=e.content,this.element.querySelector("[data-form-footer]")?this.element.querySelector("[data-form-footer]").replaceWith(t):this.element.appendChild(t)}},{key:"markAsCompleted",value:function(e){var t={state:"completed",id:this.id,data:e,completedAt:(new Date).getTime()};localStorage.setItem("hello-form-".concat(this.id),JSON.stringify(t)),Zt.eventEmitter.dispatch("form:completed",t)}},{key:"hasBeenCompleted",get:function(){return null!==localStorage.getItem("hello-form-".concat(this.id))}},{key:"id",get:function(){return this.data.id}},{key:"localeAuthKey",get:function(){var e=this.data.steps[0];return e.inputs.some((e=>"email"===e.kind))&&e.inputs.some((e=>"phone"===e.kind))?"phone_and_email":e.inputs.some((e=>"email"===e.kind))?"email":e.inputs.some((e=>"phone"===e.kind))?"phone":"none"}},{key:"elementAttributes",get:function(){return[{name:"data-controller",value:"hellotext--form"},{name:"data-hello-form",value:this.id},{name:"data-hellotext--form-data-value",value:JSON.stringify(this.data)}]}}],n&&ot(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function dt(e,t){var n=this.element.querySelector(e);if(n)return n.cloneNode(!0);var r=document.createElement(t);return r.setAttribute(e.replace("[","").replace("]",""),""),r}function ft(e){var t="function"==typeof Map?new Map:void 0;return ft=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return pt(e,arguments,vt(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),gt(r,e)},ft(e)}function pt(e,t,n){return pt=mt()?Reflect.construct.bind():function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&>(i,n.prototype),i},pt.apply(null,arguments)}function mt(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function gt(e,t){return gt=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},gt(e,t)}function vt(e){return vt=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},vt(e)}var yt=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&>(e,t)}(o,e);var t,n,r,i=(n=o,r=mt(),function(){var e,t=vt(n);if(r){var i=vt(this).constructor;e=Reflect.construct(t,arguments,i)}else e=t.apply(this,arguments);return function(e,t){if(t&&("object"==typeof t||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(this,e)});function o(){var e;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(e=i.call(this,"You need to initialize before tracking events. Call Hellotext.initialize and pass your public business id")).name="NotInitializedError",e}return t=o,Object.defineProperty(t,"prototype",{writable:!1}),t}(ft(Error));function bt(e,t,n,r,i,o,s){try{var a=e[o](s),l=a.value}catch(e){return void n(e)}a.done?t(l):Promise.resolve(l).then(r,i)}function wt(e,t){for(var n=0;n0&&this.collect()}},{key:"formMutationObserver",value:function(e){e.find((e=>"childList"===e.type&&e.addedNodes.length>0))&&Array.from(document.querySelectorAll("[data-hello-form]")).length>0&&this.collect()}},{key:"collect",value:(r=function*(){if(Zt.notInitialized)throw new yt;if(!this.fetching){if("undefined"==typeof document||!("querySelectorAll"in document))return console.warn("Document is not defined, collection is not possible. Please make sure to initialize the library after the document is loaded.");var e=function(e,t){if(!Object.prototype.hasOwnProperty.call(e,t))throw new TypeError("attempted to use private field on non-instance");return e}(this,Ot)[Ot];if(0!==e.length){var t=e.map((e=>me.get(e).then((e=>e.json()))));this.fetching=!0,yield Promise.all(t).then((e=>e.forEach(this.add))).then((()=>Zt.eventEmitter.dispatch("forms:collected",this))).then((()=>this.fetching=!1)),I.forms.autoMount&&this.forms.forEach((e=>e.mount()))}}},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){bt(o,n,i,s,a,"next",e)}function a(e){bt(o,n,i,s,a,"throw",e)}s(void 0)}))},function(){return i.apply(this,arguments)})},{key:"forEach",value:function(e){this.forms.forEach(e)}},{key:"map",value:function(e){return this.forms.map(e)}},{key:"add",value:function(e){this.includes(e.id)||(Zt.business.data||(Zt.business.setData(e.business),Zt.business.setLocale(j.toString())),Zt.business.enabledWhitelist||console.warn("No whitelist has been configured. It is advised to whitelist the domain to avoid bots from submitting forms."),this.forms.push(new ht(e)))}},{key:"getById",value:function(e){return this.forms.find((t=>t.id===e))}},{key:"getByIndex",value:function(e){return this.forms[e]}},{key:"includes",value:function(e){return this.forms.some((t=>t.id===e))}},{key:"excludes",value:function(e){return!this.includes(e)}},{key:"length",get:function(){return this.forms.length}}],n&&wt(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function xt(){return Array.from(document.querySelectorAll("[data-hello-form]")).map((e=>e.dataset.helloForm)).filter(this.excludes)}function St(e,t,n,r,i,o,s){try{var a=e[o](s),l=a.value}catch(e){return void n(e)}a.done?t(l):Promise.resolve(l).then(r,i)}function jt(e,t){for(var n=0;nFt(e))).filter((e=>void 0!==e));if(e instanceof Date)return e.toISOString();if("object"==typeof e){var n=Object.keys(e).sort(((e,t)=>e.localeCompare(t))).reduce(((t,n)=>{var r=Ft(e[n]);return void 0!==r&&(t[n]=r),t}),{});return Object.keys(n).length>0?n:void 0}return"number"==typeof e||"boolean"==typeof e?e:void 0}}function Dt(e,t){var n=Ft(function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:{}))||{};return JSON.stringify(n)}function Nt(){return(Nt=Lt((function*(e){var t;if(null===(t=globalThis.crypto)||void 0===t||!t.subtle||"undefined"==typeof TextEncoder)return function(e){for(var t=5381,n=0;n>>0).toString(16))}(e);var n=yield globalThis.crypto.subtle.digest("SHA-256",(new TextEncoder).encode(e)),r=Array.from(new Uint8Array(n)).map((e=>e.toString(16).padStart(2,"0"))).join("");return"v1:".concat(r)}))).apply(this,arguments)}var zt=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e)}var t,n,r;return t=e,null,n=[{key:"matches",value:function(e,t){return!!e&&e===t}},{key:"generate",value:(r=Lt((function*(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return yield function(e){return Nt.apply(this,arguments)}(Dt(e,t,n))})),function(e,t){return r.apply(this,arguments)})}],n&&Mt(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Vt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function $t(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};this.business=new Re(e),I.assign(t),Je.initialize(),this.page=new Ne,this.forms=new Ct,this.query=new X;var n=yield this.business.hydrate(),r=!1!==t.webchat&&$t($t({},n&&n.webchat||{}),t.webchat||{});r&&r.id&&(I.webchat.assign(r),this.webchat=yield Tt.load(r.id)),"undefined"!=typeof MutationObserver&&this.forms.collectExistingFormsOnPage()})),function(e){return o.apply(this,arguments)})},{key:"track",value:(i=Ut((function*(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(this.notInitialized)throw new yt;var n=$t($t({},t&&t.headers||{}),this.headers),r=$t($t({},Pt.identificationData),t.user_parameters||{}),i=t&&t.url?new Ne(t.url):this.page,o=$t($t({session:this.session,user_parameters:r,action:e},t),i.trackingData);return delete o.headers,yield Me.events.create({headers:n,body:o})})),function(e){return i.apply(this,arguments)})},{key:"identify",value:(r=Ut((function*(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=yield zt.generate(this.session,e,n);if(zt.matches(Pt.fingerprint,r))return new ie(!0,{json:(t=Ut((function*(){return{already_identified:!0}})),function(){return t.apply(this,arguments)})});var i=yield Me.identifications.create($t({user_id:e},n));return i.succeeded&&Pt.remember(e,n.source,r),i})),function(e){return r.apply(this,arguments)})},{key:"forget",value:function(){Pt.forget()}},{key:"on",value:function(e,t){this.eventEmitter.addSubscriber(e,t)}},{key:"removeEventListener",value:function(e,t){this.eventEmitter.removeSubscriber(e,t)}},{key:"session",get:function(){return Je.session}},{key:"isInitialized",get:function(){return void 0!==Je.session}},{key:"notInitialized",get:function(){return!this.business||void 0===this.business.id}},{key:"headers",get:function(){if(this.notInitialized)throw new yt;return{Authorization:"Bearer ".concat(this.business.id),Accept:"application/json","Content-Type":"application/json"}}}],n&&Kt(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();Jt.eventEmitter=new K,Jt.forms=void 0,Jt.business=void 0,Jt.webchat=void 0;const Zt=Jt;function Gt(e,t,n,r,i,o,s){try{var a=e[o](s),l=a.value}catch(e){return void n(e)}a.done?t(l):Promise.resolve(l).then(r,i)}function Xt(e,t){for(var n=0;n{var{type:t,parameter:n}=e,r=this.inputTargets.find((e=>e.name===n));r.setCustomValidity(Zt.business.locale.errors[t]),r.reportValidity(),r.addEventListener("input",(()=>{r.setCustomValidity(""),r.reportValidity()}))})),this.showErrorMessages();this.buttonTarget.style.display="none",this.element.querySelectorAll("input").forEach((e=>e.disabled=!0)),this.completed()},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){Gt(o,n,i,s,a,"next",e)}function a(e){Gt(o,n,i,s,a,"throw",e)}s(void 0)}))},function(e){return i.apply(this,arguments)})},{key:"completed",value:function(){if(this.form.markAsCompleted(this.formData),!I.forms.shouldShowSuccessMessage)return this.element.remove();"string"==typeof I.forms.successMessage?this.element.innerHTML=I.forms.successMessage:this.element.innerHTML=Zt.business.locale.forms[this.form.localeAuthKey]}},{key:"showErrorMessages",value:function(){this.inputTargets.forEach((e=>{var t=e.closest("article").querySelector("[data-error-container]");e.validity.valid?t.innerText="":t.innerText=e.validationMessage}))}},{key:"clearErrorMessages",value:function(){this.inputTargets.forEach((e=>{e.setCustomValidity(""),e.closest("article").querySelector("[data-error-container]").innerText=""}))}},{key:"inputTargetConnected",value:function(e){e.getAttribute("data-default-value")&&(e.value=e.getAttribute("data-default-value"))}},{key:"requiredInputs",get:function(){return this.inputTargets.filter((e=>e.required))}},{key:"invalid",get:function(){return!this.element.checkValidity()}}],n&&Xt(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),l}(v.Qr);function nn(e,t){for(var n=0;n0?this.leftFadeTarget.classList.remove("hidden"):this.leftFadeTarget.classList.add("hidden"),ee.concat(t,t+"-"+an[0],t+"-"+an[1])),[]),cn=Math.min,un=Math.max,hn=Math.round,dn=Math.floor,fn=e=>({x:e,y:e}),pn={left:"right",right:"left",bottom:"top",top:"bottom"},mn={start:"end",end:"start"};function gn(e,t,n){return un(e,cn(t,n))}function vn(e,t){return"function"==typeof e?e(t):e}function yn(e){return e.split("-")[0]}function bn(e){return e.split("-")[1]}function wn(e){return"x"===e?"y":"x"}function _n(e){return"y"===e?"height":"width"}const kn=new Set(["top","bottom"]);function On(e){return kn.has(yn(e))?"y":"x"}function Cn(e){return wn(On(e))}function xn(e,t,n){void 0===n&&(n=!1);const r=bn(e),i=Cn(e),o=_n(i);let s="x"===i?r===(n?"end":"start")?"right":"left":"start"===r?"bottom":"top";return t.reference[o]>t.floating[o]&&(s=Mn(s)),[s,Mn(s)]}function Sn(e){return e.replace(/start|end/g,(e=>mn[e]))}const jn=["left","right"],Tn=["right","left"],En=["top","bottom"],Pn=["bottom","top"];function Mn(e){return e.replace(/left|right|bottom|top/g,(e=>pn[e]))}function An(e){const{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function Ln(e,t,n){let{reference:r,floating:i}=e;const o=On(t),s=Cn(t),a=_n(s),l=yn(t),c="y"===o,u=r.x+r.width/2-i.width/2,h=r.y+r.height/2-i.height/2,d=r[a]/2-i[a]/2;let f;switch(l){case"top":f={x:u,y:r.y-i.height};break;case"bottom":f={x:u,y:r.y+r.height};break;case"right":f={x:r.x+r.width,y:h};break;case"left":f={x:r.x-i.width,y:h};break;default:f={x:r.x,y:r.y}}switch(bn(t)){case"start":f[s]-=d*(n&&c?-1:1);break;case"end":f[s]+=d*(n&&c?-1:1)}return f}async function In(e,t){var n;void 0===t&&(t={});const{x:r,y:i,platform:o,rects:s,elements:a,strategy:l}=e,{boundary:c="clippingAncestors",rootBoundary:u="viewport",elementContext:h="floating",altBoundary:d=!1,padding:f=0}=vn(t,e),p=function(e){return"number"!=typeof e?function(e){return{top:0,right:0,bottom:0,left:0,...e}}(e):{top:e,right:e,bottom:e,left:e}}(f),m=a[d?"floating"===h?"reference":"floating":h],g=An(await o.getClippingRect({element:null==(n=await(null==o.isElement?void 0:o.isElement(m)))||n?m:m.contextElement||await(null==o.getDocumentElement?void 0:o.getDocumentElement(a.floating)),boundary:c,rootBoundary:u,strategy:l})),v="floating"===h?{x:r,y:i,width:s.floating.width,height:s.floating.height}:s.reference,y=await(null==o.getOffsetParent?void 0:o.getOffsetParent(a.floating)),b=await(null==o.isElement?void 0:o.isElement(y))&&await(null==o.getScale?void 0:o.getScale(y))||{x:1,y:1},w=An(o.convertOffsetParentRelativeRectToViewportRelativeRect?await o.convertOffsetParentRelativeRectToViewportRelativeRect({elements:a,rect:v,offsetParent:y,strategy:l}):v);return{top:(g.top-w.top+p.top)/b.y,bottom:(w.bottom-g.bottom+p.bottom)/b.y,left:(g.left-w.left+p.left)/b.x,right:(w.right-g.right+p.right)/b.x}}const Rn=new Set(["left","top"]);function Bn(){return"undefined"!=typeof window}function Fn(e){return zn(e)?(e.nodeName||"").toLowerCase():"#document"}function Dn(e){var t;return(null==e||null==(t=e.ownerDocument)?void 0:t.defaultView)||window}function Nn(e){var t;return null==(t=(zn(e)?e.ownerDocument:e.document)||window.document)?void 0:t.documentElement}function zn(e){return!!Bn()&&(e instanceof Node||e instanceof Dn(e).Node)}function Vn(e){return!!Bn()&&(e instanceof Element||e instanceof Dn(e).Element)}function $n(e){return!!Bn()&&(e instanceof HTMLElement||e instanceof Dn(e).HTMLElement)}function Hn(e){return!(!Bn()||"undefined"==typeof ShadowRoot)&&(e instanceof ShadowRoot||e instanceof Dn(e).ShadowRoot)}const qn=new Set(["inline","contents"]);function Un(e){const{overflow:t,overflowX:n,overflowY:r,display:i}=rr(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!qn.has(i)}const Kn=new Set(["table","td","th"]);function Wn(e){return Kn.has(Fn(e))}const Jn=[":popover-open",":modal"];function Zn(e){return Jn.some((t=>{try{return e.matches(t)}catch(e){return!1}}))}const Gn=["transform","translate","scale","rotate","perspective"],Xn=["transform","translate","scale","rotate","perspective","filter"],Yn=["paint","layout","strict","content"];function Qn(e){const t=er(),n=Vn(e)?rr(e):e;return Gn.some((e=>!!n[e]&&"none"!==n[e]))||!!n.containerType&&"normal"!==n.containerType||!t&&!!n.backdropFilter&&"none"!==n.backdropFilter||!t&&!!n.filter&&"none"!==n.filter||Xn.some((e=>(n.willChange||"").includes(e)))||Yn.some((e=>(n.contain||"").includes(e)))}function er(){return!("undefined"==typeof CSS||!CSS.supports)&&CSS.supports("-webkit-backdrop-filter","none")}const tr=new Set(["html","body","#document"]);function nr(e){return tr.has(Fn(e))}function rr(e){return Dn(e).getComputedStyle(e)}function ir(e){return Vn(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function or(e){if("html"===Fn(e))return e;const t=e.assignedSlot||e.parentNode||Hn(e)&&e.host||Nn(e);return Hn(t)?t.host:t}function sr(e){const t=or(e);return nr(t)?e.ownerDocument?e.ownerDocument.body:e.body:$n(t)&&Un(t)?t:sr(t)}function ar(e,t,n){var r;void 0===t&&(t=[]),void 0===n&&(n=!0);const i=sr(e),o=i===(null==(r=e.ownerDocument)?void 0:r.body),s=Dn(i);if(o){const e=lr(s);return t.concat(s,s.visualViewport||[],Un(i)?i:[],e&&n?ar(e):[])}return t.concat(i,ar(i,[],n))}function lr(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function cr(e){const t=rr(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const i=$n(e),o=i?e.offsetWidth:n,s=i?e.offsetHeight:r,a=hn(n)!==o||hn(r)!==s;return a&&(n=o,r=s),{width:n,height:r,$:a}}function ur(e){return Vn(e)?e:e.contextElement}function hr(e){const t=ur(e);if(!$n(t))return fn(1);const n=t.getBoundingClientRect(),{width:r,height:i,$:o}=cr(t);let s=(o?hn(n.width):n.width)/r,a=(o?hn(n.height):n.height)/i;return s&&Number.isFinite(s)||(s=1),a&&Number.isFinite(a)||(a=1),{x:s,y:a}}const dr=fn(0);function fr(e){const t=Dn(e);return er()&&t.visualViewport?{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}:dr}function pr(e,t,n,r){void 0===t&&(t=!1),void 0===n&&(n=!1);const i=e.getBoundingClientRect(),o=ur(e);let s=fn(1);t&&(r?Vn(r)&&(s=hr(r)):s=hr(e));const a=function(e,t,n){return void 0===t&&(t=!1),!(!n||t&&n!==Dn(e))&&t}(o,n,r)?fr(o):fn(0);let l=(i.left+a.x)/s.x,c=(i.top+a.y)/s.y,u=i.width/s.x,h=i.height/s.y;if(o){const e=Dn(o),t=r&&Vn(r)?Dn(r):r;let n=e,i=lr(n);for(;i&&r&&t!==n;){const e=hr(i),t=i.getBoundingClientRect(),r=rr(i),o=t.left+(i.clientLeft+parseFloat(r.paddingLeft))*e.x,s=t.top+(i.clientTop+parseFloat(r.paddingTop))*e.y;l*=e.x,c*=e.y,u*=e.x,h*=e.y,l+=o,c+=s,n=Dn(i),i=lr(n)}}return An({width:u,height:h,x:l,y:c})}function mr(e,t){const n=ir(e).scrollLeft;return t?t.left+n:pr(Nn(e)).left+n}function gr(e,t,n){void 0===n&&(n=!1);const r=e.getBoundingClientRect();return{x:r.left+t.scrollLeft-(n?0:mr(e,r)),y:r.top+t.scrollTop}}const vr=new Set(["absolute","fixed"]);function yr(e,t,n){let r;if("viewport"===t)r=function(e,t){const n=Dn(e),r=Nn(e),i=n.visualViewport;let o=r.clientWidth,s=r.clientHeight,a=0,l=0;if(i){o=i.width,s=i.height;const e=er();(!e||e&&"fixed"===t)&&(a=i.offsetLeft,l=i.offsetTop)}return{width:o,height:s,x:a,y:l}}(e,n);else if("document"===t)r=function(e){const t=Nn(e),n=ir(e),r=e.ownerDocument.body,i=un(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),o=un(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight);let s=-n.scrollLeft+mr(e);const a=-n.scrollTop;return"rtl"===rr(r).direction&&(s+=un(t.clientWidth,r.clientWidth)-i),{width:i,height:o,x:s,y:a}}(Nn(e));else if(Vn(t))r=function(e,t){const n=pr(e,!0,"fixed"===t),r=n.top+e.clientTop,i=n.left+e.clientLeft,o=$n(e)?hr(e):fn(1);return{width:e.clientWidth*o.x,height:e.clientHeight*o.y,x:i*o.x,y:r*o.y}}(t,n);else{const n=fr(e);r={x:t.x-n.x,y:t.y-n.y,width:t.width,height:t.height}}return An(r)}function br(e,t){const n=or(e);return!(n===t||!Vn(n)||nr(n))&&("fixed"===rr(n).position||br(n,t))}function wr(e,t,n){const r=$n(t),i=Nn(t),o="fixed"===n,s=pr(e,!0,o,t);let a={scrollLeft:0,scrollTop:0};const l=fn(0);function c(){l.x=mr(i)}if(r||!r&&!o)if(("body"!==Fn(t)||Un(i))&&(a=ir(t)),r){const e=pr(t,!0,o,t);l.x=e.x+t.clientLeft,l.y=e.y+t.clientTop}else i&&c();o&&!r&&i&&c();const u=!i||r||o?fn(0):gr(i,a);return{x:s.left+a.scrollLeft-l.x-u.x,y:s.top+a.scrollTop-l.y-u.y,width:s.width,height:s.height}}function _r(e){return"static"===rr(e).position}function kr(e,t){if(!$n(e)||"fixed"===rr(e).position)return null;if(t)return t(e);let n=e.offsetParent;return Nn(e)===n&&(n=n.ownerDocument.body),n}function Or(e,t){const n=Dn(e);if(Zn(e))return n;if(!$n(e)){let t=or(e);for(;t&&!nr(t);){if(Vn(t)&&!_r(t))return t;t=or(t)}return n}let r=kr(e,t);for(;r&&Wn(r)&&_r(r);)r=kr(r,t);return r&&nr(r)&&_r(r)&&!Qn(r)?n:r||function(e){let t=or(e);for(;$n(t)&&!nr(t);){if(Qn(t))return t;if(Zn(t))return null;t=or(t)}return null}(e)||n}const Cr={convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e;const o="fixed"===i,s=Nn(r),a=!!t&&Zn(t.floating);if(r===s||a&&o)return n;let l={scrollLeft:0,scrollTop:0},c=fn(1);const u=fn(0),h=$n(r);if((h||!h&&!o)&&(("body"!==Fn(r)||Un(s))&&(l=ir(r)),$n(r))){const e=pr(r);c=hr(r),u.x=e.x+r.clientLeft,u.y=e.y+r.clientTop}const d=!s||h||o?fn(0):gr(s,l,!0);return{width:n.width*c.x,height:n.height*c.y,x:n.x*c.x-l.scrollLeft*c.x+u.x+d.x,y:n.y*c.y-l.scrollTop*c.y+u.y+d.y}},getDocumentElement:Nn,getClippingRect:function(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e;const o=[..."clippingAncestors"===n?Zn(t)?[]:function(e,t){const n=t.get(e);if(n)return n;let r=ar(e,[],!1).filter((e=>Vn(e)&&"body"!==Fn(e))),i=null;const o="fixed"===rr(e).position;let s=o?or(e):e;for(;Vn(s)&&!nr(s);){const t=rr(s),n=Qn(s);n||"fixed"!==t.position||(i=null),(o?!n&&!i:!n&&"static"===t.position&&i&&vr.has(i.position)||Un(s)&&!n&&br(e,s))?r=r.filter((e=>e!==s)):i=t,s=or(s)}return t.set(e,r),r}(t,this._c):[].concat(n),r],s=o[0],a=o.reduce(((e,n)=>{const r=yr(t,n,i);return e.top=un(r.top,e.top),e.right=cn(r.right,e.right),e.bottom=cn(r.bottom,e.bottom),e.left=un(r.left,e.left),e}),yr(t,s,i));return{width:a.right-a.left,height:a.bottom-a.top,x:a.left,y:a.top}},getOffsetParent:Or,getElementRects:async function(e){const t=this.getOffsetParent||Or,n=this.getDimensions,r=await n(e.floating);return{reference:wr(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}},getClientRects:function(e){return Array.from(e.getClientRects())},getDimensions:function(e){const{width:t,height:n}=cr(e);return{width:t,height:n}},getScale:hr,isElement:Vn,isRTL:function(e){return"rtl"===rr(e).direction}};function xr(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}const Sr=function(e){return void 0===e&&(e=0),{name:"offset",options:e,async fn(t){var n,r;const{x:i,y:o,placement:s,middlewareData:a}=t,l=await async function(e,t){const{placement:n,platform:r,elements:i}=e,o=await(null==r.isRTL?void 0:r.isRTL(i.floating)),s=yn(n),a=bn(n),l="y"===On(n),c=Rn.has(s)?-1:1,u=o&&l?-1:1,h=vn(t,e);let{mainAxis:d,crossAxis:f,alignmentAxis:p}="number"==typeof h?{mainAxis:h,crossAxis:0,alignmentAxis:null}:{mainAxis:h.mainAxis||0,crossAxis:h.crossAxis||0,alignmentAxis:h.alignmentAxis};return a&&"number"==typeof p&&(f="end"===a?-1*p:p),l?{x:f*u,y:d*c}:{x:d*c,y:f*u}}(t,e);return s===(null==(n=a.offset)?void 0:n.placement)&&null!=(r=a.arrow)&&r.alignmentOffset?{}:{x:i+l.x,y:o+l.y,data:{...l,placement:s}}}}},jr=function(e){return void 0===e&&(e={}),{name:"autoPlacement",options:e,async fn(t){var n,r,i;const{rects:o,middlewareData:s,placement:a,platform:l,elements:c}=t,{crossAxis:u=!1,alignment:h,allowedPlacements:d=ln,autoAlignment:f=!0,...p}=vn(e,t),m=void 0!==h||d===ln?function(e,t,n){return(e?[...n.filter((t=>bn(t)===e)),...n.filter((t=>bn(t)!==e))]:n.filter((e=>yn(e)===e))).filter((n=>!e||bn(n)===e||!!t&&Sn(n)!==n))}(h||null,f,d):d,g=await In(t,p),v=(null==(n=s.autoPlacement)?void 0:n.index)||0,y=m[v];if(null==y)return{};const b=xn(y,o,await(null==l.isRTL?void 0:l.isRTL(c.floating)));if(a!==y)return{reset:{placement:m[0]}};const w=[g[yn(y)],g[b[0]],g[b[1]]],_=[...(null==(r=s.autoPlacement)?void 0:r.overflows)||[],{placement:y,overflows:w}],k=m[v+1];if(k)return{data:{index:v+1,overflows:_},reset:{placement:k}};const O=_.map((e=>{const t=bn(e.placement);return[e.placement,t&&u?e.overflows.slice(0,2).reduce(((e,t)=>e+t),0):e.overflows[0],e.overflows]})).sort(((e,t)=>e[1]-t[1])),C=(null==(i=O.filter((e=>e[2].slice(0,bn(e[0])?2:3).every((e=>e<=0))))[0])?void 0:i[0])||O[0][0];return C!==a?{data:{index:v+1,overflows:_},reset:{placement:C}}:{}}}},Tr=function(e){return void 0===e&&(e={}),{name:"shift",options:e,async fn(t){const{x:n,y:r,placement:i}=t,{mainAxis:o=!0,crossAxis:s=!1,limiter:a={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...l}=vn(e,t),c={x:n,y:r},u=await In(t,l),h=On(yn(i)),d=wn(h);let f=c[d],p=c[h];if(o){const e="y"===d?"bottom":"right";f=gn(f+u["y"===d?"top":"left"],f,f-u[e])}if(s){const e="y"===h?"bottom":"right";p=gn(p+u["y"===h?"top":"left"],p,p-u[e])}const m=a.fn({...t,[d]:f,[h]:p});return{...m,data:{x:m.x-n,y:m.y-r,enabled:{[d]:o,[h]:s}}}}}},Er=function(e){return void 0===e&&(e={}),{name:"flip",options:e,async fn(t){var n,r;const{placement:i,middlewareData:o,rects:s,initialPlacement:a,platform:l,elements:c}=t,{mainAxis:u=!0,crossAxis:h=!0,fallbackPlacements:d,fallbackStrategy:f="bestFit",fallbackAxisSideDirection:p="none",flipAlignment:m=!0,...g}=vn(e,t);if(null!=(n=o.arrow)&&n.alignmentOffset)return{};const v=yn(i),y=On(a),b=yn(a)===a,w=await(null==l.isRTL?void 0:l.isRTL(c.floating)),_=d||(b||!m?[Mn(a)]:function(e){const t=Mn(e);return[Sn(e),t,Sn(t)]}(a)),k="none"!==p;!d&&k&&_.push(...function(e,t,n,r){const i=bn(e);let o=function(e,t,n){switch(e){case"top":case"bottom":return n?t?Tn:jn:t?jn:Tn;case"left":case"right":return t?En:Pn;default:return[]}}(yn(e),"start"===n,r);return i&&(o=o.map((e=>e+"-"+i)),t&&(o=o.concat(o.map(Sn)))),o}(a,m,p,w));const O=[a,..._],C=await In(t,g),x=[];let S=(null==(r=o.flip)?void 0:r.overflows)||[];if(u&&x.push(C[v]),h){const e=xn(i,s,w);x.push(C[e[0]],C[e[1]])}if(S=[...S,{placement:i,overflows:x}],!x.every((e=>e<=0))){var j,T;const e=((null==(j=o.flip)?void 0:j.index)||0)+1,t=O[e];if(t&&("alignment"!==h||y===On(t)||S.every((e=>On(e.placement)!==y||e.overflows[0]>0))))return{data:{index:e,overflows:S},reset:{placement:t}};let n=null==(T=S.filter((e=>e.overflows[0]<=0)).sort(((e,t)=>e.overflows[1]-t.overflows[1]))[0])?void 0:T.placement;if(!n)switch(f){case"bestFit":{var E;const e=null==(E=S.filter((e=>{if(k){const t=On(e.placement);return t===y||"y"===t}return!0})).map((e=>[e.placement,e.overflows.filter((e=>e>0)).reduce(((e,t)=>e+t),0)])).sort(((e,t)=>e[1]-t[1]))[0])?void 0:E[0];e&&(n=e);break}case"initialPlacement":n=a}if(i!==n)return{reset:{placement:n}}}return{}}}},Pr=JSON.parse('{"search":"Search","search_no_results_1":"Oh no!","search_no_results_2":"That emoji couldn’t be found","pick":"Pick an emoji…","add_custom":"Add custom emoji","categories":{"activity":"Activity","custom":"Custom","flags":"Flags","foods":"Food & Drink","frequent":"Frequently used","nature":"Animals & Nature","objects":"Objects","people":"Smileys & People","places":"Travel & Places","search":"Search Results","symbols":"Symbols"},"skins":{"1":"Default","2":"Light","3":"Medium-Light","4":"Medium","5":"Medium-Dark","6":"Dark","choose":"Choose default skin tone"}}'),Mr=JSON.parse('{"search":"Buscar","search_no_results_1":"Vaya!","search_no_results_2":"Ese emoji no se pudo encontrar","pick":"Elige un emoji…","add_custom":"Añadir emoji personalizado","categories":{"activity":"Actividades","custom":"Personalizados","flags":"Banderas","foods":"Comida y Bebida","frequent":"Usados con frecuencia","nature":"Animales y Naturaleza","objects":"Objetos","people":"Emoticonos y Personas","places":"Viajes y Destinos","search":"Resultados de la búsqueda","symbols":"Símbolos"},"skins":{"1":"Sin tono","2":"Claro","3":"Medio-Claro","4":"Medio","5":"Medio-Oscuro","6":"Oscuro","choose":"Elige el tono de piel predeterminado"}}');function Ar(e){return e&&e.__esModule?e.default:e}function Lr(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var Ir,Rr,Br,Fr,Dr,Nr,zr={},Vr=[],$r=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function Hr(e,t){for(var n in t)e[n]=t[n];return e}function qr(e){var t=e.parentNode;t&&t.removeChild(e)}function Ur(e,t,n){var r,i,o,s={};for(o in t)"key"==o?r=t[o]:"ref"==o?i=t[o]:s[o]=t[o];if(arguments.length>2&&(s.children=arguments.length>3?Ir.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(o in e.defaultProps)void 0===s[o]&&(s[o]=e.defaultProps[o]);return Kr(e,s,r,i,null)}function Kr(e,t,n,r,i){var o={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==i?++Br:i};return null==i&&null!=Rr.vnode&&Rr.vnode(o),o}function Wr(e){return e.children}function Jr(e,t){this.props=e,this.context=t}function Zr(e,t){if(null==t)return e.__?Zr(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?Kr(f.type,f.props,f.key,null,f.__v):f)){if(f.__=n,f.__b=n.__b+1,null===(d=v[u])||d&&f.key==d.key&&f.type===d.type)v[u]=void 0;else for(h=0;h{let e=null;try{navigator.userAgent.includes("jsdom")||(e=document.createElement("canvas").getContext("2d",{willReadFrequently:!0}))}catch{}if(!e)return()=>!1;const t=20,n=Math.floor(12.5);return e.font=n+"px Arial, Sans-Serif",e.textBaseline="top",e.canvas.width=40,e.canvas.height=25,n=>{e.clearRect(0,0,40,25),e.fillStyle="#FF0000",e.fillText(n,0,22),e.fillStyle="#0000FF",e.fillText(n,t,22);const r=e.getImageData(0,0,t,25).data,i=r.length;let o=0;for(;o=i)return!1;const s=t+o/4%t,a=Math.floor(o/4/t),l=e.getImageData(s,a,1,1).data;return r[o]===l[0]&&r[o+2]===l[2]&&!(e.measureText(n).width>=t)}})();var _i={latestVersion:function(){for(const{v:e,emoji:t}of yi)if(bi(t))return e},noCountryFlags:function(){return!bi("🇨🇦")}};const ki=["+1","grinning","kissing_heart","heart_eyes","laughing","stuck_out_tongue_winking_eye","sweat_smile","joy","scream","disappointed","unamused","weary","sob","sunglasses","heart"];let Oi=null;var Ci={add:function(e){Oi||(Oi=gi("frequently")||{});const t=e.id||e;t&&(Oi[t]||(Oi[t]=0),Oi[t]+=1,mi("last",t),mi("frequently",Oi))},get:function({maxFrequentRows:e,perLine:t}){if(!e)return[];Oi||(Oi=gi("frequently"));let n=[];if(!Oi){Oi={};for(let e in ki.slice(0,t)){const r=ki[e];Oi[r]=t-e,n.push(r)}return n}const r=e*t,i=gi("last");for(let e in Oi)n.push(e);if(n.sort(((e,t)=>{const n=Oi[t],r=Oi[e];return n==r?e.localeCompare(t):n-r})),n.length>r){const e=n.slice(r);n=n.slice(0,r);for(let t of e)t!=i&&delete Oi[t];i&&-1==n.indexOf(i)&&(delete Oi[n[n.length-1]],n.splice(-1,1,i)),mi("frequently",Oi)}return n},DEFAULTS:ki},xi={};xi=JSON.parse('{"search":"Search","search_no_results_1":"Oh no!","search_no_results_2":"That emoji couldn’t be found","pick":"Pick an emoji…","add_custom":"Add custom emoji","categories":{"activity":"Activity","custom":"Custom","flags":"Flags","foods":"Food & Drink","frequent":"Frequently used","nature":"Animals & Nature","objects":"Objects","people":"Smileys & People","places":"Travel & Places","search":"Search Results","symbols":"Symbols"},"skins":{"1":"Default","2":"Light","3":"Medium-Light","4":"Medium","5":"Medium-Dark","6":"Dark","choose":"Choose default skin tone"}}');var Si={autoFocus:{value:!1},dynamicWidth:{value:!1},emojiButtonColors:{value:null},emojiButtonRadius:{value:"100%"},emojiButtonSize:{value:36},emojiSize:{value:24},emojiVersion:{value:15,choices:[1,2,3,4,5,11,12,12.1,13,13.1,14,15]},exceptEmojis:{value:[]},icons:{value:"auto",choices:["auto","outline","solid"]},locale:{value:"en",choices:["en","ar","be","cs","de","es","fa","fi","fr","hi","it","ja","ko","nl","pl","pt","ru","sa","tr","uk","vi","zh"]},maxFrequentRows:{value:4},navPosition:{value:"top",choices:["top","bottom","none"]},noCountryFlags:{value:!1},noResultsEmoji:{value:null},perLine:{value:9},previewEmoji:{value:null},previewPosition:{value:"bottom",choices:["top","bottom","none"]},searchPosition:{value:"sticky",choices:["sticky","static","none"]},set:{value:"native",choices:["native","apple","facebook","google","twitter"]},skin:{value:1,choices:[1,2,3,4,5,6]},skinTonePosition:{value:"preview",choices:["preview","search","none"]},theme:{value:"auto",choices:["auto","light","dark"]},categories:null,categoryIcons:null,custom:null,data:null,i18n:null,getImageURL:null,getSpritesheetURL:null,onAddCustomEmoji:null,onClickOutside:null,onEmojiSelect:null,stickySearch:{deprecated:!0,value:!0}};let ji=null,Ti=null;const Ei={};async function Pi(e){if(Ei[e])return Ei[e];const t=await fetch(e),n=await t.json();return Ei[e]=n,n}let Mi=null,Ai=null,Li=!1;function Ii(e,{caller:t}={}){return Mi||(Mi=new Promise((e=>{Ai=e}))),e?async function(e){Li=!0;let{emojiVersion:t,set:n,locale:r}=e;if(t||(t=Si.emojiVersion.value),n||(n=Si.set.value),r||(r=Si.locale.value),Ti)Ti.categories=Ti.categories.filter((e=>!e.name));else{Ti=("function"==typeof e.data?await e.data():e.data)||await Pi(`https://cdn.jsdelivr.net/npm/@emoji-mart/data@latest/sets/${t}/${n}.json`),Ti.emoticons={},Ti.natives={},Ti.categories.unshift({id:"frequent",emojis:[]});for(const e in Ti.aliases){const t=Ti.aliases[e],n=Ti.emojis[t];n&&(n.aliases||(n.aliases=[]),n.aliases.push(e))}Ti.originalCategories=Ti.categories}if(ji=("function"==typeof e.i18n?await e.i18n():e.i18n)||("en"==r?Ar(xi):await Pi(`https://cdn.jsdelivr.net/npm/@emoji-mart/data@latest/i18n/${r}.json`)),e.custom)for(let t in e.custom){t=parseInt(t);const n=e.custom[t],r=e.custom[t-1];if(n.emojis&&n.emojis.length){n.id||(n.id=`custom_${t+1}`),n.name||(n.name=ji.categories.custom),r&&!n.icon&&(n.target=r.target||r),Ti.categories.push(n);for(const e of n.emojis)Ti.emojis[e.id]=e}}e.categories&&(Ti.categories=Ti.originalCategories.filter((t=>-1!=e.categories.indexOf(t.id))).sort(((t,n)=>e.categories.indexOf(t.id)-e.categories.indexOf(n.id))));let i=null,o=null;"native"==n&&(i=_i.latestVersion(),o=e.noCountryFlags||_i.noCountryFlags());let s=Ti.categories.length,a=!1;for(;s--;){const t=Ti.categories[s];if("frequent"==t.id){let{maxFrequentRows:n,perLine:r}=e;n=n>=0?n:Si.maxFrequentRows.value,r||(r=Si.perLine.value),t.emojis=Ci.get({maxFrequentRows:n,perLine:r})}if(!t.emojis||!t.emojis.length){Ti.categories.splice(s,1);continue}const{categoryIcons:n}=e;if(n){const e=n[t.id];e&&!t.icon&&(t.icon=e)}let r=t.emojis.length;for(;r--;){const n=t.emojis[r],s=n.id?n:Ti.emojis[n],l=()=>{t.emojis.splice(r,1)};if(!s||e.exceptEmojis&&e.exceptEmojis.includes(s.id))l();else if(i&&s.version>i)l();else if(!o||"flags"!=t.id||Ni.includes(s.id)){if(!s.search){if(a=!0,s.search=","+[[s.id,!1],[s.name,!0],[s.keywords,!1],[s.emoticons,!1]].map((([e,t])=>{if(e)return(Array.isArray(e)?e:[e]).map((e=>(t?e.split(/[-|_|\s]+/):[e]).map((e=>e.toLowerCase())))).flat()})).flat().filter((e=>e&&e.trim())).join(","),s.emoticons)for(const e of s.emoticons)Ti.emoticons[e]||(Ti.emoticons[e]=s.id);let e=0;for(const t of s.skins){if(!t)continue;e++;const{native:n}=t;n&&(Ti.natives[n]=s.id,s.search+=`,${n}`);const r=1==e?"":`:skin-tone-${e}:`;t.shortcodes=`:${s.id}:${r}`}}}else l()}}a&&Di.reset(),Ai()}(e):t&&!Li&&console.warn(`\`${t}\` requires data to be initialized first. Promise will be pending until \`init\` is called.`),Mi}function Ri(e,t,n){e||(e={});const r={};for(let i in t)r[i]=Bi(i,e,t,n);return r}function Bi(e,t,n,r){const i=n[e];let o=r&&r.getAttribute(e)||(null!=t[e]&&null!=t[e]?t[e]:null);return i?(null!=o&&i.value&&typeof i.value!=typeof o&&(o="boolean"==typeof i.value?"false"!=o:i.value.constructor(o)),i.transform&&o&&(o=i.transform(o)),(null==o||i.choices&&-1==i.choices.indexOf(o))&&(o=i.value),o):o}let Fi=null;var Di={search:async function(e,{maxResults:t,caller:n}={}){if(!e||!e.trim().length)return null;t||(t=90),await Ii(null,{caller:n||"SearchIndex.search"});const r=e.toLowerCase().replace(/(\w)-/,"$1 ").split(/[\s|,]+/).filter(((e,t,n)=>e.trim()&&n.indexOf(e)==t));if(!r.length)return;let i,o,s=Fi||(Fi=Object.values(Ti.emojis));for(const e of r){if(!s.length)break;i=[],o={};for(const t of s){if(!t.search)continue;const n=t.search.indexOf(`,${e}`);-1!=n&&(i.push(t),o[t.id]||(o[t.id]=0),o[t.id]+=t.id==e?0:n+1)}s=i}return i.length<2||(i.sort(((e,t)=>{const n=o[e.id],r=o[t.id];return n==r?e.id.localeCompare(t.id):n-r})),i.length>t&&(i=i.slice(0,t))),i},get:function(e){return e.id?e:Ti.emojis[e]||Ti.emojis[Ti.aliases[e]]||Ti.emojis[Ti.natives[e]]},reset:function(){Fi=null},SHORTCODES_REGEX:/^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/};const Ni=["checkered_flag","crossed_flags","pirate_flag","rainbow-flag","transgender_flag","triangular_flag_on_post","waving_black_flag","waving_white_flag"];var zi={categories:{activity:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:pi("path",{d:"M12 0C5.373 0 0 5.372 0 12c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.628-5.372-12-12-12m9.949 11H17.05c.224-2.527 1.232-4.773 1.968-6.113A9.966 9.966 0 0 1 21.949 11M13 11V2.051a9.945 9.945 0 0 1 4.432 1.564c-.858 1.491-2.156 4.22-2.392 7.385H13zm-2 0H8.961c-.238-3.165-1.536-5.894-2.393-7.385A9.95 9.95 0 0 1 11 2.051V11zm0 2v8.949a9.937 9.937 0 0 1-4.432-1.564c.857-1.492 2.155-4.221 2.393-7.385H11zm4.04 0c.236 3.164 1.534 5.893 2.392 7.385A9.92 9.92 0 0 1 13 21.949V13h2.04zM4.982 4.887C5.718 6.227 6.726 8.473 6.951 11h-4.9a9.977 9.977 0 0 1 2.931-6.113M2.051 13h4.9c-.226 2.527-1.233 4.771-1.969 6.113A9.972 9.972 0 0 1 2.051 13m16.967 6.113c-.735-1.342-1.744-3.586-1.968-6.113h4.899a9.961 9.961 0 0 1-2.931 6.113"})}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:pi("path",{d:"M16.17 337.5c0 44.98 7.565 83.54 13.98 107.9C35.22 464.3 50.46 496 174.9 496c9.566 0 19.59-.4707 29.84-1.271L17.33 307.3C16.53 317.6 16.17 327.7 16.17 337.5zM495.8 174.5c0-44.98-7.565-83.53-13.98-107.9c-4.688-17.54-18.34-31.23-36.04-35.95C435.5 27.91 392.9 16 337 16c-9.564 0-19.59 .4707-29.84 1.271l187.5 187.5C495.5 194.4 495.8 184.3 495.8 174.5zM26.77 248.8l236.3 236.3c142-36.1 203.9-150.4 222.2-221.1L248.9 26.87C106.9 62.96 45.07 177.2 26.77 248.8zM256 335.1c0 9.141-7.474 16-16 16c-4.094 0-8.188-1.564-11.31-4.689L164.7 283.3C161.6 280.2 160 276.1 160 271.1c0-8.529 6.865-16 16-16c4.095 0 8.189 1.562 11.31 4.688l64.01 64C254.4 327.8 256 331.9 256 335.1zM304 287.1c0 9.141-7.474 16-16 16c-4.094 0-8.188-1.564-11.31-4.689L212.7 235.3C209.6 232.2 208 228.1 208 223.1c0-9.141 7.473-16 16-16c4.094 0 8.188 1.562 11.31 4.688l64.01 64.01C302.5 279.8 304 283.9 304 287.1zM256 175.1c0-9.141 7.473-16 16-16c4.094 0 8.188 1.562 11.31 4.688l64.01 64.01c3.125 3.125 4.688 7.219 4.688 11.31c0 9.133-7.468 16-16 16c-4.094 0-8.189-1.562-11.31-4.688l-64.01-64.01C257.6 184.2 256 180.1 256 175.1z"})})},custom:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512",children:pi("path",{d:"M417.1 368c-5.937 10.27-16.69 16-27.75 16c-5.422 0-10.92-1.375-15.97-4.281L256 311.4V448c0 17.67-14.33 32-31.1 32S192 465.7 192 448V311.4l-118.3 68.29C68.67 382.6 63.17 384 57.75 384c-11.06 0-21.81-5.734-27.75-16c-8.828-15.31-3.594-34.88 11.72-43.72L159.1 256L41.72 187.7C26.41 178.9 21.17 159.3 29.1 144C36.63 132.5 49.26 126.7 61.65 128.2C65.78 128.7 69.88 130.1 73.72 132.3L192 200.6V64c0-17.67 14.33-32 32-32S256 46.33 256 64v136.6l118.3-68.29c3.838-2.213 7.939-3.539 12.07-4.051C398.7 126.7 411.4 132.5 417.1 144c8.828 15.31 3.594 34.88-11.72 43.72L288 256l118.3 68.28C421.6 333.1 426.8 352.7 417.1 368z"})}),flags:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:pi("path",{d:"M0 0l6.084 24H8L1.916 0zM21 5h-4l-1-4H4l3 12h3l1 4h13L21 5zM6.563 3h7.875l2 8H8.563l-2-8zm8.832 10l-2.856 1.904L12.063 13h3.332zM19 13l-1.5-6h1.938l2 8H16l3-2z"})}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:pi("path",{d:"M64 496C64 504.8 56.75 512 48 512h-32C7.25 512 0 504.8 0 496V32c0-17.75 14.25-32 32-32s32 14.25 32 32V496zM476.3 0c-6.365 0-13.01 1.35-19.34 4.233c-45.69 20.86-79.56 27.94-107.8 27.94c-59.96 0-94.81-31.86-163.9-31.87C160.9 .3055 131.6 4.867 96 15.75v350.5c32-9.984 59.87-14.1 84.85-14.1c73.63 0 124.9 31.78 198.6 31.78c31.91 0 68.02-5.971 111.1-23.09C504.1 355.9 512 344.4 512 332.1V30.73C512 11.1 495.3 0 476.3 0z"})})},foods:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:pi("path",{d:"M17 4.978c-1.838 0-2.876.396-3.68.934.513-1.172 1.768-2.934 4.68-2.934a1 1 0 0 0 0-2c-2.921 0-4.629 1.365-5.547 2.512-.064.078-.119.162-.18.244C11.73 1.838 10.798.023 9.207.023 8.579.022 7.85.306 7 .978 5.027 2.54 5.329 3.902 6.492 4.999 3.609 5.222 0 7.352 0 12.969c0 4.582 4.961 11.009 9 11.009 1.975 0 2.371-.486 3-1 .629.514 1.025 1 3 1 4.039 0 9-6.418 9-11 0-5.953-4.055-8-7-8M8.242 2.546c.641-.508.943-.523.965-.523.426.169.975 1.405 1.357 3.055-1.527-.629-2.741-1.352-2.98-1.846.059-.112.241-.356.658-.686M15 21.978c-1.08 0-1.21-.109-1.559-.402l-.176-.146c-.367-.302-.816-.452-1.266-.452s-.898.15-1.266.452l-.176.146c-.347.292-.477.402-1.557.402-2.813 0-7-5.389-7-9.009 0-5.823 4.488-5.991 5-5.991 1.939 0 2.484.471 3.387 1.251l.323.276a1.995 1.995 0 0 0 2.58 0l.323-.276c.902-.78 1.447-1.251 3.387-1.251.512 0 5 .168 5 6 0 3.617-4.187 9-7 9"})}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:pi("path",{d:"M481.9 270.1C490.9 279.1 496 291.3 496 304C496 316.7 490.9 328.9 481.9 337.9C472.9 346.9 460.7 352 448 352H64C51.27 352 39.06 346.9 30.06 337.9C21.06 328.9 16 316.7 16 304C16 291.3 21.06 279.1 30.06 270.1C39.06 261.1 51.27 256 64 256H448C460.7 256 472.9 261.1 481.9 270.1zM475.3 388.7C478.3 391.7 480 395.8 480 400V416C480 432.1 473.3 449.3 461.3 461.3C449.3 473.3 432.1 480 416 480H96C79.03 480 62.75 473.3 50.75 461.3C38.74 449.3 32 432.1 32 416V400C32 395.8 33.69 391.7 36.69 388.7C39.69 385.7 43.76 384 48 384H464C468.2 384 472.3 385.7 475.3 388.7zM50.39 220.8C45.93 218.6 42.03 215.5 38.97 211.6C35.91 207.7 33.79 203.2 32.75 198.4C31.71 193.5 31.8 188.5 32.99 183.7C54.98 97.02 146.5 32 256 32C365.5 32 457 97.02 479 183.7C480.2 188.5 480.3 193.5 479.2 198.4C478.2 203.2 476.1 207.7 473 211.6C469.1 215.5 466.1 218.6 461.6 220.8C457.2 222.9 452.3 224 447.3 224H64.67C59.73 224 54.84 222.9 50.39 220.8zM372.7 116.7C369.7 119.7 368 123.8 368 128C368 131.2 368.9 134.3 370.7 136.9C372.5 139.5 374.1 141.6 377.9 142.8C380.8 143.1 384 144.3 387.1 143.7C390.2 143.1 393.1 141.6 395.3 139.3C397.6 137.1 399.1 134.2 399.7 131.1C400.3 128 399.1 124.8 398.8 121.9C397.6 118.1 395.5 116.5 392.9 114.7C390.3 112.9 387.2 111.1 384 111.1C379.8 111.1 375.7 113.7 372.7 116.7V116.7zM244.7 84.69C241.7 87.69 240 91.76 240 96C240 99.16 240.9 102.3 242.7 104.9C244.5 107.5 246.1 109.6 249.9 110.8C252.8 111.1 256 112.3 259.1 111.7C262.2 111.1 265.1 109.6 267.3 107.3C269.6 105.1 271.1 102.2 271.7 99.12C272.3 96.02 271.1 92.8 270.8 89.88C269.6 86.95 267.5 84.45 264.9 82.7C262.3 80.94 259.2 79.1 256 79.1C251.8 79.1 247.7 81.69 244.7 84.69V84.69zM116.7 116.7C113.7 119.7 112 123.8 112 128C112 131.2 112.9 134.3 114.7 136.9C116.5 139.5 118.1 141.6 121.9 142.8C124.8 143.1 128 144.3 131.1 143.7C134.2 143.1 137.1 141.6 139.3 139.3C141.6 137.1 143.1 134.2 143.7 131.1C144.3 128 143.1 124.8 142.8 121.9C141.6 118.1 139.5 116.5 136.9 114.7C134.3 112.9 131.2 111.1 128 111.1C123.8 111.1 119.7 113.7 116.7 116.7L116.7 116.7z"})})},frequent:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[pi("path",{d:"M13 4h-2l-.001 7H9v2h2v2h2v-2h4v-2h-4z"}),pi("path",{d:"M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0m0 22C6.486 22 2 17.514 2 12S6.486 2 12 2s10 4.486 10 10-4.486 10-10 10"})]}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:pi("path",{d:"M256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512zM232 256C232 264 236 271.5 242.7 275.1L338.7 339.1C349.7 347.3 364.6 344.3 371.1 333.3C379.3 322.3 376.3 307.4 365.3 300L280 243.2V120C280 106.7 269.3 96 255.1 96C242.7 96 231.1 106.7 231.1 120L232 256z"})})},nature:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[pi("path",{d:"M15.5 8a1.5 1.5 0 1 0 .001 3.001A1.5 1.5 0 0 0 15.5 8M8.5 8a1.5 1.5 0 1 0 .001 3.001A1.5 1.5 0 0 0 8.5 8"}),pi("path",{d:"M18.933 0h-.027c-.97 0-2.138.787-3.018 1.497-1.274-.374-2.612-.51-3.887-.51-1.285 0-2.616.133-3.874.517C7.245.79 6.069 0 5.093 0h-.027C3.352 0 .07 2.67.002 7.026c-.039 2.479.276 4.238 1.04 5.013.254.258.882.677 1.295.882.191 3.177.922 5.238 2.536 6.38.897.637 2.187.949 3.2 1.102C8.04 20.6 8 20.795 8 21c0 1.773 2.35 3 4 3 1.648 0 4-1.227 4-3 0-.201-.038-.393-.072-.586 2.573-.385 5.435-1.877 5.925-7.587.396-.22.887-.568 1.104-.788.763-.774 1.079-2.534 1.04-5.013C23.929 2.67 20.646 0 18.933 0M3.223 9.135c-.237.281-.837 1.155-.884 1.238-.15-.41-.368-1.349-.337-3.291.051-3.281 2.478-4.972 3.091-5.031.256.015.731.27 1.265.646-1.11 1.171-2.275 2.915-2.352 5.125-.133.546-.398.858-.783 1.313M12 22c-.901 0-1.954-.693-2-1 0-.654.475-1.236 1-1.602V20a1 1 0 1 0 2 0v-.602c.524.365 1 .947 1 1.602-.046.307-1.099 1-2 1m3-3.48v.02a4.752 4.752 0 0 0-1.262-1.02c1.092-.516 2.239-1.334 2.239-2.217 0-1.842-1.781-2.195-3.977-2.195-2.196 0-3.978.354-3.978 2.195 0 .883 1.148 1.701 2.238 2.217A4.8 4.8 0 0 0 9 18.539v-.025c-1-.076-2.182-.281-2.973-.842-1.301-.92-1.838-3.045-1.853-6.478l.023-.041c.496-.826 1.49-1.45 1.804-3.102 0-2.047 1.357-3.631 2.362-4.522C9.37 3.178 10.555 3 11.948 3c1.447 0 2.685.192 3.733.57 1 .9 2.316 2.465 2.316 4.48.313 1.651 1.307 2.275 1.803 3.102.035.058.068.117.102.178-.059 5.967-1.949 7.01-4.902 7.19m6.628-8.202c-.037-.065-.074-.13-.113-.195a7.587 7.587 0 0 0-.739-.987c-.385-.455-.648-.768-.782-1.313-.076-2.209-1.241-3.954-2.353-5.124.531-.376 1.004-.63 1.261-.647.636.071 3.044 1.764 3.096 5.031.027 1.81-.347 3.218-.37 3.235"})]}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 576 512",children:pi("path",{d:"M332.7 19.85C334.6 8.395 344.5 0 356.1 0C363.6 0 370.6 3.52 375.1 9.502L392 32H444.1C456.8 32 469.1 37.06 478.1 46.06L496 64H552C565.3 64 576 74.75 576 88V112C576 156.2 540.2 192 496 192H426.7L421.6 222.5L309.6 158.5L332.7 19.85zM448 64C439.2 64 432 71.16 432 80C432 88.84 439.2 96 448 96C456.8 96 464 88.84 464 80C464 71.16 456.8 64 448 64zM416 256.1V480C416 497.7 401.7 512 384 512H352C334.3 512 320 497.7 320 480V364.8C295.1 377.1 268.8 384 240 384C211.2 384 184 377.1 160 364.8V480C160 497.7 145.7 512 128 512H96C78.33 512 64 497.7 64 480V249.8C35.23 238.9 12.64 214.5 4.836 183.3L.9558 167.8C-3.331 150.6 7.094 133.2 24.24 128.1C41.38 124.7 58.76 135.1 63.05 152.2L66.93 167.8C70.49 182 83.29 191.1 97.97 191.1H303.8L416 256.1z"})})},objects:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[pi("path",{d:"M12 0a9 9 0 0 0-5 16.482V21s2.035 3 5 3 5-3 5-3v-4.518A9 9 0 0 0 12 0zm0 2c3.86 0 7 3.141 7 7s-3.14 7-7 7-7-3.141-7-7 3.14-7 7-7zM9 17.477c.94.332 1.946.523 3 .523s2.06-.19 3-.523v.834c-.91.436-1.925.689-3 .689a6.924 6.924 0 0 1-3-.69v-.833zm.236 3.07A8.854 8.854 0 0 0 12 21c.965 0 1.888-.167 2.758-.451C14.155 21.173 13.153 22 12 22c-1.102 0-2.117-.789-2.764-1.453z"}),pi("path",{d:"M14.745 12.449h-.004c-.852-.024-1.188-.858-1.577-1.824-.421-1.061-.703-1.561-1.182-1.566h-.009c-.481 0-.783.497-1.235 1.537-.436.982-.801 1.811-1.636 1.791l-.276-.043c-.565-.171-.853-.691-1.284-1.794-.125-.313-.202-.632-.27-.913-.051-.213-.127-.53-.195-.634C7.067 9.004 7.039 9 6.99 9A1 1 0 0 1 7 7h.01c1.662.017 2.015 1.373 2.198 2.134.486-.981 1.304-2.058 2.797-2.075 1.531.018 2.28 1.153 2.731 2.141l.002-.008C14.944 8.424 15.327 7 16.979 7h.032A1 1 0 1 1 17 9h-.011c-.149.076-.256.474-.319.709a6.484 6.484 0 0 1-.311.951c-.429.973-.79 1.789-1.614 1.789"})]}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 384 512",children:pi("path",{d:"M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"})})},people:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[pi("path",{d:"M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0m0 22C6.486 22 2 17.514 2 12S6.486 2 12 2s10 4.486 10 10-4.486 10-10 10"}),pi("path",{d:"M8 7a2 2 0 1 0-.001 3.999A2 2 0 0 0 8 7M16 7a2 2 0 1 0-.001 3.999A2 2 0 0 0 16 7M15.232 15c-.693 1.195-1.87 2-3.349 2-1.477 0-2.655-.805-3.347-2H15m3-2H6a6 6 0 1 0 12 0"})]}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:pi("path",{d:"M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM256 432C332.1 432 396.2 382 415.2 314.1C419.1 300.4 407.8 288 393.6 288H118.4C104.2 288 92.92 300.4 96.76 314.1C115.8 382 179.9 432 256 432V432zM176.4 160C158.7 160 144.4 174.3 144.4 192C144.4 209.7 158.7 224 176.4 224C194 224 208.4 209.7 208.4 192C208.4 174.3 194 160 176.4 160zM336.4 224C354 224 368.4 209.7 368.4 192C368.4 174.3 354 160 336.4 160C318.7 160 304.4 174.3 304.4 192C304.4 209.7 318.7 224 336.4 224z"})})},places:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[pi("path",{d:"M6.5 12C5.122 12 4 13.121 4 14.5S5.122 17 6.5 17 9 15.879 9 14.5 7.878 12 6.5 12m0 3c-.275 0-.5-.225-.5-.5s.225-.5.5-.5.5.225.5.5-.225.5-.5.5M17.5 12c-1.378 0-2.5 1.121-2.5 2.5s1.122 2.5 2.5 2.5 2.5-1.121 2.5-2.5-1.122-2.5-2.5-2.5m0 3c-.275 0-.5-.225-.5-.5s.225-.5.5-.5.5.225.5.5-.225.5-.5.5"}),pi("path",{d:"M22.482 9.494l-1.039-.346L21.4 9h.6c.552 0 1-.439 1-.992 0-.006-.003-.008-.003-.008H23c0-1-.889-2-1.984-2h-.642l-.731-1.717C19.262 3.012 18.091 2 16.764 2H7.236C5.909 2 4.738 3.012 4.357 4.283L3.626 6h-.642C1.889 6 1 7 1 8h.003S1 8.002 1 8.008C1 8.561 1.448 9 2 9h.6l-.043.148-1.039.346a2.001 2.001 0 0 0-1.359 2.097l.751 7.508a1 1 0 0 0 .994.901H3v1c0 1.103.896 2 2 2h2c1.104 0 2-.897 2-2v-1h6v1c0 1.103.896 2 2 2h2c1.104 0 2-.897 2-2v-1h1.096a.999.999 0 0 0 .994-.901l.751-7.508a2.001 2.001 0 0 0-1.359-2.097M6.273 4.857C6.402 4.43 6.788 4 7.236 4h9.527c.448 0 .834.43.963.857L19.313 9H4.688l1.585-4.143zM7 21H5v-1h2v1zm12 0h-2v-1h2v1zm2.189-3H2.811l-.662-6.607L3 11h18l.852.393L21.189 18z"})]}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:pi("path",{d:"M39.61 196.8L74.8 96.29C88.27 57.78 124.6 32 165.4 32H346.6C387.4 32 423.7 57.78 437.2 96.29L472.4 196.8C495.6 206.4 512 229.3 512 256V448C512 465.7 497.7 480 480 480H448C430.3 480 416 465.7 416 448V400H96V448C96 465.7 81.67 480 64 480H32C14.33 480 0 465.7 0 448V256C0 229.3 16.36 206.4 39.61 196.8V196.8zM109.1 192H402.9L376.8 117.4C372.3 104.6 360.2 96 346.6 96H165.4C151.8 96 139.7 104.6 135.2 117.4L109.1 192zM96 256C78.33 256 64 270.3 64 288C64 305.7 78.33 320 96 320C113.7 320 128 305.7 128 288C128 270.3 113.7 256 96 256zM416 320C433.7 320 448 305.7 448 288C448 270.3 433.7 256 416 256C398.3 256 384 270.3 384 288C384 305.7 398.3 320 416 320z"})})},symbols:{outline:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:pi("path",{d:"M0 0h11v2H0zM4 11h3V6h4V4H0v2h4zM15.5 17c1.381 0 2.5-1.116 2.5-2.493s-1.119-2.493-2.5-2.493S13 13.13 13 14.507 14.119 17 15.5 17m0-2.986c.276 0 .5.222.5.493 0 .272-.224.493-.5.493s-.5-.221-.5-.493.224-.493.5-.493M21.5 19.014c-1.381 0-2.5 1.116-2.5 2.493S20.119 24 21.5 24s2.5-1.116 2.5-2.493-1.119-2.493-2.5-2.493m0 2.986a.497.497 0 0 1-.5-.493c0-.271.224-.493.5-.493s.5.222.5.493a.497.497 0 0 1-.5.493M22 13l-9 9 1.513 1.5 8.99-9.009zM17 11c2.209 0 4-1.119 4-2.5V2s.985-.161 1.498.949C23.01 4.055 23 6 23 6s1-1.119 1-3.135C24-.02 21 0 21 0h-2v6.347A5.853 5.853 0 0 0 17 6c-2.209 0-4 1.119-4 2.5s1.791 2.5 4 2.5M10.297 20.482l-1.475-1.585a47.54 47.54 0 0 1-1.442 1.129c-.307-.288-.989-1.016-2.045-2.183.902-.836 1.479-1.466 1.729-1.892s.376-.871.376-1.336c0-.592-.273-1.178-.818-1.759-.546-.581-1.329-.871-2.349-.871-1.008 0-1.79.293-2.344.879-.556.587-.832 1.181-.832 1.784 0 .813.419 1.748 1.256 2.805-.847.614-1.444 1.208-1.794 1.784a3.465 3.465 0 0 0-.523 1.833c0 .857.308 1.56.924 2.107.616.549 1.423.823 2.42.823 1.173 0 2.444-.379 3.813-1.137L8.235 24h2.819l-2.09-2.383 1.333-1.135zm-6.736-6.389a1.02 1.02 0 0 1 .73-.286c.31 0 .559.085.747.254a.849.849 0 0 1 .283.659c0 .518-.419 1.112-1.257 1.784-.536-.651-.805-1.231-.805-1.742a.901.901 0 0 1 .302-.669M3.74 22c-.427 0-.778-.116-1.057-.349-.279-.232-.418-.487-.418-.766 0-.594.509-1.288 1.527-2.083.968 1.134 1.717 1.946 2.248 2.438-.921.507-1.686.76-2.3.76"})}),solid:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:pi("path",{d:"M500.3 7.251C507.7 13.33 512 22.41 512 31.1V175.1C512 202.5 483.3 223.1 447.1 223.1C412.7 223.1 383.1 202.5 383.1 175.1C383.1 149.5 412.7 127.1 447.1 127.1V71.03L351.1 90.23V207.1C351.1 234.5 323.3 255.1 287.1 255.1C252.7 255.1 223.1 234.5 223.1 207.1C223.1 181.5 252.7 159.1 287.1 159.1V63.1C287.1 48.74 298.8 35.61 313.7 32.62L473.7 .6198C483.1-1.261 492.9 1.173 500.3 7.251H500.3zM74.66 303.1L86.5 286.2C92.43 277.3 102.4 271.1 113.1 271.1H174.9C185.6 271.1 195.6 277.3 201.5 286.2L213.3 303.1H239.1C266.5 303.1 287.1 325.5 287.1 351.1V463.1C287.1 490.5 266.5 511.1 239.1 511.1H47.1C21.49 511.1-.0019 490.5-.0019 463.1V351.1C-.0019 325.5 21.49 303.1 47.1 303.1H74.66zM143.1 359.1C117.5 359.1 95.1 381.5 95.1 407.1C95.1 434.5 117.5 455.1 143.1 455.1C170.5 455.1 191.1 434.5 191.1 407.1C191.1 381.5 170.5 359.1 143.1 359.1zM440.3 367.1H496C502.7 367.1 508.6 372.1 510.1 378.4C513.3 384.6 511.6 391.7 506.5 396L378.5 508C372.9 512.1 364.6 513.3 358.6 508.9C352.6 504.6 350.3 496.6 353.3 489.7L391.7 399.1H336C329.3 399.1 323.4 395.9 321 389.6C318.7 383.4 320.4 376.3 325.5 371.1L453.5 259.1C459.1 255 467.4 254.7 473.4 259.1C479.4 263.4 481.6 271.4 478.7 278.3L440.3 367.1zM116.7 219.1L19.85 119.2C-8.112 90.26-6.614 42.31 24.85 15.34C51.82-8.137 93.26-3.642 118.2 21.83L128.2 32.32L137.7 21.83C162.7-3.642 203.6-8.137 231.6 15.34C262.6 42.31 264.1 90.26 236.1 119.2L139.7 219.1C133.2 225.6 122.7 225.6 116.7 219.1H116.7z"})})}},search:{loupe:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:pi("path",{d:"M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"})}),delete:pi("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:pi("path",{d:"M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z"})})}};function Vi(e){let{id:t,skin:n,emoji:r}=e;if(e.shortcodes){const r=e.shortcodes.match(Di.SHORTCODES_REGEX);r&&(t=r[1],r[2]&&(n=r[2]))}if(r||(r=Di.get(t||e.native)),!r)return e.fallback;const i=r.skins[n-1]||r.skins[0],o=i.src||("native"==e.set||e.spritesheet?void 0:"function"==typeof e.getImageURL?e.getImageURL(e.set,i.unified):`https://cdn.jsdelivr.net/npm/emoji-datasource-${e.set}@15.0.1/img/${e.set}/64/${i.unified}.png`),s="function"==typeof e.getSpritesheetURL?e.getSpritesheetURL(e.set):`https://cdn.jsdelivr.net/npm/emoji-datasource-${e.set}@15.0.1/img/${e.set}/sheets-256/64.png`;return pi("span",{class:"emoji-mart-emoji","data-emoji-set":e.set,children:o?pi("img",{style:{maxWidth:e.size||"1em",maxHeight:e.size||"1em",display:"inline-block"},alt:i.native||i.shortcodes,src:o}):"native"==e.set?pi("span",{style:{fontSize:e.size,fontFamily:'"EmojiMart", "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji"'},children:i.native}):pi("span",{style:{display:"block",width:e.size,height:e.size,backgroundImage:`url(${s})`,backgroundSize:`${100*Ti.sheet.cols}% ${100*Ti.sheet.rows}%`,backgroundPosition:`${100/(Ti.sheet.cols-1)*i.x}% ${100/(Ti.sheet.rows-1)*i.y}%`}})})}const $i="undefined"!=typeof window&&window.HTMLElement?window.HTMLElement:Object;class Hi extends $i{static get observedAttributes(){return Object.keys(this.Props)}update(e={}){for(let t in e)this.attributeChangedCallback(t,null,e[t])}attributeChangedCallback(e,t,n){if(!this.component)return;const r=Bi(e,{[e]:n},this.constructor.Props,this);this.component.componentWillReceiveProps?this.component.componentWillReceiveProps({[e]:r}):(this.component.props[e]=r,this.component.forceUpdate())}disconnectedCallback(){this.disconnected=!0,this.component&&this.component.unregister&&this.component.unregister()}constructor(e={}){if(super(),this.props=e,e.parent||e.ref){let t=null;const n=e.parent||(t=e.ref&&e.ref.current);t&&(t.innerHTML=""),n&&n.appendChild(this)}}}class qi extends Hi{setShadow(){this.attachShadow({mode:"open"})}injectStyles(e){if(!e)return;const t=document.createElement("style");t.textContent=e,this.shadowRoot.insertBefore(t,this.shadowRoot.firstChild)}constructor(e,{styles:t}={}){super(e),this.setShadow(),this.injectStyles(t)}}var Ui={fallback:"",id:"",native:"",shortcodes:"",size:{value:"",transform:e=>/\D/.test(e)?e:`${e}px`},set:Si.set,skin:Si.skin};class Ki extends Hi{async connectedCallback(){const e=Ri(this.props,Ui,this);e.element=this,e.ref=e=>{this.component=e},await Ii(),this.disconnected||di(pi(Vi,{...e}),this)}constructor(e){super(e)}}Lr(Ki,"Props",Ui),"undefined"==typeof customElements||customElements.get("em-emoji")||customElements.define("em-emoji",Ki);var Wi,Ji,Zi=[],Gi=Rr.__b,Xi=Rr.__r,Yi=Rr.diffed,Qi=Rr.__c,eo=Rr.unmount;function to(){var e;for(Zi.sort((function(e,t){return e.__v.__b-t.__v.__b}));e=Zi.pop();)if(e.__P)try{e.__H.__h.forEach(ro),e.__H.__h.forEach(io),e.__H.__h=[]}catch(t){e.__H.__h=[],Rr.__e(t,e.__v)}}Rr.__b=function(e){Wi=null,Gi&&Gi(e)},Rr.__r=function(e){Xi&&Xi(e);var t=(Wi=e.__c).__H;t&&(t.__h.forEach(ro),t.__h.forEach(io),t.__h=[])},Rr.diffed=function(e){Yi&&Yi(e);var t=e.__c;t&&t.__H&&t.__H.__h.length&&(1!==Zi.push(t)&&Ji===Rr.requestAnimationFrame||((Ji=Rr.requestAnimationFrame)||function(e){var t,n=function(){clearTimeout(r),no&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,100);no&&(t=requestAnimationFrame(n))})(to)),Wi=null},Rr.__c=function(e,t){t.some((function(e){try{e.__h.forEach(ro),e.__h=e.__h.filter((function(e){return!e.__||io(e)}))}catch(n){t.some((function(e){e.__h&&(e.__h=[])})),t=[],Rr.__e(n,e.__v)}})),Qi&&Qi(e,t)},Rr.unmount=function(e){eo&&eo(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.forEach((function(e){try{ro(e)}catch(e){t=e}})),t&&Rr.__e(t,n.__v))};var no="function"==typeof requestAnimationFrame;function ro(e){var t=Wi,n=e.__c;"function"==typeof n&&(e.__c=void 0,n()),Wi=t}function io(e){var t=Wi;e.__c=e.__(),Wi=t}function oo(e,t){for(var n in e)if("__source"!==n&&!(n in t))return!0;for(var r in t)if("__source"!==r&&e[r]!==t[r])return!0;return!1}function so(e){this.props=e}(so.prototype=new Jr).isPureReactComponent=!0,so.prototype.shouldComponentUpdate=function(e,t){return oo(this.props,e)||oo(this.state,t)};var ao=Rr.__b;Rr.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),ao&&ao(e)},"undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.forward_ref");var lo=Rr.__e;Rr.__e=function(e,t,n){if(e.then)for(var r,i=t;i=i.__;)if((r=i.__c)&&r.__c)return null==t.__e&&(t.__e=n.__e,t.__k=n.__k),r.__c(e,t);lo(e,t,n)};var co=Rr.unmount;function uo(){this.__u=0,this.t=null,this.__b=null}function ho(e){var t=e.__.__c;return t&&t.__e&&t.__e(e)}function fo(){this.u=null,this.o=null}Rr.unmount=function(e){var t=e.__c;t&&t.__R&&t.__R(),t&&!0===e.__h&&(e.type=null),co&&co(e)},(uo.prototype=new Jr).__c=function(e,t){var n=t.__c,r=this;null==r.t&&(r.t=[]),r.t.push(n);var i=ho(r.__v),o=!1,s=function(){o||(o=!0,n.__R=null,i?i(a):a())};n.__R=s;var a=function(){if(!--r.__u){if(r.state.__e){var e=r.state.__e;r.__v.__k[0]=function e(t,n,r){return t&&(t.__v=null,t.__k=t.__k&&t.__k.map((function(t){return e(t,n,r)})),t.__c&&t.__c.__P===n&&(t.__e&&r.insertBefore(t.__e,t.__d),t.__c.__e=!0,t.__c.__P=r)),t}(e,e.__c.__P,e.__c.__O)}var t;for(r.setState({__e:r.__b=null});t=r.t.pop();)t.forceUpdate()}},l=!0===t.__h;r.__u++||l||r.setState({__e:r.__b=r.__v.__k[0]}),e.then(s,s)},uo.prototype.componentWillUnmount=function(){this.t=[]},uo.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var n=document.createElement("div"),r=this.__v.__k[0].__c;this.__v.__k[0]=function e(t,n,r){return t&&(t.__c&&t.__c.__H&&(t.__c.__H.__.forEach((function(e){"function"==typeof e.__c&&e.__c()})),t.__c.__H=null),null!=(t=function(e,t){for(var n in t)e[n]=t[n];return e}({},t)).__c&&(t.__c.__P===r&&(t.__c.__P=n),t.__c=null),t.__k=t.__k&&t.__k.map((function(t){return e(t,n,r)}))),t}(this.__b,n,r.__O=r.__P)}this.__b=null}var i=t.__e&&Ur(Wr,null,e.fallback);return i&&(i.__h=null),[Ur(Wr,null,t.__e?null:e.children),i]};var po=function(e,t,n){if(++n[1]===n[0]&&e.o.delete(t),e.props.revealOrder&&("t"!==e.props.revealOrder[0]||!e.o.size))for(n=e.u;n;){for(;n.length>3;)n.pop()();if(n[1]{const r=t.name||ji.categories[t.id],i=!this.props.unfocused&&t.id==this.state.categoryId;return i&&(e=n),pi("button",{"aria-label":r,"aria-selected":i||void 0,title:r,type:"button",class:"flex flex-grow flex-center",onMouseDown:e=>e.preventDefault(),onClick:()=>{this.props.onClick({category:t,i:n})},children:this.renderIcon(t)})})),pi("div",{class:"bar",style:{width:100/this.categories.length+"%",opacity:null==e?0:1,transform:"rtl"===this.props.dir?`scaleX(-1) translateX(${100*e}%)`:`translateX(${100*e}%)`}})]})})}constructor(){super(),this.categories=Ti.categories.filter((e=>!e.target)),this.state={categoryId:this.categories[0].id}}}class jo extends so{shouldComponentUpdate(e){for(let t in e)if("children"!=t&&e[t]!=this.props[t])return!0;return!1}render(){return this.props.children}}class To extends Jr{getInitialState(e=this.props){return{skin:gi("skin")||e.skin,theme:this.initTheme(e.theme)}}componentWillMount(){this.dir=ji.rtl?"rtl":"ltr",this.refs={menu:{current:null},navigation:{current:null},scroll:{current:null},search:{current:null},searchInput:{current:null},skinToneButton:{current:null},skinToneRadio:{current:null}},this.initGrid(),0==this.props.stickySearch&&"sticky"==this.props.searchPosition&&(console.warn("[EmojiMart] Deprecation warning: `stickySearch` has been renamed `searchPosition`."),this.props.searchPosition="static")}componentDidMount(){if(this.register(),this.shadowRoot=this.base.parentNode,this.props.autoFocus){const{searchInput:e}=this.refs;e.current&&e.current.focus()}}componentWillReceiveProps(e){this.nextState||(this.nextState={});for(const t in e)this.nextState[t]=e[t];clearTimeout(this.nextStateTimer),this.nextStateTimer=setTimeout((()=>{let e=!1;for(const t in this.nextState)this.props[t]=this.nextState[t],"custom"!==t&&"categories"!==t||(e=!0);delete this.nextState;const t=this.getInitialState();if(e)return this.reset(t);this.setState(t)}))}componentWillUnmount(){this.unregister()}async reset(e={}){await Ii(this.props),this.initGrid(),this.unobserve(),this.setState(e,(()=>{this.observeCategories(),this.observeRows()}))}register(){document.addEventListener("click",this.handleClickOutside),this.observe()}unregister(){document.removeEventListener("click",this.handleClickOutside),this.darkMedia?.removeEventListener("change",this.darkMediaCallback),this.unobserve()}observe(){this.observeCategories(),this.observeRows()}unobserve({except:e=[]}={}){Array.isArray(e)||(e=[e]);for(const t of this.observers)e.includes(t)||t.disconnect();this.observers=[].concat(e)}initGrid(){const{categories:e}=Ti;this.refs.categories=new Map;const t=Ti.categories.map((e=>e.id)).join(",");this.navKey&&this.navKey!=t&&this.refs.scroll.current&&(this.refs.scroll.current.scrollTop=0),this.navKey=t,this.grid=[],this.grid.setsize=0;const n=(e,t)=>{const n=[];n.__categoryId=t.id,n.__index=e.length,this.grid.push(n);const r=this.grid.length-1,i=r%10?{}:{current:null};return i.index=r,i.posinset=this.grid.setsize+1,e.push(i),n};for(let t of e){const e=[];let r=n(e,t);for(let i of t.emojis)r.length==this.getPerLine()&&(r=n(e,t)),this.grid.setsize+=1,r.push(i);this.refs.categories.set(t.id,{root:{current:null},rows:e})}}initTheme(e){if("auto"!=e)return e;if(!this.darkMedia){if(this.darkMedia=matchMedia("(prefers-color-scheme: dark)"),this.darkMedia.media.match(/^not/))return"light";this.darkMedia.addEventListener("change",this.darkMediaCallback)}return this.darkMedia.matches?"dark":"light"}initDynamicPerLine(e=this.props){if(!e.dynamicWidth)return;const{element:t,emojiButtonSize:n}=e,r=()=>{const{width:e}=t.getBoundingClientRect();return Math.floor(e/n)},i=new ResizeObserver((()=>{this.unobserve({except:i}),this.setState({perLine:r()},(()=>{this.initGrid(),this.forceUpdate((()=>{this.observeCategories(),this.observeRows()}))}))}));return i.observe(t),this.observers.push(i),r()}getPerLine(){return this.state.perLine||this.props.perLine}getEmojiByPos([e,t]){const n=this.state.searchResults||this.grid,r=n[e]&&n[e][t];if(r)return Di.get(r)}observeCategories(){const e=this.refs.navigation.current;if(!e)return;const t=new Map,n={root:this.refs.scroll.current,threshold:[0,1]},r=new IntersectionObserver((n=>{for(const e of n){const n=e.target.dataset.id;t.set(n,e.intersectionRatio)}const r=[...t];for(const[t,n]of r)if(n){(i=t)!=e.state.categoryId&&e.setState({categoryId:i});break}var i}),n);for(const{root:e}of this.refs.categories.values())r.observe(e.current);this.observers.push(r)}observeRows(){const e={...this.state.visibleRows},t=new IntersectionObserver((t=>{for(const n of t){const t=parseInt(n.target.dataset.index);n.isIntersecting?e[t]=!0:delete e[t]}this.setState({visibleRows:e})}),{root:this.refs.scroll.current,rootMargin:`${15*this.props.emojiButtonSize}px 0px ${10*this.props.emojiButtonSize}px`});for(const{rows:e}of this.refs.categories.values())for(const n of e)n.current&&t.observe(n.current);this.observers.push(t)}preventDefault(e){e.preventDefault()}unfocusSearch(){const e=this.refs.searchInput.current;e&&e.blur()}navigate({e,input:t,left:n,right:r,up:i,down:o}){const s=this.state.searchResults||this.grid;if(!s.length)return;let[a,l]=this.state.pos;const c=(()=>{if(0==a&&0==l&&!e.repeat&&(n||i))return null;if(-1==a)return e.repeat||!r&&!o||t.selectionStart!=t.value.length?null:[0,0];if(n||r){let e=s[a];const t=n?-1:1;if(l+=t,!e[l]){if(a+=t,e=s[a],!e)return a=n?0:s.length-1,l=n?0:s[a].length-1,[a,l];l=n?e.length-1:0}return[a,l]}if(i||o){a+=i?-1:1;const e=s[a];return e?(e[l]||(l=e.length-1),[a,l]):(a=i?0:s.length-1,l=i?0:s[a].length-1,[a,l])}})();c?(e.preventDefault(),this.setState({pos:c,keyboard:!0},(()=>{this.scrollTo({row:c[0]})}))):this.state.pos[0]>-1&&this.setState({pos:[-1,-1]})}scrollTo({categoryId:e,row:t}){const n=this.state.searchResults||this.grid;if(!n.length)return;const r=this.refs.scroll.current,i=r.getBoundingClientRect();let o=0;if(t>=0&&(e=n[t].__categoryId),e&&(o=(this.refs[e]||this.refs.categories.get(e).root).current.getBoundingClientRect().top-(i.top-r.scrollTop)+1),t>=0)if(t){const e=o+n[t].__index*this.props.emojiButtonSize,s=e+this.props.emojiButtonSize+.88*this.props.emojiButtonSize;if(er.scrollTop+i.height))return;o=s-i.height}}else o=0;this.ignoreMouse(),r.scrollTop=o}ignoreMouse(){this.mouseIsIgnored=!0,clearTimeout(this.ignoreMouseTimer),this.ignoreMouseTimer=setTimeout((()=>{delete this.mouseIsIgnored}),100)}handleEmojiOver(e){this.mouseIsIgnored||this.state.showSkins||this.setState({pos:e||[-1,-1],keyboard:!1})}handleEmojiClick({e,emoji:t,pos:n}){if(this.props.onEmojiSelect&&(!t&&n&&(t=this.getEmojiByPos(n)),t)){const n=function(e,{skinIndex:t=0}={}){const n=e.skins[t]||(t=0,e.skins[t]),r={id:e.id,name:e.name,native:n.native,unified:n.unified,keywords:e.keywords,shortcodes:n.shortcodes||e.shortcodes};return e.skins.length>1&&(r.skin=t+1),n.src&&(r.src=n.src),e.aliases&&e.aliases.length&&(r.aliases=e.aliases),e.emoticons&&e.emoticons.length&&(r.emoticons=e.emoticons),r}(t,{skinIndex:this.state.skin-1});this.props.maxFrequentRows&&Ci.add(n,this.props),this.props.onEmojiSelect(n,e)}}closeSkins(){this.state.showSkins&&(this.setState({showSkins:null,tempSkin:null}),this.base.removeEventListener("click",this.handleBaseClick),this.base.removeEventListener("keydown",this.handleBaseKeydown))}handleSkinMouseOver(e){this.setState({tempSkin:e})}handleSkinClick(e){this.ignoreMouse(),this.closeSkins(),this.setState({skin:e,tempSkin:null}),mi("skin",e)}renderNav(){return pi(So,{ref:this.refs.navigation,icons:this.props.icons,theme:this.state.theme,dir:this.dir,unfocused:!!this.state.searchResults,position:this.props.navPosition,onClick:this.handleCategoryClick},this.navKey)}renderPreview(){const e=this.getEmojiByPos(this.state.pos),t=this.state.searchResults&&!this.state.searchResults.length;return pi("div",{id:"preview",class:"flex flex-middle",dir:this.dir,"data-position":this.props.previewPosition,children:[pi("div",{class:"flex flex-middle flex-grow",children:[pi("div",{class:"flex flex-auto flex-middle flex-center",style:{height:this.props.emojiButtonSize,fontSize:this.props.emojiButtonSize},children:pi(Vi,{emoji:e,id:t?this.props.noResultsEmoji||"cry":this.props.previewEmoji||("top"==this.props.previewPosition?"point_down":"point_up"),set:this.props.set,size:this.props.emojiButtonSize,skin:this.state.tempSkin||this.state.skin,spritesheet:!0,getSpritesheetURL:this.props.getSpritesheetURL})}),pi("div",{class:`margin-${this.dir[0]}`,children:pi("div",e||t?{class:`padding-${this.dir[2]} align-${this.dir[0]}`,children:[pi("div",{class:"preview-title ellipsis",children:e?e.name:ji.search_no_results_1}),pi("div",{class:"preview-subtitle ellipsis color-c",children:e?e.skins[0].shortcodes:ji.search_no_results_2})]}:{class:"preview-placeholder color-c",children:ji.pick})})]}),!e&&"preview"==this.props.skinTonePosition&&this.renderSkinToneButton()]})}renderEmojiButton(e,{pos:t,posinset:n,grid:r}){const i=this.props.emojiButtonSize,o=this.state.tempSkin||this.state.skin,s=(e.skins[o-1]||e.skins[0]).native,a=(l=this.state.pos,c=t,Array.isArray(l)&&Array.isArray(c)&&l.length===c.length&&l.every(((e,t)=>e==c[t])));var l,c;const u=t.concat(e.id).join("");return pi(jo,{selected:a,skin:o,size:i,children:pi("button",{"aria-label":s,"aria-selected":a||void 0,"aria-posinset":n,"aria-setsize":r.setsize,"data-keyboard":this.state.keyboard,title:"none"==this.props.previewPosition?e.name:void 0,type:"button",class:"flex flex-center flex-middle",tabindex:"-1",onClick:t=>this.handleEmojiClick({e:t,emoji:e}),onMouseEnter:()=>this.handleEmojiOver(t),onMouseLeave:()=>this.handleEmojiOver(),style:{width:this.props.emojiButtonSize,height:this.props.emojiButtonSize,fontSize:this.props.emojiSize,lineHeight:0},children:[pi("div",{"aria-hidden":"true",class:"background",style:{borderRadius:this.props.emojiButtonRadius,backgroundColor:this.props.emojiButtonColors?this.props.emojiButtonColors[(n-1)%this.props.emojiButtonColors.length]:void 0}}),pi(Vi,{emoji:e,set:this.props.set,size:this.props.emojiSize,skin:o,spritesheet:!0,getSpritesheetURL:this.props.getSpritesheetURL})]})},u)}renderSearch(){const e="none"==this.props.previewPosition||"search"==this.props.skinTonePosition;return pi("div",{children:[pi("div",{class:"spacer"}),pi("div",{class:"flex flex-middle",children:[pi("div",{class:"search relative flex-grow",children:[pi("input",{type:"search",ref:this.refs.searchInput,placeholder:ji.search,onClick:this.handleSearchClick,onInput:this.handleSearchInput,onKeyDown:this.handleSearchKeyDown,autoComplete:"off"}),pi("span",{class:"icon loupe flex",children:zi.search.loupe}),this.state.searchResults&&pi("button",{title:"Clear","aria-label":"Clear",type:"button",class:"icon delete flex",onClick:this.clearSearch,onMouseDown:this.preventDefault,children:zi.search.delete})]}),e&&this.renderSkinToneButton()]})]})}renderSearchResults(){const{searchResults:e}=this.state;return e?pi("div",{class:"category",ref:this.refs.search,children:[pi("div",{class:`sticky padding-small align-${this.dir[0]}`,children:ji.categories.search}),pi("div",{children:e.length?e.map(((t,n)=>pi("div",{class:"flex",children:t.map(((t,r)=>this.renderEmojiButton(t,{pos:[n,r],posinset:n*this.props.perLine+r+1,grid:e})))}))):pi("div",{class:`padding-small align-${this.dir[0]}`,children:this.props.onAddCustomEmoji&&pi("a",{onClick:this.props.onAddCustomEmoji,children:ji.add_custom})})})]}):null}renderCategories(){const{categories:e}=Ti,t=!!this.state.searchResults,n=this.getPerLine();return pi("div",{style:{visibility:t?"hidden":void 0,display:t?"none":void 0,height:"100%"},children:e.map((e=>{const{root:t,rows:r}=this.refs.categories.get(e.id);return pi("div",{"data-id":e.target?e.target.id:e.id,class:"category",ref:t,children:[pi("div",{class:`sticky padding-small align-${this.dir[0]}`,children:e.name||ji.categories[e.id]}),pi("div",{class:"relative",style:{height:r.length*this.props.emojiButtonSize},children:r.map(((t,r)=>{const i=t.index-t.index%10,o=this.state.visibleRows[i],s="current"in t?t:void 0;if(!o&&!s)return null;const a=r*n,l=a+n,c=e.emojis.slice(a,l);return c.length{if(!e)return pi("div",{style:{width:this.props.emojiButtonSize,height:this.props.emojiButtonSize}});const r=Di.get(e);return this.renderEmojiButton(r,{pos:[t.index,n],posinset:t.posinset+n,grid:this.grid})}))},t.index)}))})]})}))})}renderSkinToneButton(){return"none"==this.props.skinTonePosition?null:pi("div",{class:"flex flex-auto flex-center flex-middle",style:{position:"relative",width:this.props.emojiButtonSize,height:this.props.emojiButtonSize},children:pi("button",{type:"button",ref:this.refs.skinToneButton,class:"skin-tone-button flex flex-auto flex-center flex-middle","aria-selected":this.state.showSkins?"":void 0,"aria-label":ji.skins.choose,title:ji.skins.choose,onClick:this.openSkins,style:{width:this.props.emojiSize,height:this.props.emojiSize},children:pi("span",{class:`skin-tone skin-tone-${this.state.skin}`})})})}renderLiveRegion(){const e=this.getEmojiByPos(this.state.pos);return pi("div",{"aria-live":"polite",class:"sr-only",children:e?e.name:""})}renderSkins(){const e=this.refs.skinToneButton.current.getBoundingClientRect(),t=this.base.getBoundingClientRect(),n={};return"ltr"==this.dir?n.right=t.right-e.right-3:n.left=e.left-t.left-3,"bottom"==this.props.previewPosition&&"preview"==this.props.skinTonePosition?n.bottom=t.bottom-e.top+6:(n.top=e.bottom-t.top+3,n.bottom="auto"),pi("div",{ref:this.refs.menu,role:"radiogroup",dir:this.dir,"aria-label":ji.skins.choose,class:"menu hidden","data-position":n.top?"top":"bottom",style:n,children:[...Array(6).keys()].map((e=>{const t=e+1,n=this.state.skin==t;return pi("div",{children:[pi("input",{type:"radio",name:"skin-tone",value:t,"aria-label":ji.skins[t],ref:n?this.refs.skinToneRadio:null,defaultChecked:n,onChange:()=>this.handleSkinMouseOver(t),onKeyDown:e=>{"Enter"!=e.code&&"Space"!=e.code&&"Tab"!=e.code||(e.preventDefault(),this.handleSkinClick(t))}}),pi("button",{"aria-hidden":"true",tabindex:"-1",onClick:()=>this.handleSkinClick(t),onMouseEnter:()=>this.handleSkinMouseOver(t),onMouseLeave:()=>this.handleSkinMouseOver(),class:"option flex flex-grow flex-middle",children:[pi("span",{class:`skin-tone skin-tone-${t}`}),pi("span",{class:"margin-small-lr",children:ji.skins[t]})]})]})}))})}render(){const e=this.props.perLine*this.props.emojiButtonSize;return pi("section",{id:"root",class:"flex flex-column",dir:this.dir,style:{width:this.props.dynamicWidth?"100%":`calc(${e}px + (var(--padding) + var(--sidebar-width)))`},"data-emoji-set":this.props.set,"data-theme":this.state.theme,"data-menu":this.state.showSkins?"":void 0,children:["top"==this.props.previewPosition&&this.renderPreview(),"top"==this.props.navPosition&&this.renderNav(),"sticky"==this.props.searchPosition&&pi("div",{class:"padding-lr",children:this.renderSearch()}),pi("div",{ref:this.refs.scroll,class:"scroll flex-grow padding-lr",children:pi("div",{style:{width:this.props.dynamicWidth?"100%":e,height:"100%"},children:["static"==this.props.searchPosition&&this.renderSearch(),this.renderSearchResults(),this.renderCategories()]})}),"bottom"==this.props.navPosition&&this.renderNav(),"bottom"==this.props.previewPosition&&this.renderPreview(),this.state.showSkins&&this.renderSkins(),this.renderLiveRegion()]})}constructor(e){super(),Lr(this,"darkMediaCallback",(()=>{"auto"==this.props.theme&&this.setState({theme:this.darkMedia.matches?"dark":"light"})})),Lr(this,"handleClickOutside",(e=>{const{element:t}=this.props;e.target!=t&&(this.state.showSkins&&this.closeSkins(),this.props.onClickOutside&&this.props.onClickOutside(e))})),Lr(this,"handleBaseClick",(e=>{this.state.showSkins&&(e.target.closest(".menu")||(e.preventDefault(),e.stopImmediatePropagation(),this.closeSkins()))})),Lr(this,"handleBaseKeydown",(e=>{this.state.showSkins&&"Escape"==e.key&&(e.preventDefault(),e.stopImmediatePropagation(),this.closeSkins())})),Lr(this,"handleSearchClick",(()=>{this.getEmojiByPos(this.state.pos)&&this.setState({pos:[-1,-1]})})),Lr(this,"handleSearchInput",(async()=>{const e=this.refs.searchInput.current;if(!e)return;const{value:t}=e,n=await Di.search(t),r=()=>{this.refs.scroll.current&&(this.refs.scroll.current.scrollTop=0)};if(!n)return this.setState({searchResults:n,pos:[-1,-1]},r);const i=e.selectionStart==e.value.length?[0,0]:[-1,-1],o=[];o.setsize=n.length;let s=null;for(let e of n)o.length&&s.length!=this.getPerLine()||(s=[],s.__categoryId="search",s.__index=o.length,o.push(s)),s.push(e);this.ignoreMouse(),this.setState({searchResults:o,pos:i},r)})),Lr(this,"handleSearchKeyDown",(e=>{const t=e.currentTarget;switch(e.stopImmediatePropagation(),e.key){case"ArrowLeft":this.navigate({e,input:t,left:!0});break;case"ArrowRight":this.navigate({e,input:t,right:!0});break;case"ArrowUp":this.navigate({e,input:t,up:!0});break;case"ArrowDown":this.navigate({e,input:t,down:!0});break;case"Enter":e.preventDefault(),this.handleEmojiClick({e,pos:this.state.pos});break;case"Escape":e.preventDefault(),this.state.searchResults?this.clearSearch():this.unfocusSearch()}})),Lr(this,"clearSearch",(()=>{const e=this.refs.searchInput.current;e&&(e.value="",e.focus(),this.handleSearchInput())})),Lr(this,"handleCategoryClick",(({category:e,i:t})=>{this.scrollTo(0==t?{row:-1}:{categoryId:e.id})})),Lr(this,"openSkins",(e=>{const{currentTarget:t}=e,n=t.getBoundingClientRect();this.setState({showSkins:n},(async()=>{await async function(e=1){for(let t in[...Array(e).keys()])await new Promise(requestAnimationFrame)}(2);const e=this.refs.menu.current;e&&(e.classList.remove("hidden"),this.refs.skinToneRadio.current.focus(),this.base.addEventListener("click",this.handleBaseClick,!0),this.base.addEventListener("keydown",this.handleBaseKeydown,!0))}))})),this.observers=[],this.state={pos:[-1,-1],perLine:this.initDynamicPerLine(e),visibleRows:{0:!0},...this.getInitialState(e)}}}class Eo extends qi{async connectedCallback(){const e=Ri(this.props,Si,this);e.element=this,e.ref=e=>{this.component=e},await Ii(e),this.disconnected||di(pi(To,{...e}),this.shadowRoot)}constructor(e){super(e,{styles:Ar(Po)})}}Lr(Eo,"Props",Si),"undefined"==typeof customElements||customElements.get("em-emoji-picker")||customElements.define("em-emoji-picker",Eo);var Po={};Po=':host {\n width: min-content;\n height: 435px;\n min-height: 230px;\n border-radius: var(--border-radius);\n box-shadow: var(--shadow);\n --border-radius: 10px;\n --category-icon-size: 18px;\n --font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;\n --font-size: 15px;\n --preview-placeholder-size: 21px;\n --preview-title-size: 1.1em;\n --preview-subtitle-size: .9em;\n --shadow-color: 0deg 0% 0%;\n --shadow: .3px .5px 2.7px hsl(var(--shadow-color) / .14), .4px .8px 1px -3.2px hsl(var(--shadow-color) / .14), 1px 2px 2.5px -4.5px hsl(var(--shadow-color) / .14);\n display: flex;\n}\n\n[data-theme="light"] {\n --em-rgb-color: var(--rgb-color, 34, 36, 39);\n --em-rgb-accent: var(--rgb-accent, 34, 102, 237);\n --em-rgb-background: var(--rgb-background, 255, 255, 255);\n --em-rgb-input: var(--rgb-input, 255, 255, 255);\n --em-color-border: var(--color-border, rgba(0, 0, 0, .05));\n --em-color-border-over: var(--color-border-over, rgba(0, 0, 0, .1));\n}\n\n[data-theme="dark"] {\n --em-rgb-color: var(--rgb-color, 222, 222, 221);\n --em-rgb-accent: var(--rgb-accent, 58, 130, 247);\n --em-rgb-background: var(--rgb-background, 21, 22, 23);\n --em-rgb-input: var(--rgb-input, 0, 0, 0);\n --em-color-border: var(--color-border, rgba(255, 255, 255, .1));\n --em-color-border-over: var(--color-border-over, rgba(255, 255, 255, .2));\n}\n\n#root {\n --color-a: rgb(var(--em-rgb-color));\n --color-b: rgba(var(--em-rgb-color), .65);\n --color-c: rgba(var(--em-rgb-color), .45);\n --padding: 12px;\n --padding-small: calc(var(--padding) / 2);\n --sidebar-width: 16px;\n --duration: 225ms;\n --duration-fast: 125ms;\n --duration-instant: 50ms;\n --easing: cubic-bezier(.4, 0, .2, 1);\n width: 100%;\n text-align: left;\n border-radius: var(--border-radius);\n background-color: rgb(var(--em-rgb-background));\n position: relative;\n}\n\n@media (prefers-reduced-motion) {\n #root {\n --duration: 0;\n --duration-fast: 0;\n --duration-instant: 0;\n }\n}\n\n#root[data-menu] button {\n cursor: auto;\n}\n\n#root[data-menu] .menu button {\n cursor: pointer;\n}\n\n:host, #root, input, button {\n color: rgb(var(--em-rgb-color));\n font-family: var(--font-family);\n font-size: var(--font-size);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n line-height: normal;\n}\n\n*, :before, :after {\n box-sizing: border-box;\n min-width: 0;\n margin: 0;\n padding: 0;\n}\n\n.relative {\n position: relative;\n}\n\n.flex {\n display: flex;\n}\n\n.flex-auto {\n flex: none;\n}\n\n.flex-center {\n justify-content: center;\n}\n\n.flex-column {\n flex-direction: column;\n}\n\n.flex-grow {\n flex: auto;\n}\n\n.flex-middle {\n align-items: center;\n}\n\n.flex-wrap {\n flex-wrap: wrap;\n}\n\n.padding {\n padding: var(--padding);\n}\n\n.padding-t {\n padding-top: var(--padding);\n}\n\n.padding-lr {\n padding-left: var(--padding);\n padding-right: var(--padding);\n}\n\n.padding-r {\n padding-right: var(--padding);\n}\n\n.padding-small {\n padding: var(--padding-small);\n}\n\n.padding-small-b {\n padding-bottom: var(--padding-small);\n}\n\n.padding-small-lr {\n padding-left: var(--padding-small);\n padding-right: var(--padding-small);\n}\n\n.margin {\n margin: var(--padding);\n}\n\n.margin-r {\n margin-right: var(--padding);\n}\n\n.margin-l {\n margin-left: var(--padding);\n}\n\n.margin-small-l {\n margin-left: var(--padding-small);\n}\n\n.margin-small-lr {\n margin-left: var(--padding-small);\n margin-right: var(--padding-small);\n}\n\n.align-l {\n text-align: left;\n}\n\n.align-r {\n text-align: right;\n}\n\n.color-a {\n color: var(--color-a);\n}\n\n.color-b {\n color: var(--color-b);\n}\n\n.color-c {\n color: var(--color-c);\n}\n\n.ellipsis {\n white-space: nowrap;\n max-width: 100%;\n width: auto;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.sr-only {\n width: 1px;\n height: 1px;\n position: absolute;\n top: auto;\n left: -10000px;\n overflow: hidden;\n}\n\na {\n cursor: pointer;\n color: rgb(var(--em-rgb-accent));\n}\n\na:hover {\n text-decoration: underline;\n}\n\n.spacer {\n height: 10px;\n}\n\n[dir="rtl"] .scroll {\n padding-left: 0;\n padding-right: var(--padding);\n}\n\n.scroll {\n padding-right: 0;\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.scroll::-webkit-scrollbar {\n width: var(--sidebar-width);\n height: var(--sidebar-width);\n}\n\n.scroll::-webkit-scrollbar-track {\n border: 0;\n}\n\n.scroll::-webkit-scrollbar-button {\n width: 0;\n height: 0;\n display: none;\n}\n\n.scroll::-webkit-scrollbar-corner {\n background-color: rgba(0, 0, 0, 0);\n}\n\n.scroll::-webkit-scrollbar-thumb {\n min-height: 20%;\n min-height: 65px;\n border: 4px solid rgb(var(--em-rgb-background));\n border-radius: 8px;\n}\n\n.scroll::-webkit-scrollbar-thumb:hover {\n background-color: var(--em-color-border-over) !important;\n}\n\n.scroll:hover::-webkit-scrollbar-thumb {\n background-color: var(--em-color-border);\n}\n\n.sticky {\n z-index: 1;\n background-color: rgba(var(--em-rgb-background), .9);\n -webkit-backdrop-filter: blur(4px);\n backdrop-filter: blur(4px);\n font-weight: 500;\n position: sticky;\n top: -1px;\n}\n\n[dir="rtl"] .search input[type="search"] {\n padding: 10px 2.2em 10px 2em;\n}\n\n[dir="rtl"] .search .loupe {\n left: auto;\n right: .7em;\n}\n\n[dir="rtl"] .search .delete {\n left: .7em;\n right: auto;\n}\n\n.search {\n z-index: 2;\n position: relative;\n}\n\n.search input, .search button {\n font-size: calc(var(--font-size) - 1px);\n}\n\n.search input[type="search"] {\n width: 100%;\n background-color: var(--em-color-border);\n transition-duration: var(--duration);\n transition-property: background-color, box-shadow;\n transition-timing-function: var(--easing);\n border: 0;\n border-radius: 10px;\n outline: 0;\n padding: 10px 2em 10px 2.2em;\n display: block;\n}\n\n.search input[type="search"]::-ms-input-placeholder {\n color: inherit;\n opacity: .6;\n}\n\n.search input[type="search"]::placeholder {\n color: inherit;\n opacity: .6;\n}\n\n.search input[type="search"], .search input[type="search"]::-webkit-search-decoration, .search input[type="search"]::-webkit-search-cancel-button, .search input[type="search"]::-webkit-search-results-button, .search input[type="search"]::-webkit-search-results-decoration {\n -webkit-appearance: none;\n -ms-appearance: none;\n appearance: none;\n}\n\n.search input[type="search"]:focus {\n background-color: rgb(var(--em-rgb-input));\n box-shadow: inset 0 0 0 1px rgb(var(--em-rgb-accent)), 0 1px 3px rgba(65, 69, 73, .2);\n}\n\n.search .icon {\n z-index: 1;\n color: rgba(var(--em-rgb-color), .7);\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n}\n\n.search .loupe {\n pointer-events: none;\n left: .7em;\n}\n\n.search .delete {\n right: .7em;\n}\n\nsvg {\n fill: currentColor;\n width: 1em;\n height: 1em;\n}\n\nbutton {\n -webkit-appearance: none;\n -ms-appearance: none;\n appearance: none;\n cursor: pointer;\n color: currentColor;\n background-color: rgba(0, 0, 0, 0);\n border: 0;\n}\n\n#nav {\n z-index: 2;\n padding-top: 12px;\n padding-bottom: 12px;\n padding-right: var(--sidebar-width);\n position: relative;\n}\n\n#nav button {\n color: var(--color-b);\n transition: color var(--duration) var(--easing);\n}\n\n#nav button:hover {\n color: var(--color-a);\n}\n\n#nav svg, #nav img {\n width: var(--category-icon-size);\n height: var(--category-icon-size);\n}\n\n#nav[dir="rtl"] .bar {\n left: auto;\n right: 0;\n}\n\n#nav .bar {\n width: 100%;\n height: 3px;\n background-color: rgb(var(--em-rgb-accent));\n transition: transform var(--duration) var(--easing);\n border-radius: 3px 3px 0 0;\n position: absolute;\n bottom: -12px;\n left: 0;\n}\n\n#nav button[aria-selected] {\n color: rgb(var(--em-rgb-accent));\n}\n\n#preview {\n z-index: 2;\n padding: calc(var(--padding) + 4px) var(--padding);\n padding-right: var(--sidebar-width);\n position: relative;\n}\n\n#preview .preview-placeholder {\n font-size: var(--preview-placeholder-size);\n}\n\n#preview .preview-title {\n font-size: var(--preview-title-size);\n}\n\n#preview .preview-subtitle {\n font-size: var(--preview-subtitle-size);\n}\n\n#nav:before, #preview:before {\n content: "";\n height: 2px;\n position: absolute;\n left: 0;\n right: 0;\n}\n\n#nav[data-position="top"]:before, #preview[data-position="top"]:before {\n background: linear-gradient(to bottom, var(--em-color-border), transparent);\n top: 100%;\n}\n\n#nav[data-position="bottom"]:before, #preview[data-position="bottom"]:before {\n background: linear-gradient(to top, var(--em-color-border), transparent);\n bottom: 100%;\n}\n\n.category:last-child {\n min-height: calc(100% + 1px);\n}\n\n.category button {\n font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;\n position: relative;\n}\n\n.category button > * {\n position: relative;\n}\n\n.category button .background {\n opacity: 0;\n background-color: var(--em-color-border);\n transition: opacity var(--duration-fast) var(--easing) var(--duration-instant);\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n}\n\n.category button:hover .background {\n transition-duration: var(--duration-instant);\n transition-delay: 0s;\n}\n\n.category button[aria-selected] .background {\n opacity: 1;\n}\n\n.category button[data-keyboard] .background {\n transition: none;\n}\n\n.row {\n width: 100%;\n position: absolute;\n top: 0;\n left: 0;\n}\n\n.skin-tone-button {\n border: 1px solid rgba(0, 0, 0, 0);\n border-radius: 100%;\n}\n\n.skin-tone-button:hover {\n border-color: var(--em-color-border);\n}\n\n.skin-tone-button:active .skin-tone {\n transform: scale(.85) !important;\n}\n\n.skin-tone-button .skin-tone {\n transition: transform var(--duration) var(--easing);\n}\n\n.skin-tone-button[aria-selected] {\n background-color: var(--em-color-border);\n border-top-color: rgba(0, 0, 0, .05);\n border-bottom-color: rgba(0, 0, 0, 0);\n border-left-width: 0;\n border-right-width: 0;\n}\n\n.skin-tone-button[aria-selected] .skin-tone {\n transform: scale(.9);\n}\n\n.menu {\n z-index: 2;\n white-space: nowrap;\n border: 1px solid var(--em-color-border);\n background-color: rgba(var(--em-rgb-background), .9);\n -webkit-backdrop-filter: blur(4px);\n backdrop-filter: blur(4px);\n transition-property: opacity, transform;\n transition-duration: var(--duration);\n transition-timing-function: var(--easing);\n border-radius: 10px;\n padding: 4px;\n position: absolute;\n box-shadow: 1px 1px 5px rgba(0, 0, 0, .05);\n}\n\n.menu.hidden {\n opacity: 0;\n}\n\n.menu[data-position="bottom"] {\n transform-origin: 100% 100%;\n}\n\n.menu[data-position="bottom"].hidden {\n transform: scale(.9)rotate(-3deg)translateY(5%);\n}\n\n.menu[data-position="top"] {\n transform-origin: 100% 0;\n}\n\n.menu[data-position="top"].hidden {\n transform: scale(.9)rotate(3deg)translateY(-5%);\n}\n\n.menu input[type="radio"] {\n clip: rect(0 0 0 0);\n width: 1px;\n height: 1px;\n border: 0;\n margin: 0;\n padding: 0;\n position: absolute;\n overflow: hidden;\n}\n\n.menu input[type="radio"]:checked + .option {\n box-shadow: 0 0 0 2px rgb(var(--em-rgb-accent));\n}\n\n.option {\n width: 100%;\n border-radius: 6px;\n padding: 4px 6px;\n}\n\n.option:hover {\n color: #fff;\n background-color: rgb(var(--em-rgb-accent));\n}\n\n.skin-tone {\n width: 16px;\n height: 16px;\n border-radius: 100%;\n display: inline-block;\n position: relative;\n overflow: hidden;\n}\n\n.skin-tone:after {\n content: "";\n mix-blend-mode: overlay;\n background: linear-gradient(rgba(255, 255, 255, .2), rgba(0, 0, 0, 0));\n border: 1px solid rgba(0, 0, 0, .8);\n border-radius: 100%;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n box-shadow: inset 0 -2px 3px #000, inset 0 1px 2px #fff;\n}\n\n.skin-tone-1 {\n background-color: #ffc93a;\n}\n\n.skin-tone-2 {\n background-color: #ffdab7;\n}\n\n.skin-tone-3 {\n background-color: #e7b98f;\n}\n\n.skin-tone-4 {\n background-color: #c88c61;\n}\n\n.skin-tone-5 {\n background-color: #a46134;\n}\n\n.skin-tone-6 {\n background-color: #5d4437;\n}\n\n[data-index] {\n justify-content: space-between;\n}\n\n[data-emoji-set="twitter"] .skin-tone:after {\n box-shadow: none;\n border-color: rgba(0, 0, 0, .5);\n}\n\n[data-emoji-set="twitter"] .skin-tone-1 {\n background-color: #fade72;\n}\n\n[data-emoji-set="twitter"] .skin-tone-2 {\n background-color: #f3dfd0;\n}\n\n[data-emoji-set="twitter"] .skin-tone-3 {\n background-color: #eed3a8;\n}\n\n[data-emoji-set="twitter"] .skin-tone-4 {\n background-color: #cfad8d;\n}\n\n[data-emoji-set="twitter"] .skin-tone-5 {\n background-color: #a8805d;\n}\n\n[data-emoji-set="twitter"] .skin-tone-6 {\n background-color: #765542;\n}\n\n[data-emoji-set="google"] .skin-tone:after {\n box-shadow: inset 0 0 2px 2px rgba(0, 0, 0, .4);\n}\n\n[data-emoji-set="google"] .skin-tone-1 {\n background-color: #f5c748;\n}\n\n[data-emoji-set="google"] .skin-tone-2 {\n background-color: #f1d5aa;\n}\n\n[data-emoji-set="google"] .skin-tone-3 {\n background-color: #d4b48d;\n}\n\n[data-emoji-set="google"] .skin-tone-4 {\n background-color: #aa876b;\n}\n\n[data-emoji-set="google"] .skin-tone-5 {\n background-color: #916544;\n}\n\n[data-emoji-set="google"] .skin-tone-6 {\n background-color: #61493f;\n}\n\n[data-emoji-set="facebook"] .skin-tone:after {\n border-color: rgba(0, 0, 0, .4);\n box-shadow: inset 0 -2px 3px #000, inset 0 1px 4px #fff;\n}\n\n[data-emoji-set="facebook"] .skin-tone-1 {\n background-color: #f5c748;\n}\n\n[data-emoji-set="facebook"] .skin-tone-2 {\n background-color: #f1d5aa;\n}\n\n[data-emoji-set="facebook"] .skin-tone-3 {\n background-color: #d4b48d;\n}\n\n[data-emoji-set="facebook"] .skin-tone-4 {\n background-color: #aa876b;\n}\n\n[data-emoji-set="facebook"] .skin-tone-5 {\n background-color: #916544;\n}\n\n[data-emoji-set="facebook"] .skin-tone-6 {\n background-color: #61493f;\n}\n\n';var Mo=e=>{Object.assign(e,{show(){this.openValue=!0},hide(){this.openValue=!1},toggle(){this.openValue=!this.openValue},setupFloatingUI(e){var{trigger:t,popover:n,strategy:r}=e;this.floatingUICleanup=function(e,t,n,r){void 0===r&&(r={});const{ancestorScroll:i=!0,ancestorResize:o=!0,elementResize:s="function"==typeof ResizeObserver,layoutShift:a="function"==typeof IntersectionObserver,animationFrame:l=!1}=r,c=ur(e),u=i||o?[...c?ar(c):[],...ar(t)]:[];u.forEach((e=>{i&&e.addEventListener("scroll",n,{passive:!0}),o&&e.addEventListener("resize",n)}));const h=c&&a?function(e,t){let n,r=null;const i=Nn(e);function o(){var e;clearTimeout(n),null==(e=r)||e.disconnect(),r=null}return function s(a,l){void 0===a&&(a=!1),void 0===l&&(l=1),o();const c=e.getBoundingClientRect(),{left:u,top:h,width:d,height:f}=c;if(a||t(),!d||!f)return;const p={rootMargin:-dn(h)+"px "+-dn(i.clientWidth-(u+d))+"px "+-dn(i.clientHeight-(h+f))+"px "+-dn(u)+"px",threshold:un(0,cn(1,l))||1};let m=!0;function g(t){const r=t[0].intersectionRatio;if(r!==l){if(!m)return s();r?s(!1,r):n=setTimeout((()=>{s(!1,1e-7)}),1e3)}1!==r||xr(c,e.getBoundingClientRect())||s(),m=!1}try{r=new IntersectionObserver(g,{...p,root:i.ownerDocument})}catch(e){r=new IntersectionObserver(g,p)}r.observe(e)}(!0),o}(c,n):null;let d,f=-1,p=null;s&&(p=new ResizeObserver((e=>{let[r]=e;r&&r.target===c&&p&&(p.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame((()=>{var e;null==(e=p)||e.observe(t)}))),n()})),c&&!l&&p.observe(c),p.observe(t));let m=l?pr(e):null;return l&&function t(){const r=pr(e);m&&!xr(m,r)&&n(),m=r,d=requestAnimationFrame(t)}(),n(),()=>{var e;u.forEach((e=>{i&&e.removeEventListener("scroll",n),o&&e.removeEventListener("resize",n)})),null==h||h(),null==(e=p)||e.disconnect(),p=null,l&&cancelAnimationFrame(d)}}(t,n,(()=>{((e,t,n)=>{const r=new Map,i={platform:Cr,...n},o={...i.platform,_c:r};return(async(e,t,n)=>{const{placement:r="bottom",strategy:i="absolute",middleware:o=[],platform:s}=n,a=o.filter(Boolean),l=await(null==s.isRTL?void 0:s.isRTL(t));let c=await s.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:h}=Ln(c,r,l),d=r,f={},p=0;for(let n=0;n{var{x:t,y:r,strategy:i}=e,o={left:"".concat(t,"px"),top:"".concat(r,"px"),position:i};Object.assign(n.style,o)}))}))},openValueChanged(){this.disabledValue||(this.openValue?(this.popoverTarget.showPopover(),this.popoverTarget.setAttribute("aria-expanded","true"),this.onPopoverOpened&&this.onPopoverOpened()):(this.popoverTarget.hidePopover(),this.popoverTarget.removeAttribute("aria-expanded"),this.onPopoverClosed&&this.onPopoverClosed()))}})};function Ao(e,t){for(var n=0;n{var[n,r]=e;t.searchParams.append(n,r)})),yield fetch(t,{method:"GET",headers:Zt.headers})})),function(e){return o.apply(this,arguments)})},{key:"create",value:(i=Do((function*(e){var t=yield fetch(this.url,{method:"POST",headers:{Authorization:"Bearer ".concat(Zt.business.id)},body:e});return new ie(t.ok,t)})),function(e){return i.apply(this,arguments)})},{key:"markAsSeen",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=e?this.url+"/".concat(e):this.url+"/seen";fetch(t,{method:"PATCH",headers:Zt.headers,body:JSON.stringify({session:Zt.session})})}},{key:"url",get:function(){return e.endpoint.replace(":id",this.webchatId)}}],r=[{key:"endpoint",get:function(){return I.endpoint("public/webchats/:id/messages")}}],n&&No(t.prototype,n),r&&No(t,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Vo(e,t){for(var n=0;n{this.webSocket.send(JSON.stringify(i))}))}},{key:"onMessage",value:function(e){this.webSocket.addEventListener("message",(t=>{var n=JSON.parse(t.data),{type:r,message:i}=n;this.ignoredEvents.includes(r)||e(i)}))}},{key:"webSocket",get:function(){return e.webSocket?e.webSocket:e.webSocket=new WebSocket(I.actionCableUrl)}},{key:"ignoredEvents",get:function(){return["ping","confirm_subscription","welcome"]}}])&&Vo(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Ho(e,t){for(var n=0;n{"message"===t.type&&e(t)}))}},{key:"onReaction",value:function(e){qo(Ko(s.prototype),"onMessage",this).call(this,(t=>{"reaction.create"!==t.type&&"reaction.destroy"!==t.type||e(t)}))}},{key:"onTypingStart",value:function(e){qo(Ko(s.prototype),"onMessage",this).call(this,(t=>{"started_typing"===t.type&&e(t)}))}},{key:"updateSubscriptionWith",value:function(e){this.unsubscribe(),setTimeout((()=>{this.conversation=e,this.subscribe()}),1e3)}}])&&Ho(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),s}($o);const Jo=Wo;function Zo(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Go(e){for(var t=1;t{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})}));var t=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout((()=>{this.clearTypingIndicator()}),t)}},{key:"resetTypingIndicatorTimer",value:function(){if(this.typingIndicatorVisible){clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout);var e=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout((()=>{this.clearTypingIndicator()}),e)}}},{key:"clearTypingIndicator",value:function(){this.hasTypingIndicatorTarget&&this.typingIndicatorTarget.remove(),this.typingIndicatorVisible=!1,clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout)}},{key:"onMessageInputChange",value:function(){this.resizeInput(),clearTimeout(this.typingIndicatorTimeout),this.hasSentTypingIndicator||(this.webChatChannel.startTypingIndicator(),this.hasSentTypingIndicator=!0),this.typingIndicatorTimeout=setTimeout((()=>{this.hasSentTypingIndicator=!1}),3e3)}},{key:"onOutboundMessageSent",value:function(e){var{data:t}=e,n={"message:sent":e=>{var t=(new DOMParser).parseFromString(e.element,"text/html").body.firstElementChild;this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(t,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(t),t.scrollIntoView({behavior:"instant"})},"message:failed":e=>{var t;null===(t=this.messagesContainerTarget.querySelector("#".concat(e.id)))||void 0===t||t.classList.add("failed")}};n[t.type]?n[t.type](t):console.log("Unhandled message event: ".concat(t.type))}},{key:"onScroll",value:(o=Qo((function*(){if(!(this.messagesContainerTarget.scrollTop>300||!this.nextPageValue||this.fetchingNextPage)){this.fetchingNextPage=!0;var e=yield this.messagesAPI.index({page:this.nextPageValue,session:Zt.session}),{next:t,messages:n}=yield e.json();this.nextPageValue=t,this.oldScrollHeight=this.messagesContainerTarget.scrollHeight,n.forEach((e=>{var{body:t,attachments:n}=e,r=document.createElement("div");r.innerHTML=t;var i=this.messageTemplateTarget.cloneNode(!0);i.setAttribute("data-hellotext--webchat-target","message"),i.style.removeProperty("display"),i.querySelector("[data-body]").innerHTML=r.innerHTML,"received"===e.state?i.classList.add("received"):i.classList.remove("received"),n&&n.forEach((e=>{var t=this.attachmentImageTarget.cloneNode(!0);t.removeAttribute("data-hellotext--webchat-target"),t.src=e,t.style.display="block",i.querySelector("[data-attachment-container]").appendChild(t)})),i.setAttribute("data-body",t),this.messagesContainerTarget.prepend(i)})),this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight-this.oldScrollHeight,behavior:"instant"}),this.fetchingNextPage=!1}})),function(){return o.apply(this,arguments)})},{key:"onClickOutside",value:function(e){A.behaviour===M.POPOVER&&this.openValue&&e.target.nodeType&&!1===this.element.contains(e.target)&&(this.openValue=!1)}},{key:"closePopover",value:function(){this.popoverTarget.classList.add(...this.fadeOutClasses),setTimeout((()=>{this.openValue=!1}),250)}},{key:"onPopoverOpened",value:function(){this.popoverTarget.classList.remove(...this.fadeOutClasses),this.onMobile||this.inputTarget.focus(),this.scrolled||(requestAnimationFrame((()=>{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})})),this.scrolled=!0),Zt.eventEmitter.dispatch("webchat:opened"),localStorage.setItem("hellotext--webchat--".concat(this.idValue),"opened"),this.hasTeaserTarget&&this.teaserTarget.classList.add("hidden"),"none"!==this.unreadCounterTarget.style.display&&(this.unreadCounterTarget.style.display="none",this.unreadCounterTarget.innerText="0",this.messagesAPI.markAsSeen())}},{key:"onPopoverClosed",value:function(){Zt.eventEmitter.dispatch("webchat:closed"),localStorage.setItem("hellotext--webchat--".concat(this.idValue),"closed"),this.hasTeaserTarget&&this.teaserValue.body&&this.teaserTarget.classList.remove("hidden")}},{key:"onMessageReaction",value:function(e){var{message:t,reaction:n,type:r}=e,i=this.messageTargets.find((e=>e.dataset.id===t)).querySelector("[data-reactions]");if("reaction.destroy"===r)return i.querySelector('[data-id="'.concat(n.id,'"]')).remove();if(i.querySelector('[data-id="'.concat(n.id,'"]')))i.querySelector('[data-id="'.concat(n.id,'"]')).innerText=n.emoji;else{var o=document.createElement("span");o.innerText=n.emoji,o.setAttribute("data-id",n.id),i.appendChild(o)}}},{key:"onMessageReceived",value:function(e){var{id:t,body:n,attachments:r}=e;if(e.carousel)return this.insertCarouselMessage(e);var i=document.createElement("div");i.innerHTML=n;var o=this.messageTemplateTarget.cloneNode(!0);if(o.style.display="flex",o.querySelector("[data-body]").innerHTML=i.innerHTML,o.setAttribute("data-hellotext--webchat-target","message"),r&&r.forEach((e=>{var t=this.attachmentImageTarget.cloneNode(!0);t.src=e,t.style.display="block",o.querySelector("[data-attachment-container]").appendChild(t)})),this.clearTypingIndicator(),this.messagesContainerTarget.appendChild(o),Zt.eventEmitter.dispatch("webchat:message:received",Go(Go({},e),{},{body:o.querySelector("[data-body]").innerText})),o.scrollIntoView({behavior:"smooth"}),this.openValue)this.messagesAPI.markAsSeen(t);else{this.unreadCounterTarget.style.display="flex";var s=(parseInt(this.unreadCounterTarget.innerText)||0)+1;this.unreadCounterTarget.innerText=s>99?"99+":s}}},{key:"insertCarouselMessage",value:function(e){var t,n=e.html,r=(new DOMParser).parseFromString(n,"text/html").body.firstElementChild;if(this.clearTypingIndicator(),this.messagesContainerTarget.appendChild(r),r.scrollIntoView({behavior:"smooth"}),Zt.eventEmitter.dispatch("webchat:message:received",Go(Go({},e),{},{body:(null===(t=r.querySelector("[data-body]"))||void 0===t?void 0:t.innerText)||""})),this.openValue)this.messagesAPI.markAsSeen(e.id);else{this.unreadCounterTarget.style.display="flex";var i=(parseInt(this.unreadCounterTarget.innerText)||0)+1;this.unreadCounterTarget.innerText=i>99?"99+":i}}},{key:"resizeInput",value:function(){this.inputTarget.style.height="auto";var e=this.inputTarget.scrollHeight;this.inputTarget.style.height="".concat(Math.min(e,96),"px")}},{key:"sendQuickReplyMessage",value:(i=Qo((function*(e){var t,{detail:{id:n,product:r,buttonId:i,body:o,cardElement:s}}=e,a=new FormData;a.append("message[body]",o),a.append("message[replied_to]",n),a.append("message[product]",r),a.append("message[button]",i),a.append("session",Zt.session),a.append("locale",j.toString());var l=this.buildMessageElement(),c=null===(t=s.querySelector("img"))||void 0===t?void 0:t.cloneNode(!0);l.querySelector("[data-body]").innerText=o,c&&(c.removeAttribute("width"),c.removeAttribute("height"),l.querySelector("[data-attachment-container]").appendChild(c)),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(l,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(l),l.scrollIntoView({behavior:"smooth"}),this.broadcastChannel.postMessage({type:"message:sent",element:l.outerHTML});var u=yield this.messagesAPI.create(a);if(u.failed)return clearTimeout(this.optimisticTypingTimeout),this.broadcastChannel.postMessage({type:"message:failed",id:l.id}),l.classList.add("failed");var h=yield u.json();this.dispatch("set:id",{target:l,detail:h.id});var d={id:h.id,body:o,attachments:c?[c.src]:[],replied_to:n,product:r,button:i,type:"quick_reply"};Zt.eventEmitter.dispatch("webchat:message:sent",d)})),function(e){return i.apply(this,arguments)})},{key:"sendMessage",value:(r=Qo((function*(e){var t=new FormData,n={body:this.inputTarget.value,attachments:this.files};if(0!==this.inputTarget.value.trim().length||0!==this.files.length){this.inputTarget.value.trim().length>0?t.append("message[body]",this.inputTarget.value):delete n.body,this.files.forEach((e=>{t.append("message[attachments][]",e)})),t.append("session",Zt.session),t.append("locale",j.toString());var r=this.buildMessageElement();this.inputTarget.value.trim().length>0?r.querySelector("[data-body]").innerText=this.inputTarget.value:r.querySelector("[data-message-bubble]").remove();var i=this.attachmentContainerTarget.querySelectorAll("img");i.length>0&&i.forEach((e=>{r.querySelector("[data-attachment-container]").appendChild(e.cloneNode(!0))})),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(r,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(r),r.scrollIntoView({behavior:"smooth"}),this.broadcastChannel.postMessage({type:"message:sent",element:r.outerHTML}),this.inputTarget.value="",this.resizeInput(),this.files=[],this.attachmentInputTarget.value="",this.attachmentContainerTarget.innerHTML="",this.attachmentContainerTarget.style.display="none",this.errorMessageContainerTarget.style.display="none",this.inputTarget.focus(),this.typingIndicatorVisible||(clearTimeout(this.optimisticTypingTimeout),this.optimisticTypingTimeout=setTimeout((()=>{this.showOptimisticTypingIndicator()}),this.optimisticTypingIndicatorWaitValue));var o=yield this.messagesAPI.create(t);if(o.failed)return clearTimeout(this.optimisticTypingTimeout),this.broadcastChannel.postMessage({type:"message:failed",id:r.id}),r.classList.add("failed");var s=yield o.json();r.setAttribute("data-id",s.id),n.id=s.id,Zt.eventEmitter.dispatch("webchat:message:sent",n),s.conversation!==this.conversationIdValue&&(this.conversationIdValue=s.conversation,this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)),this.typingIndicatorVisible&&this.resetTypingIndicatorTimer(),this.attachmentContainerTarget.style.display=""}else e&&e.target&&e.preventDefault()})),function(e){return r.apply(this,arguments)})},{key:"buildMessageElement",value:function(){var e=this.messageTemplateTarget.cloneNode(!0);return e.id="hellotext--webchat--".concat(this.idValue,"--message--").concat(Date.now()),e.classList.add("received"),e.style.removeProperty("display"),e.setAttribute("data-controller","hellotext--message"),e.setAttribute("data-hellotext--webchat-target","message"),e}},{key:"openAttachment",value:function(){this.attachmentInputTarget.click()}},{key:"onFileInputChange",value:function(){this.errorMessageContainerTarget.style.display="none";var e=Array.from(this.attachmentInputTarget.files);this.attachmentInputTarget.value="";var t=e.find((e=>{var t=e.type.split("/")[0];return["image","video","audio"].includes(t)?this.mediaValue[t].max_sizethis.createAttachmentElement(e))),this.inputTarget.focus()}},{key:"createAttachmentElement",value:function(e){var t=this.attachmentElement();if(this.attachmentContainerTarget.style.display="",t.setAttribute("data-name",e.name),e.type.startsWith("image/")){var n=this.attachmentImageTarget.cloneNode(!0);n.src=URL.createObjectURL(e),n.style.display="block",t.appendChild(n),this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}else{var r=t.querySelector("main");r.style.height="5rem",r.style.borderRadius="0.375rem",r.style.backgroundColor="#e5e7eb",r.style.padding="0.25rem",t.querySelector("p[data-attachment-name]").innerText=e.name,this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}}},{key:"removeAttachment",value:function(e){var{currentTarget:t}=e,n=t.closest("[data-hellotext--webchat-target='attachment']");this.files=this.files.filter((e=>e.name!==n.dataset.name)),this.attachmentInputTarget.value="",n.remove(),this.inputTarget.focus()}},{key:"attachmentTargetDisconnected",value:function(){0===this.attachmentTargets.length&&(this.attachmentContainerTarget.innerHTML="",this.attachmentContainerTarget.style.display="none")}},{key:"attachmentElement",value:function(){var e=this.attachmentTemplateTarget.cloneNode(!0);return e.removeAttribute("hidden"),e.style.display="flex",e.setAttribute("data-hellotext--webchat-target","attachment"),e}},{key:"onEmojiSelect",value:function(e){var{detail:t}=e,n=this.inputTarget.value,r=this.inputTarget.selectionStart,i=this.inputTarget.selectionEnd;this.inputTarget.value=n.slice(0,r)+t+n.slice(i),this.inputTarget.selectionStart=this.inputTarget.selectionEnd=r+t.length,this.inputTarget.focus()}},{key:"byteToMegabyte",value:function(e){return Math.ceil(e/1024/1024)}},{key:"middlewares",get:function(){return[Sr(this.offsetValue),Tr({padding:this.paddingValue}),Er()]}},{key:"shouldOpenOnMount",get:function(){return"opened"===localStorage.getItem("hellotext--webchat--".concat(this.idValue))&&!this.onMobile}},{key:"onMobile",get:function(){return window.matchMedia("(max-width: ".concat(this.fullScreenThresholdValue,"px)")).matches}}],n&&es(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),c}(v.Qr);os.values={id:String,conversationId:String,media:Object,fileSizeErrorMessage:String,placement:{type:String,default:"bottom-end"},open:{type:Boolean,default:!1},autoPlacement:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},nextPage:{type:Number,default:void 0},fullScreenThreshold:{type:Number,default:1024},typingIndicatorKeepAlive:{type:Number,default:3e4},offset:{type:Number,default:24},padding:{type:Number,default:24},optimisticTypingIndicatorWait:{type:Number,default:1e3},teaser:Object},os.classes=["fadeOut"],os.targets=["trigger","popover","input","attachmentInput","attachmentButton","errorMessageContainer","attachmentTemplate","attachmentContainer","attachment","messageTemplate","messagesContainer","title","attachmentImage","footer","toolbar","message","unreadCounter","typingIndicator","typingIndicatorTemplate","teaser"];var ss=v.Mx.start();ss.register("hellotext--form",tn),ss.register("hellotext--webchat",os),ss.register("hellotext--webchat--emoji",Bo),ss.register("hellotext--message",sn),window.Hellotext=Zt;const as=Zt},989:(e,t,n)=>{n.d(t,{Z:()=>a});var r=n(81),i=n.n(r),o=n(645),s=n.n(o)()(i());s.push([e.id,"form[data-hello-form] {\n position: relative;\n}\n\nform[data-hello-form] article [data-error-container] {\n font-size: 0.875rem;\n line-height: 1.25rem;\n display: none;\n}\n\nform[data-hello-form] article:has(input:invalid) [data-error-container] {\n display: block;\n}\n\nform[data-hello-form] [data-logo-container] {\n display: flex;\n justify-content: center;\n align-items: flex-end;\n position: absolute;\n right: 1rem;\n bottom: 1rem;\n}\n\nform[data-hello-form] [data-logo-container] small {\n margin: 0 0.3rem;\n}\n\nform[data-hello-form] [data-logo-container] [data-hello-brand] {\n width: 4rem;\n}\n",""]);const a=s},645:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,i,o){"string"==typeof e&&(e=[[null,e,void 0]]);var s={};if(r)for(var a=0;a0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=o),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),i&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=i):u[4]="".concat(i)),t.push(u))}},t}},81:e=>{e.exports=function(e){return e[1]}},379:e=>{var t=[];function n(e){for(var n=-1,r=0;r{var t={};e.exports=function(e,n){var r=function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}t[e]=n}return t[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(n)}},216:e=>{e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},565:(e,t,n)=>{e.exports=function(e){var t=n.nc;t&&e.setAttribute("nonce",t)}},795:e=>{e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var t=e.insertStyleElement(e);return{update:function(n){!function(e,t,n){var r="";n.supports&&(r+="@supports (".concat(n.supports,") {")),n.media&&(r+="@media ".concat(n.media," {"));var i=void 0!==n.layer;i&&(r+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),r+=n.css,i&&(r+="}"),n.media&&(r+="}"),n.supports&&(r+="}");var o=n.sourceMap;o&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),t.styleTagTransform(r,e,t.options)}(t,e,n)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},589:e=>{e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}}},t={};function n(r){var i=t[r];if(void 0!==i)return i.exports;var o=t[r]={id:r,exports:{}};return e[r](o,o.exports,n),o.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.nc=void 0,n(599);var r=n(184);return r.default})()));
\ No newline at end of file
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Hellotext=t():e.Hellotext=t()}("undefined"!=typeof self?self:this,(()=>(()=>{"use strict";var e,t,n,r,i={599:(e,t,n)=>{n.d(t,{Mx:()=>J,Qr:()=>ie});class r{constructor(e,t,n){this.eventTarget=e,this.eventName=t,this.eventOptions=n,this.unorderedBindings=new Set}connect(){this.eventTarget.addEventListener(this.eventName,this,this.eventOptions)}disconnect(){this.eventTarget.removeEventListener(this.eventName,this,this.eventOptions)}bindingConnected(e){this.unorderedBindings.add(e)}bindingDisconnected(e){this.unorderedBindings.delete(e)}handleEvent(e){const t=function(e){if("immediatePropagationStopped"in e)return e;{const{stopImmediatePropagation:t}=e;return Object.assign(e,{immediatePropagationStopped:!1,stopImmediatePropagation(){this.immediatePropagationStopped=!0,t.call(this)}})}}(e);for(const e of this.bindings){if(t.immediatePropagationStopped)break;e.handleEvent(t)}}hasBindings(){return this.unorderedBindings.size>0}get bindings(){return Array.from(this.unorderedBindings).sort(((e,t)=>{const n=e.index,r=t.index;return nr?1:0}))}}class i{constructor(e){this.application=e,this.eventListenerMaps=new Map,this.started=!1}start(){this.started||(this.started=!0,this.eventListeners.forEach((e=>e.connect())))}stop(){this.started&&(this.started=!1,this.eventListeners.forEach((e=>e.disconnect())))}get eventListeners(){return Array.from(this.eventListenerMaps.values()).reduce(((e,t)=>e.concat(Array.from(t.values()))),[])}bindingConnected(e){this.fetchEventListenerForBinding(e).bindingConnected(e)}bindingDisconnected(e,t=!1){this.fetchEventListenerForBinding(e).bindingDisconnected(e),t&&this.clearEventListenersForBinding(e)}handleError(e,t,n={}){this.application.handleError(e,`Error ${t}`,n)}clearEventListenersForBinding(e){const t=this.fetchEventListenerForBinding(e);t.hasBindings()||(t.disconnect(),this.removeMappedEventListenerFor(e))}removeMappedEventListenerFor(e){const{eventTarget:t,eventName:n,eventOptions:r}=e,i=this.fetchEventListenerMapForEventTarget(t),o=this.cacheKey(n,r);i.delete(o),0==i.size&&this.eventListenerMaps.delete(t)}fetchEventListenerForBinding(e){const{eventTarget:t,eventName:n,eventOptions:r}=e;return this.fetchEventListener(t,n,r)}fetchEventListener(e,t,n){const r=this.fetchEventListenerMapForEventTarget(e),i=this.cacheKey(t,n);let o=r.get(i);return o||(o=this.createEventListener(e,t,n),r.set(i,o)),o}createEventListener(e,t,n){const i=new r(e,t,n);return this.started&&i.connect(),i}fetchEventListenerMapForEventTarget(e){let t=this.eventListenerMaps.get(e);return t||(t=new Map,this.eventListenerMaps.set(e,t)),t}cacheKey(e,t){const n=[e];return Object.keys(t).sort().forEach((e=>{n.push(`${t[e]?"":"!"}${e}`)})),n.join(":")}}const o={stop:({event:e,value:t})=>(t&&e.stopPropagation(),!0),prevent:({event:e,value:t})=>(t&&e.preventDefault(),!0),self:({event:e,value:t,element:n})=>!t||n===e.target},s=/^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;function a(e){return e.replace(/(?:[_-])([a-z0-9])/g,((e,t)=>t.toUpperCase()))}function c(e){return a(e.replace(/--/g,"-").replace(/__/g,"_"))}function l(e){return e.charAt(0).toUpperCase()+e.slice(1)}function u(e){return e.replace(/([A-Z])/g,((e,t)=>`-${t.toLowerCase()}`))}function h(e){return null!=e}function d(e,t){return Object.prototype.hasOwnProperty.call(e,t)}const f=["meta","ctrl","alt","shift"];class p{constructor(e,t,n,r){this.element=e,this.index=t,this.eventTarget=n.eventTarget||e,this.eventName=n.eventName||function(e){const t=e.tagName.toLowerCase();if(t in m)return m[t](e)}(e)||y("missing event name"),this.eventOptions=n.eventOptions||{},this.identifier=n.identifier||y("missing identifier"),this.methodName=n.methodName||y("missing method name"),this.keyFilter=n.keyFilter||"",this.schema=r}static forToken(e,t){return new this(e.element,e.index,function(e){const t=e.trim().match(s)||[];let n=t[2],r=t[3];return r&&!["keydown","keyup","keypress"].includes(n)&&(n+=`.${r}`,r=""),{eventTarget:(i=t[4],"window"==i?window:"document"==i?document:void 0),eventName:n,eventOptions:t[7]?(o=t[7],o.split(":").reduce(((e,t)=>Object.assign(e,{[t.replace(/^!/,"")]:!/^!/.test(t)})),{})):{},identifier:t[5],methodName:t[6],keyFilter:t[1]||r};var i,o}(e.content),t)}toString(){const e=this.keyFilter?`.${this.keyFilter}`:"",t=this.eventTargetName?`@${this.eventTargetName}`:"";return`${this.eventName}${e}${t}->${this.identifier}#${this.methodName}`}shouldIgnoreKeyboardEvent(e){if(!this.keyFilter)return!1;const t=this.keyFilter.split("+");if(this.keyFilterDissatisfied(e,t))return!0;const n=t.filter((e=>!f.includes(e)))[0];return!!n&&(d(this.keyMappings,n)||y(`contains unknown key filter: ${this.keyFilter}`),this.keyMappings[n].toLowerCase()!==e.key.toLowerCase())}shouldIgnoreMouseEvent(e){if(!this.keyFilter)return!1;const t=[this.keyFilter];return!!this.keyFilterDissatisfied(e,t)}get params(){const e={},t=new RegExp(`^data-${this.identifier}-(.+)-param$`,"i");for(const{name:n,value:r}of Array.from(this.element.attributes)){const i=n.match(t),o=i&&i[1];o&&(e[a(o)]=g(r))}return e}get eventTargetName(){return(e=this.eventTarget)==window?"window":e==document?"document":void 0;var e}get keyMappings(){return this.schema.keyMappings}keyFilterDissatisfied(e,t){const[n,r,i,o]=f.map((e=>t.includes(e)));return e.metaKey!==n||e.ctrlKey!==r||e.altKey!==i||e.shiftKey!==o}}const m={a:()=>"click",button:()=>"click",form:()=>"submit",details:()=>"toggle",input:e=>"submit"==e.getAttribute("type")?"click":"input",select:()=>"change",textarea:()=>"input"};function y(e){throw new Error(e)}function g(e){try{return JSON.parse(e)}catch(t){return e}}class v{constructor(e,t){this.context=e,this.action=t}get index(){return this.action.index}get eventTarget(){return this.action.eventTarget}get eventOptions(){return this.action.eventOptions}get identifier(){return this.context.identifier}handleEvent(e){const t=this.prepareActionEvent(e);this.willBeInvokedByEvent(e)&&this.applyEventModifiers(t)&&this.invokeWithEvent(t)}get eventName(){return this.action.eventName}get method(){const e=this.controller[this.methodName];if("function"==typeof e)return e;throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`)}applyEventModifiers(e){const{element:t}=this.action,{actionDescriptorFilters:n}=this.context.application,{controller:r}=this.context;let i=!0;for(const[o,s]of Object.entries(this.eventOptions))if(o in n){const a=n[o];i=i&&a({name:o,value:s,event:e,element:t,controller:r})}return i}prepareActionEvent(e){return Object.assign(e,{params:this.action.params})}invokeWithEvent(e){const{target:t,currentTarget:n}=e;try{this.method.call(this.controller,e),this.context.logDebugActivity(this.methodName,{event:e,target:t,currentTarget:n,action:this.methodName})}catch(t){const{identifier:n,controller:r,element:i,index:o}=this,s={identifier:n,controller:r,element:i,index:o,event:e};this.context.handleError(t,`invoking action "${this.action}"`,s)}}willBeInvokedByEvent(e){const t=e.target;return!(e instanceof KeyboardEvent&&this.action.shouldIgnoreKeyboardEvent(e))&&!(e instanceof MouseEvent&&this.action.shouldIgnoreMouseEvent(e))&&(this.element===t||(t instanceof Element&&this.element.contains(t)?this.scope.containsElement(t):this.scope.containsElement(this.action.element)))}get controller(){return this.context.controller}get methodName(){return this.action.methodName}get element(){return this.scope.element}get scope(){return this.context.scope}}class b{constructor(e,t){this.mutationObserverInit={attributes:!0,childList:!0,subtree:!0},this.element=e,this.started=!1,this.delegate=t,this.elements=new Set,this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,this.mutationObserverInit),this.refresh())}pause(e){this.started&&(this.mutationObserver.disconnect(),this.started=!1),e(),this.started||(this.mutationObserver.observe(this.element,this.mutationObserverInit),this.started=!0)}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started){const e=new Set(this.matchElementsInTree());for(const t of Array.from(this.elements))e.has(t)||this.removeElement(t);for(const t of Array.from(e))this.addElement(t)}}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){"attributes"==e.type?this.processAttributeChange(e.target,e.attributeName):"childList"==e.type&&(this.processRemovedNodes(e.removedNodes),this.processAddedNodes(e.addedNodes))}processAttributeChange(e,t){this.elements.has(e)?this.delegate.elementAttributeChanged&&this.matchElement(e)?this.delegate.elementAttributeChanged(e,t):this.removeElement(e):this.matchElement(e)&&this.addElement(e)}processRemovedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.processTree(e,this.removeElement)}}processAddedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.elementIsActive(e)&&this.processTree(e,this.addElement)}}matchElement(e){return this.delegate.matchElement(e)}matchElementsInTree(e=this.element){return this.delegate.matchElementsInTree(e)}processTree(e,t){for(const n of this.matchElementsInTree(e))t.call(this,n)}elementFromNode(e){if(e.nodeType==Node.ELEMENT_NODE)return e}elementIsActive(e){return e.isConnected==this.element.isConnected&&this.element.contains(e)}addElement(e){this.elements.has(e)||this.elementIsActive(e)&&(this.elements.add(e),this.delegate.elementMatched&&this.delegate.elementMatched(e))}removeElement(e){this.elements.has(e)&&(this.elements.delete(e),this.delegate.elementUnmatched&&this.delegate.elementUnmatched(e))}}class w{constructor(e,t,n){this.attributeName=t,this.delegate=n,this.elementObserver=new b(e,this)}get element(){return this.elementObserver.element}get selector(){return`[${this.attributeName}]`}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get started(){return this.elementObserver.started}matchElement(e){return e.hasAttribute(this.attributeName)}matchElementsInTree(e){const t=this.matchElement(e)?[e]:[],n=Array.from(e.querySelectorAll(this.selector));return t.concat(n)}elementMatched(e){this.delegate.elementMatchedAttribute&&this.delegate.elementMatchedAttribute(e,this.attributeName)}elementUnmatched(e){this.delegate.elementUnmatchedAttribute&&this.delegate.elementUnmatchedAttribute(e,this.attributeName)}elementAttributeChanged(e,t){this.delegate.elementAttributeValueChanged&&this.attributeName==t&&this.delegate.elementAttributeValueChanged(e,t)}}function O(e,t){let n=e.get(t);return n||(n=new Set,e.set(t,n)),n}class T{constructor(){this.valuesByKey=new Map}get keys(){return Array.from(this.valuesByKey.keys())}get values(){return Array.from(this.valuesByKey.values()).reduce(((e,t)=>e.concat(Array.from(t))),[])}get size(){return Array.from(this.valuesByKey.values()).reduce(((e,t)=>e+t.size),0)}add(e,t){!function(e,t,n){O(e,t).add(n)}(this.valuesByKey,e,t)}delete(e,t){!function(e,t,n){O(e,t).delete(n),function(e,t){const n=e.get(t);null!=n&&0==n.size&&e.delete(t)}(e,t)}(this.valuesByKey,e,t)}has(e,t){const n=this.valuesByKey.get(e);return null!=n&&n.has(t)}hasKey(e){return this.valuesByKey.has(e)}hasValue(e){return Array.from(this.valuesByKey.values()).some((t=>t.has(e)))}getValuesForKey(e){const t=this.valuesByKey.get(e);return t?Array.from(t):[]}getKeysForValue(e){return Array.from(this.valuesByKey).filter((([t,n])=>n.has(e))).map((([e,t])=>e))}}class S{constructor(e,t,n,r){this._selector=t,this.details=r,this.elementObserver=new b(e,this),this.delegate=n,this.matchesByElement=new T}get started(){return this.elementObserver.started}get selector(){return this._selector}set selector(e){this._selector=e,this.refresh()}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get element(){return this.elementObserver.element}matchElement(e){const{selector:t}=this;if(t){const n=e.matches(t);return this.delegate.selectorMatchElement?n&&this.delegate.selectorMatchElement(e,this.details):n}return!1}matchElementsInTree(e){const{selector:t}=this;if(t){const n=this.matchElement(e)?[e]:[],r=Array.from(e.querySelectorAll(t)).filter((e=>this.matchElement(e)));return n.concat(r)}return[]}elementMatched(e){const{selector:t}=this;t&&this.selectorMatched(e,t)}elementUnmatched(e){const t=this.matchesByElement.getKeysForValue(e);for(const n of t)this.selectorUnmatched(e,n)}elementAttributeChanged(e,t){const{selector:n}=this;if(n){const t=this.matchElement(e),r=this.matchesByElement.has(n,e);t&&!r?this.selectorMatched(e,n):!t&&r&&this.selectorUnmatched(e,n)}}selectorMatched(e,t){this.delegate.selectorMatched(e,t,this.details),this.matchesByElement.add(t,e)}selectorUnmatched(e,t){this.delegate.selectorUnmatched(e,t,this.details),this.matchesByElement.delete(t,e)}}class k{constructor(e,t){this.element=e,this.delegate=t,this.started=!1,this.stringMap=new Map,this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,{attributes:!0,attributeOldValue:!0}),this.refresh())}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started)for(const e of this.knownAttributeNames)this.refreshAttribute(e,null)}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){const t=e.attributeName;t&&this.refreshAttribute(t,e.oldValue)}refreshAttribute(e,t){const n=this.delegate.getStringMapKeyForAttribute(e);if(null!=n){this.stringMap.has(e)||this.stringMapKeyAdded(n,e);const r=this.element.getAttribute(e);if(this.stringMap.get(e)!=r&&this.stringMapValueChanged(r,n,t),null==r){const t=this.stringMap.get(e);this.stringMap.delete(e),t&&this.stringMapKeyRemoved(n,e,t)}else this.stringMap.set(e,r)}}stringMapKeyAdded(e,t){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(e,t)}stringMapValueChanged(e,t,n){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(e,t,n)}stringMapKeyRemoved(e,t,n){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(e,t,n)}get knownAttributeNames(){return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)))}get currentAttributeNames(){return Array.from(this.element.attributes).map((e=>e.name))}get recordedAttributeNames(){return Array.from(this.stringMap.keys())}}class E{constructor(e,t,n){this.attributeObserver=new w(e,t,this),this.delegate=n,this.tokensByElement=new T}get started(){return this.attributeObserver.started}start(){this.attributeObserver.start()}pause(e){this.attributeObserver.pause(e)}stop(){this.attributeObserver.stop()}refresh(){this.attributeObserver.refresh()}get element(){return this.attributeObserver.element}get attributeName(){return this.attributeObserver.attributeName}elementMatchedAttribute(e){this.tokensMatched(this.readTokensForElement(e))}elementAttributeValueChanged(e){const[t,n]=this.refreshTokensForElement(e);this.tokensUnmatched(t),this.tokensMatched(n)}elementUnmatchedAttribute(e){this.tokensUnmatched(this.tokensByElement.getValuesForKey(e))}tokensMatched(e){e.forEach((e=>this.tokenMatched(e)))}tokensUnmatched(e){e.forEach((e=>this.tokenUnmatched(e)))}tokenMatched(e){this.delegate.tokenMatched(e),this.tokensByElement.add(e.element,e)}tokenUnmatched(e){this.delegate.tokenUnmatched(e),this.tokensByElement.delete(e.element,e)}refreshTokensForElement(e){const t=this.tokensByElement.getValuesForKey(e),n=this.readTokensForElement(e),r=function(e,t){const n=Math.max(e.length,t.length);return Array.from({length:n},((n,r)=>[e[r],t[r]]))}(t,n).findIndex((([e,t])=>{return r=t,!((n=e)&&r&&n.index==r.index&&n.content==r.content);var n,r}));return-1==r?[[],[]]:[t.slice(r),n.slice(r)]}readTokensForElement(e){const t=this.attributeName;return function(e,t,n){return e.trim().split(/\s+/).filter((e=>e.length)).map(((e,r)=>({element:t,attributeName:n,content:e,index:r})))}(e.getAttribute(t)||"",e,t)}}class j{constructor(e,t,n){this.tokenListObserver=new E(e,t,this),this.delegate=n,this.parseResultsByToken=new WeakMap,this.valuesByTokenByElement=new WeakMap}get started(){return this.tokenListObserver.started}start(){this.tokenListObserver.start()}stop(){this.tokenListObserver.stop()}refresh(){this.tokenListObserver.refresh()}get element(){return this.tokenListObserver.element}get attributeName(){return this.tokenListObserver.attributeName}tokenMatched(e){const{element:t}=e,{value:n}=this.fetchParseResultForToken(e);n&&(this.fetchValuesByTokenForElement(t).set(e,n),this.delegate.elementMatchedValue(t,n))}tokenUnmatched(e){const{element:t}=e,{value:n}=this.fetchParseResultForToken(e);n&&(this.fetchValuesByTokenForElement(t).delete(e),this.delegate.elementUnmatchedValue(t,n))}fetchParseResultForToken(e){let t=this.parseResultsByToken.get(e);return t||(t=this.parseToken(e),this.parseResultsByToken.set(e,t)),t}fetchValuesByTokenForElement(e){let t=this.valuesByTokenByElement.get(e);return t||(t=new Map,this.valuesByTokenByElement.set(e,t)),t}parseToken(e){try{return{value:this.delegate.parseValueForToken(e)}}catch(e){return{error:e}}}}class P{constructor(e,t){this.context=e,this.delegate=t,this.bindingsByAction=new Map}start(){this.valueListObserver||(this.valueListObserver=new j(this.element,this.actionAttribute,this),this.valueListObserver.start())}stop(){this.valueListObserver&&(this.valueListObserver.stop(),delete this.valueListObserver,this.disconnectAllActions())}get element(){return this.context.element}get identifier(){return this.context.identifier}get actionAttribute(){return this.schema.actionAttribute}get schema(){return this.context.schema}get bindings(){return Array.from(this.bindingsByAction.values())}connectAction(e){const t=new v(this.context,e);this.bindingsByAction.set(e,t),this.delegate.bindingConnected(t)}disconnectAction(e){const t=this.bindingsByAction.get(e);t&&(this.bindingsByAction.delete(e),this.delegate.bindingDisconnected(t))}disconnectAllActions(){this.bindings.forEach((e=>this.delegate.bindingDisconnected(e,!0))),this.bindingsByAction.clear()}parseValueForToken(e){const t=p.forToken(e,this.schema);if(t.identifier==this.identifier)return t}elementMatchedValue(e,t){this.connectAction(t)}elementUnmatchedValue(e,t){this.disconnectAction(t)}}class C{constructor(e,t){this.context=e,this.receiver=t,this.stringMapObserver=new k(this.element,this),this.valueDescriptorMap=this.controller.valueDescriptorMap}start(){this.stringMapObserver.start(),this.invokeChangedCallbacksForDefaultValues()}stop(){this.stringMapObserver.stop()}get element(){return this.context.element}get controller(){return this.context.controller}getStringMapKeyForAttribute(e){if(e in this.valueDescriptorMap)return this.valueDescriptorMap[e].name}stringMapKeyAdded(e,t){const n=this.valueDescriptorMap[t];this.hasValue(e)||this.invokeChangedCallback(e,n.writer(this.receiver[e]),n.writer(n.defaultValue))}stringMapValueChanged(e,t,n){const r=this.valueDescriptorNameMap[t];null!==e&&(null===n&&(n=r.writer(r.defaultValue)),this.invokeChangedCallback(t,e,n))}stringMapKeyRemoved(e,t,n){const r=this.valueDescriptorNameMap[e];this.hasValue(e)?this.invokeChangedCallback(e,r.writer(this.receiver[e]),n):this.invokeChangedCallback(e,r.writer(r.defaultValue),n)}invokeChangedCallbacksForDefaultValues(){for(const{key:e,name:t,defaultValue:n,writer:r}of this.valueDescriptors)null==n||this.controller.data.has(e)||this.invokeChangedCallback(t,r(n),void 0)}invokeChangedCallback(e,t,n){const r=`${e}Changed`,i=this.receiver[r];if("function"==typeof i){const r=this.valueDescriptorNameMap[e];try{const e=r.reader(t);let o=n;n&&(o=r.reader(n)),i.call(this.receiver,e,o)}catch(e){throw e instanceof TypeError&&(e.message=`Stimulus Value "${this.context.identifier}.${r.name}" - ${e.message}`),e}}}get valueDescriptors(){const{valueDescriptorMap:e}=this;return Object.keys(e).map((t=>e[t]))}get valueDescriptorNameMap(){const e={};return Object.keys(this.valueDescriptorMap).forEach((t=>{const n=this.valueDescriptorMap[t];e[n.name]=n})),e}hasValue(e){const t=`has${l(this.valueDescriptorNameMap[e].name)}`;return this.receiver[t]}}class x{constructor(e,t){this.context=e,this.delegate=t,this.targetsByName=new T}start(){this.tokenListObserver||(this.tokenListObserver=new E(this.element,this.attributeName,this),this.tokenListObserver.start())}stop(){this.tokenListObserver&&(this.disconnectAllTargets(),this.tokenListObserver.stop(),delete this.tokenListObserver)}tokenMatched({element:e,content:t}){this.scope.containsElement(e)&&this.connectTarget(e,t)}tokenUnmatched({element:e,content:t}){this.disconnectTarget(e,t)}connectTarget(e,t){var n;this.targetsByName.has(t,e)||(this.targetsByName.add(t,e),null===(n=this.tokenListObserver)||void 0===n||n.pause((()=>this.delegate.targetConnected(e,t))))}disconnectTarget(e,t){var n;this.targetsByName.has(t,e)&&(this.targetsByName.delete(t,e),null===(n=this.tokenListObserver)||void 0===n||n.pause((()=>this.delegate.targetDisconnected(e,t))))}disconnectAllTargets(){for(const e of this.targetsByName.keys)for(const t of this.targetsByName.getValuesForKey(e))this.disconnectTarget(t,e)}get attributeName(){return`data-${this.context.identifier}-target`}get element(){return this.context.element}get scope(){return this.context.scope}}function A(e,t){const n=M(e);return Array.from(n.reduce(((e,n)=>(function(e,t){const n=e[t];return Array.isArray(n)?n:[]}(n,t).forEach((t=>e.add(t))),e)),new Set))}function M(e){const t=[];for(;e;)t.push(e),e=Object.getPrototypeOf(e);return t.reverse()}class _{constructor(e,t){this.started=!1,this.context=e,this.delegate=t,this.outletsByName=new T,this.outletElementsByName=new T,this.selectorObserverMap=new Map,this.attributeObserverMap=new Map}start(){this.started||(this.outletDefinitions.forEach((e=>{this.setupSelectorObserverForOutlet(e),this.setupAttributeObserverForOutlet(e)})),this.started=!0,this.dependentContexts.forEach((e=>e.refresh())))}refresh(){this.selectorObserverMap.forEach((e=>e.refresh())),this.attributeObserverMap.forEach((e=>e.refresh()))}stop(){this.started&&(this.started=!1,this.disconnectAllOutlets(),this.stopSelectorObservers(),this.stopAttributeObservers())}stopSelectorObservers(){this.selectorObserverMap.size>0&&(this.selectorObserverMap.forEach((e=>e.stop())),this.selectorObserverMap.clear())}stopAttributeObservers(){this.attributeObserverMap.size>0&&(this.attributeObserverMap.forEach((e=>e.stop())),this.attributeObserverMap.clear())}selectorMatched(e,t,{outletName:n}){const r=this.getOutlet(e,n);r&&this.connectOutlet(r,e,n)}selectorUnmatched(e,t,{outletName:n}){const r=this.getOutletFromMap(e,n);r&&this.disconnectOutlet(r,e,n)}selectorMatchElement(e,{outletName:t}){const n=this.selector(t),r=this.hasOutlet(e,t),i=e.matches(`[${this.schema.controllerAttribute}~=${t}]`);return!!n&&r&&i&&e.matches(n)}elementMatchedAttribute(e,t){const n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}elementAttributeValueChanged(e,t){const n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}elementUnmatchedAttribute(e,t){const n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}connectOutlet(e,t,n){var r;this.outletElementsByName.has(n,t)||(this.outletsByName.add(n,e),this.outletElementsByName.add(n,t),null===(r=this.selectorObserverMap.get(n))||void 0===r||r.pause((()=>this.delegate.outletConnected(e,t,n))))}disconnectOutlet(e,t,n){var r;this.outletElementsByName.has(n,t)&&(this.outletsByName.delete(n,e),this.outletElementsByName.delete(n,t),null===(r=this.selectorObserverMap.get(n))||void 0===r||r.pause((()=>this.delegate.outletDisconnected(e,t,n))))}disconnectAllOutlets(){for(const e of this.outletElementsByName.keys)for(const t of this.outletElementsByName.getValuesForKey(e))for(const n of this.outletsByName.getValuesForKey(e))this.disconnectOutlet(n,t,e)}updateSelectorObserverForOutlet(e){const t=this.selectorObserverMap.get(e);t&&(t.selector=this.selector(e))}setupSelectorObserverForOutlet(e){const t=this.selector(e),n=new S(document.body,t,this,{outletName:e});this.selectorObserverMap.set(e,n),n.start()}setupAttributeObserverForOutlet(e){const t=this.attributeNameForOutletName(e),n=new w(this.scope.element,t,this);this.attributeObserverMap.set(e,n),n.start()}selector(e){return this.scope.outlets.getSelectorForOutletName(e)}attributeNameForOutletName(e){return this.scope.schema.outletAttributeForScope(this.identifier,e)}getOutletNameFromOutletAttributeName(e){return this.outletDefinitions.find((t=>this.attributeNameForOutletName(t)===e))}get outletDependencies(){const e=new T;return this.router.modules.forEach((t=>{A(t.definition.controllerConstructor,"outlets").forEach((n=>e.add(n,t.identifier)))})),e}get outletDefinitions(){return this.outletDependencies.getKeysForValue(this.identifier)}get dependentControllerIdentifiers(){return this.outletDependencies.getValuesForKey(this.identifier)}get dependentContexts(){const e=this.dependentControllerIdentifiers;return this.router.contexts.filter((t=>e.includes(t.identifier)))}hasOutlet(e,t){return!!this.getOutlet(e,t)||!!this.getOutletFromMap(e,t)}getOutlet(e,t){return this.application.getControllerForElementAndIdentifier(e,t)}getOutletFromMap(e,t){return this.outletsByName.getValuesForKey(t).find((t=>t.element===e))}get scope(){return this.context.scope}get schema(){return this.context.schema}get identifier(){return this.context.identifier}get application(){return this.context.application}get router(){return this.application.router}}class I{constructor(e,t){this.logDebugActivity=(e,t={})=>{const{identifier:n,controller:r,element:i}=this;t=Object.assign({identifier:n,controller:r,element:i},t),this.application.logDebugActivity(this.identifier,e,t)},this.module=e,this.scope=t,this.controller=new e.controllerConstructor(this),this.bindingObserver=new P(this,this.dispatcher),this.valueObserver=new C(this,this.controller),this.targetObserver=new x(this,this),this.outletObserver=new _(this,this);try{this.controller.initialize(),this.logDebugActivity("initialize")}catch(e){this.handleError(e,"initializing controller")}}connect(){this.bindingObserver.start(),this.valueObserver.start(),this.targetObserver.start(),this.outletObserver.start();try{this.controller.connect(),this.logDebugActivity("connect")}catch(e){this.handleError(e,"connecting controller")}}refresh(){this.outletObserver.refresh()}disconnect(){try{this.controller.disconnect(),this.logDebugActivity("disconnect")}catch(e){this.handleError(e,"disconnecting controller")}this.outletObserver.stop(),this.targetObserver.stop(),this.valueObserver.stop(),this.bindingObserver.stop()}get application(){return this.module.application}get identifier(){return this.module.identifier}get schema(){return this.application.schema}get dispatcher(){return this.application.dispatcher}get element(){return this.scope.element}get parentElement(){return this.element.parentElement}handleError(e,t,n={}){const{identifier:r,controller:i,element:o}=this;n=Object.assign({identifier:r,controller:i,element:o},n),this.application.handleError(e,`Error ${t}`,n)}targetConnected(e,t){this.invokeControllerMethod(`${t}TargetConnected`,e)}targetDisconnected(e,t){this.invokeControllerMethod(`${t}TargetDisconnected`,e)}outletConnected(e,t,n){this.invokeControllerMethod(`${c(n)}OutletConnected`,e,t)}outletDisconnected(e,t,n){this.invokeControllerMethod(`${c(n)}OutletDisconnected`,e,t)}invokeControllerMethod(e,...t){const n=this.controller;"function"==typeof n[e]&&n[e](...t)}}const L="function"==typeof Object.getOwnPropertySymbols?e=>[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)]:Object.getOwnPropertyNames,F=(()=>{function e(e){function t(){return Reflect.construct(e,arguments,new.target)}return t.prototype=Object.create(e.prototype,{constructor:{value:t}}),Reflect.setPrototypeOf(t,e),t}try{return function(){const t=e((function(){this.a.call(this)}));t.prototype.a=function(){},new t}(),e}catch(e){return e=>class extends e{}}})();class N{constructor(e,t){this.application=e,this.definition=function(e){return{identifier:e.identifier,controllerConstructor:(t=e.controllerConstructor,function(e,t){const n=F(e),r=function(e,t){return L(t).reduce(((n,r)=>{const i=function(e,t,n){const r=Object.getOwnPropertyDescriptor(e,n);if(!r||!("value"in r)){const e=Object.getOwnPropertyDescriptor(t,n).value;return r&&(e.get=r.get||e.get,e.set=r.set||e.set),e}}(e,t,r);return i&&Object.assign(n,{[r]:i}),n}),{})}(e.prototype,t);return Object.defineProperties(n.prototype,r),n}(t,function(e){return A(e,"blessings").reduce(((t,n)=>{const r=n(e);for(const e in r){const n=t[e]||{};t[e]=Object.assign(n,r[e])}return t}),{})}(t)))};var t}(t),this.contextsByScope=new WeakMap,this.connectedContexts=new Set}get identifier(){return this.definition.identifier}get controllerConstructor(){return this.definition.controllerConstructor}get contexts(){return Array.from(this.connectedContexts)}connectContextForScope(e){const t=this.fetchContextForScope(e);this.connectedContexts.add(t),t.connect()}disconnectContextForScope(e){const t=this.contextsByScope.get(e);t&&(this.connectedContexts.delete(t),t.disconnect())}fetchContextForScope(e){let t=this.contextsByScope.get(e);return t||(t=new I(this,e),this.contextsByScope.set(e,t)),t}}class B{constructor(e){this.scope=e}has(e){return this.data.has(this.getDataKey(e))}get(e){return this.getAll(e)[0]}getAll(e){return(this.data.get(this.getDataKey(e))||"").match(/[^\s]+/g)||[]}getAttributeName(e){return this.data.getAttributeNameForKey(this.getDataKey(e))}getDataKey(e){return`${e}-class`}get data(){return this.scope.data}}class D{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get(e){const t=this.getAttributeNameForKey(e);return this.element.getAttribute(t)}set(e,t){const n=this.getAttributeNameForKey(e);return this.element.setAttribute(n,t),this.get(e)}has(e){const t=this.getAttributeNameForKey(e);return this.element.hasAttribute(t)}delete(e){if(this.has(e)){const t=this.getAttributeNameForKey(e);return this.element.removeAttribute(t),!0}return!1}getAttributeNameForKey(e){return`data-${this.identifier}-${u(e)}`}}class R{constructor(e){this.warnedKeysByObject=new WeakMap,this.logger=e}warn(e,t,n){let r=this.warnedKeysByObject.get(e);r||(r=new Set,this.warnedKeysByObject.set(e,r)),r.has(t)||(r.add(t),this.logger.warn(n,e))}}function V(e,t){return`[${e}~="${t}"]`}class q{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findTarget(t)||this.findLegacyTarget(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllTargets(t),...this.findAllLegacyTargets(t)]),[])}findTarget(e){const t=this.getSelectorForTargetName(e);return this.scope.findElement(t)}findAllTargets(e){const t=this.getSelectorForTargetName(e);return this.scope.findAllElements(t)}getSelectorForTargetName(e){return V(this.schema.targetAttributeForScope(this.identifier),e)}findLegacyTarget(e){const t=this.getLegacySelectorForTargetName(e);return this.deprecate(this.scope.findElement(t),e)}findAllLegacyTargets(e){const t=this.getLegacySelectorForTargetName(e);return this.scope.findAllElements(t).map((t=>this.deprecate(t,e)))}getLegacySelectorForTargetName(e){const t=`${this.identifier}.${e}`;return V(this.schema.targetAttribute,t)}deprecate(e,t){if(e){const{identifier:n}=this,r=this.schema.targetAttribute,i=this.schema.targetAttributeForScope(n);this.guide.warn(e,`target:${t}`,`Please replace ${r}="${n}.${t}" with ${i}="${t}". The ${r} attribute is deprecated and will be removed in a future version of Stimulus.`)}return e}get guide(){return this.scope.guide}}class ${constructor(e,t){this.scope=e,this.controllerElement=t}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findOutlet(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllOutlets(t)]),[])}getSelectorForOutletName(e){const t=this.schema.outletAttributeForScope(this.identifier,e);return this.controllerElement.getAttribute(t)}findOutlet(e){const t=this.getSelectorForOutletName(e);if(t)return this.findElement(t,e)}findAllOutlets(e){const t=this.getSelectorForOutletName(e);return t?this.findAllElements(t,e):[]}findElement(e,t){return this.scope.queryElements(e).filter((n=>this.matchesElement(n,e,t)))[0]}findAllElements(e,t){return this.scope.queryElements(e).filter((n=>this.matchesElement(n,e,t)))}matchesElement(e,t,n){const r=e.getAttribute(this.scope.schema.controllerAttribute)||"";return e.matches(t)&&r.split(" ").includes(n)}}class K{constructor(e,t,n,r){this.targets=new q(this),this.classes=new B(this),this.data=new D(this),this.containsElement=e=>e.closest(this.controllerSelector)===this.element,this.schema=e,this.element=t,this.identifier=n,this.guide=new R(r),this.outlets=new $(this.documentScope,t)}findElement(e){return this.element.matches(e)?this.element:this.queryElements(e).find(this.containsElement)}findAllElements(e){return[...this.element.matches(e)?[this.element]:[],...this.queryElements(e).filter(this.containsElement)]}queryElements(e){return Array.from(this.element.querySelectorAll(e))}get controllerSelector(){return V(this.schema.controllerAttribute,this.identifier)}get isDocumentScope(){return this.element===document.documentElement}get documentScope(){return this.isDocumentScope?this:new K(this.schema,document.documentElement,this.identifier,this.guide.logger)}}class H{constructor(e,t,n){this.element=e,this.schema=t,this.delegate=n,this.valueListObserver=new j(this.element,this.controllerAttribute,this),this.scopesByIdentifierByElement=new WeakMap,this.scopeReferenceCounts=new WeakMap}start(){this.valueListObserver.start()}stop(){this.valueListObserver.stop()}get controllerAttribute(){return this.schema.controllerAttribute}parseValueForToken(e){const{element:t,content:n}=e;return this.parseValueForElementAndIdentifier(t,n)}parseValueForElementAndIdentifier(e,t){const n=this.fetchScopesByIdentifierForElement(e);let r=n.get(t);return r||(r=this.delegate.createScopeForElementAndIdentifier(e,t),n.set(t,r)),r}elementMatchedValue(e,t){const n=(this.scopeReferenceCounts.get(t)||0)+1;this.scopeReferenceCounts.set(t,n),1==n&&this.delegate.scopeConnected(t)}elementUnmatchedValue(e,t){const n=this.scopeReferenceCounts.get(t);n&&(this.scopeReferenceCounts.set(t,n-1),1==n&&this.delegate.scopeDisconnected(t))}fetchScopesByIdentifierForElement(e){let t=this.scopesByIdentifierByElement.get(e);return t||(t=new Map,this.scopesByIdentifierByElement.set(e,t)),t}}class U{constructor(e){this.application=e,this.scopeObserver=new H(this.element,this.schema,this),this.scopesByIdentifier=new T,this.modulesByIdentifier=new Map}get element(){return this.application.element}get schema(){return this.application.schema}get logger(){return this.application.logger}get controllerAttribute(){return this.schema.controllerAttribute}get modules(){return Array.from(this.modulesByIdentifier.values())}get contexts(){return this.modules.reduce(((e,t)=>e.concat(t.contexts)),[])}start(){this.scopeObserver.start()}stop(){this.scopeObserver.stop()}loadDefinition(e){this.unloadIdentifier(e.identifier);const t=new N(this.application,e);this.connectModule(t);const n=e.controllerConstructor.afterLoad;n&&n.call(e.controllerConstructor,e.identifier,this.application)}unloadIdentifier(e){const t=this.modulesByIdentifier.get(e);t&&this.disconnectModule(t)}getContextForElementAndIdentifier(e,t){const n=this.modulesByIdentifier.get(t);if(n)return n.contexts.find((t=>t.element==e))}proposeToConnectScopeForElementAndIdentifier(e,t){const n=this.scopeObserver.parseValueForElementAndIdentifier(e,t);n?this.scopeObserver.elementMatchedValue(n.element,n):console.error(`Couldn't find or create scope for identifier: "${t}" and element:`,e)}handleError(e,t,n){this.application.handleError(e,t,n)}createScopeForElementAndIdentifier(e,t){return new K(this.schema,e,t,this.logger)}scopeConnected(e){this.scopesByIdentifier.add(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.connectContextForScope(e)}scopeDisconnected(e){this.scopesByIdentifier.delete(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.disconnectContextForScope(e)}connectModule(e){this.modulesByIdentifier.set(e.identifier,e),this.scopesByIdentifier.getValuesForKey(e.identifier).forEach((t=>e.connectContextForScope(t)))}disconnectModule(e){this.modulesByIdentifier.delete(e.identifier),this.scopesByIdentifier.getValuesForKey(e.identifier).forEach((t=>e.disconnectContextForScope(t)))}}const z={controllerAttribute:"data-controller",actionAttribute:"data-action",targetAttribute:"data-target",targetAttributeForScope:e=>`data-${e}-target`,outletAttributeForScope:(e,t)=>`data-${e}-${t}-outlet`,keyMappings:Object.assign(Object.assign({enter:"Enter",tab:"Tab",esc:"Escape",space:" ",up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",page_up:"PageUp",page_down:"PageDown"},W("abcdefghijklmnopqrstuvwxyz".split("").map((e=>[e,e])))),W("0123456789".split("").map((e=>[e,e]))))};function W(e){return e.reduce(((e,[t,n])=>Object.assign(Object.assign({},e),{[t]:n})),{})}class J{constructor(e=document.documentElement,t=z){this.logger=console,this.debug=!1,this.logDebugActivity=(e,t,n={})=>{this.debug&&this.logFormattedMessage(e,t,n)},this.element=e,this.schema=t,this.dispatcher=new i(this),this.router=new U(this),this.actionDescriptorFilters=Object.assign({},o)}static start(e,t){const n=new this(e,t);return n.start(),n}async start(){await new Promise((e=>{"loading"==document.readyState?document.addEventListener("DOMContentLoaded",(()=>e())):e()})),this.logDebugActivity("application","starting"),this.dispatcher.start(),this.router.start(),this.logDebugActivity("application","start")}stop(){this.logDebugActivity("application","stopping"),this.dispatcher.stop(),this.router.stop(),this.logDebugActivity("application","stop")}register(e,t){this.load({identifier:e,controllerConstructor:t})}registerActionOption(e,t){this.actionDescriptorFilters[e]=t}load(e,...t){(Array.isArray(e)?e:[e,...t]).forEach((e=>{e.controllerConstructor.shouldLoad&&this.router.loadDefinition(e)}))}unload(e,...t){(Array.isArray(e)?e:[e,...t]).forEach((e=>this.router.unloadIdentifier(e)))}get controllers(){return this.router.contexts.map((e=>e.controller))}getControllerForElementAndIdentifier(e,t){const n=this.router.getContextForElementAndIdentifier(e,t);return n?n.controller:null}handleError(e,t,n){var r;this.logger.error("%s\n\n%o\n\n%o",t,e,n),null===(r=window.onerror)||void 0===r||r.call(window,t,"",0,0,e)}logFormattedMessage(e,t,n={}){n=Object.assign({application:this},n),this.logger.groupCollapsed(`${e} #${t}`),this.logger.log("details:",Object.assign({},n)),this.logger.groupEnd()}}function Z(e,t,n){return e.application.getControllerForElementAndIdentifier(t,n)}function G(e,t,n){let r=Z(e,t,n);return r||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,n),r=Z(e,t,n),r||void 0)}function Q([e,t],n){return function(e){const{token:t,typeDefinition:n}=e,r=`${u(t)}-value`,i=function(e){const{controller:t,token:n,typeDefinition:r}=e,i=function(e){const{controller:t,token:n,typeObject:r}=e,i=h(r.type),o=h(r.default),s=i&&o,a=i&&!o,c=!i&&o,l=X(r.type),u=Y(e.typeObject.default);if(a)return l;if(c)return u;if(l!==u)throw new Error(`The specified default value for the Stimulus Value "${t?`${t}.${n}`:n}" must match the defined type "${l}". The provided default value of "${r.default}" is of type "${u}".`);return s?l:void 0}({controller:t,token:n,typeObject:r}),o=Y(r),s=X(r),a=i||o||s;if(a)return a;throw new Error(`Unknown value type "${t?`${t}.${r}`:n}" for "${n}" value`)}(e);return{type:i,key:r,name:a(r),get defaultValue(){return function(e){const t=X(e);if(t)return ee[t];const n=d(e,"default"),r=d(e,"type"),i=e;if(n)return i.default;if(r){const{type:e}=i,t=X(e);if(t)return ee[t]}return e}(n)},get hasCustomDefaultValue(){return void 0!==Y(n)},reader:te[i],writer:ne[i]||ne.default}}({controller:n,token:e,typeDefinition:t})}function X(e){switch(e){case Array:return"array";case Boolean:return"boolean";case Number:return"number";case Object:return"object";case String:return"string"}}function Y(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string"}return Array.isArray(e)?"array":"[object Object]"===Object.prototype.toString.call(e)?"object":void 0}const ee={get array(){return[]},boolean:!1,number:0,get object(){return{}},string:""},te={array(e){const t=JSON.parse(e);if(!Array.isArray(t))throw new TypeError(`expected value of type "array" but instead got value "${e}" of type "${Y(t)}"`);return t},boolean:e=>!("0"==e||"false"==String(e).toLowerCase()),number:e=>Number(e.replace(/_/g,"")),object(e){const t=JSON.parse(e);if(null===t||"object"!=typeof t||Array.isArray(t))throw new TypeError(`expected value of type "object" but instead got value "${e}" of type "${Y(t)}"`);return t},string:e=>e},ne={default:function(e){return`${e}`},array:re,object:re};function re(e){return JSON.stringify(e)}class ie{constructor(e){this.context=e}static get shouldLoad(){return!0}static afterLoad(e,t){}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get outlets(){return this.scope.outlets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(e,{target:t=this.element,detail:n={},prefix:r=this.identifier,bubbles:i=!0,cancelable:o=!0}={}){const s=new CustomEvent(r?`${r}:${e}`:e,{detail:n,bubbles:i,cancelable:o});return t.dispatchEvent(s),s}}ie.blessings=[function(e){return A(e,"classes").reduce(((e,t)=>{return Object.assign(e,{[`${n=t}Class`]:{get(){const{classes:e}=this;if(e.has(n))return e.get(n);{const t=e.getAttributeName(n);throw new Error(`Missing attribute "${t}"`)}}},[`${n}Classes`]:{get(){return this.classes.getAll(n)}},[`has${l(n)}Class`]:{get(){return this.classes.has(n)}}});var n}),{})},function(e){return A(e,"targets").reduce(((e,t)=>{return Object.assign(e,{[`${n=t}Target`]:{get(){const e=this.targets.find(n);if(e)return e;throw new Error(`Missing target element "${n}" for "${this.identifier}" controller`)}},[`${n}Targets`]:{get(){return this.targets.findAll(n)}},[`has${l(n)}Target`]:{get(){return this.targets.has(n)}}});var n}),{})},function(e){const t=function(e,t){return M(e).reduce(((e,n)=>(e.push(...function(e,t){const n=e[t];return n?Object.keys(n).map((e=>[e,n[e]])):[]}(n,t)),e)),[])}(e,"values"),n={valueDescriptorMap:{get(){return t.reduce(((e,t)=>{const n=Q(t,this.identifier),r=this.data.getAttributeNameForKey(n.key);return Object.assign(e,{[r]:n})}),{})}}};return t.reduce(((e,t)=>Object.assign(e,function(e,t){const n=Q(e,void 0),{key:r,name:i,reader:o,writer:s}=n;return{[i]:{get(){const e=this.data.get(r);return null!==e?o(e):n.defaultValue},set(e){void 0===e?this.data.delete(r):this.data.set(r,s(e))}},[`has${l(i)}`]:{get(){return this.data.has(r)||n.hasCustomDefaultValue}}}}(t))),n)},function(e){return A(e,"outlets").reduce(((e,t)=>Object.assign(e,function(e){const t=c(e);return{[`${t}Outlet`]:{get(){const t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t){const n=G(this,t,e);if(n)return n;throw new Error(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`)}throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${n}".`)}},[`${t}Outlets`]:{get(){const t=this.outlets.findAll(e);return t.length>0?t.map((t=>{const n=G(this,t,e);if(n)return n;console.warn(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`,t)})).filter((e=>e)):[]}},[`${t}OutletElement`]:{get(){const t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t)return t;throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${n}".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${l(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}(t))),{})}],ie.targets=[],ie.outlets=[],ie.values={}},228:(e,t,n)=>{n.d(t,{default:()=>hi});var r=n(379),i=n.n(r),o=n(795),s=n.n(o),a=n(569),c=n.n(a),l=n(565),u=n.n(l),h=n(216),d=n.n(h),f=n(589),p=n.n(f),m=n(989),y={};y.styleTagTransform=p(),y.setAttributes=u(),y.insert=c().bind(null,"head"),y.domAPI=s(),y.insertStyleElement=d(),i()(m.Z,y),m.Z&&m.Z.locals&&m.Z.locals;var g=n(599);function v(e,t){for(var n=0;n{var[t,n]=e;this[t]=n})),this}},{key:"shouldShowSuccessMessage",get:function(){return this.successMessage}}],null&&v(t.prototype,null),n&&v(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function w(e,t){for(var n=0;n{var[t,n]=e;if(!["primaryColor","secondaryColor","typography"].includes(t))throw new Error("Invalid style property: ".concat(t));if("typography"!==t&&!this.isHexOrRgba(n))throw new Error("Invalid color value: ".concat(n," for ").concat(t,". Colors must be hex or rgb/a."))})),this._style=e}},{key:"appearance",get:function(){return this._appearance},set:function(e){if(!this.isPlainObject(e))throw new Error("Appearance must be an object");Object.entries(e).forEach((e=>{var[t,n]=e;if(!["header","launcher"].includes(t))throw new Error("Invalid appearance property: ".concat(t));if(!this.isPlainObject(n))throw new Error("Appearance ".concat(t," must be an object"));Object.entries(n).forEach((e=>{var[n,r]=e;if("header"===t&&"name"!==n)throw new Error("Invalid appearance header property: ".concat(n));if("launcher"===t&&"iconUrl"!==n)throw new Error("Invalid appearance launcher property: ".concat(n));if(null!=r&&"string"!=typeof r)throw new Error("Invalid appearance ".concat(t,".").concat(n," value: ").concat(r))}))})),this._appearance=e}},{key:"whatsapp",get:function(){return this._whatsapp},set:function(e){if(!this.isPlainObject(e))throw new Error("WhatsApp must be an object");Object.entries(e).forEach((e=>{var[t,n]=e;if(!["number","restrictToChannel"].includes(t))throw new Error("Invalid WhatsApp property: ".concat(t));if(null!=n){if("number"===t&&"string"!=typeof n)throw new Error("Invalid WhatsApp number value: ".concat(n));if("restrictToChannel"===t&&"boolean"!=typeof n)throw new Error("Invalid WhatsApp restrictToChannel value: ".concat(n))}})),this._whatsapp=e}},{key:"mode",get:function(){return this._mode},set:function(e){if(!Object.values(M).includes(e))throw new Error("Invalid mode value: ".concat(e));this._mode=e}},{key:"behaviour",get:function(){return this._behaviour},set:function(e){if(null!=e){if("object"!=typeof e||Array.isArray(e))throw new Error("Invalid behaviour value: ".concat(e));this._behaviour=e}else this._behaviour=e}},{key:"hasBehaviourOverride",get:function(){return this._hasBehaviourOverride}},{key:"behaviourOverride",set:function(e){this._hasBehaviourOverride=!!e}},{key:"strategy",get:function(){return this._strategy?this._strategy:"body"==this.container?A.FIXED:A.ABSOLUTE},set:function(e){if(e&&!Object.values(A).includes(e))throw new Error("Invalid strategy value: ".concat(e));this._strategy=e}},{key:"assign",value:function(e){return e&&Object.entries(e).forEach((e=>{var[t,n]=e;this[t]=n})),this}},{key:"isHexOrRgba",value:function(e){return/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(e)||/^rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3},?\s*(0|1|0?\.\d+)?\s*\)$/.test(e)}},{key:"isPlainObject",value:function(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}}],null&&C(t.prototype,null),n&&C(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function I(e,t){for(var n=0;n{var[t,n]=e;"forms"===t?this.forms=b.assign(n):"webchat"===t?this.webchat=_.assign(n):this[t]=n})),this}},{key:"locale",get:function(){return P.toString()},set:function(e){P.identifier=e}},{key:"endpoint",value:function(e){return"".concat(this.apiRoot,"/").concat(e)}}],null&&I(t.prototype,null),n&&I(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function F(e){var t="function"==typeof Map?new Map:void 0;return F=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return N(e,arguments,R(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),D(r,e)},F(e)}function N(e,t,n){return N=B()?Reflect.construct.bind():function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&D(i,n.prototype),i},N.apply(null,arguments)}function B(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function D(e,t){return D=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},D(e,t)}function R(e){return R=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},R(e)}L.apiRoot="https://api.hellotext.com/v1",L.actionCableUrl="wss://www.hellotext.com/cable",L.autoGenerateSession=!0,L.session=null,L.forms=b,L.webchat=_;var V=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&D(e,t)}(o,e);var t,n,r,i=(n=o,r=B(),function(){var e,t=R(n);if(r){var i=R(this).constructor;e=Reflect.construct(t,arguments,i)}else e=t.apply(this,arguments);return function(e,t){if(t&&("object"==typeof t||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(this,e)});function o(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(t=i.call(this,"".concat(e," is not valid. Please provide a valid event name"))).name="InvalidEvent",t}return t=o,Object.defineProperty(t,"prototype",{writable:!1}),t}(F(Error));function q(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function $(e){for(var t=1;tt===e))}}],(n=[{key:"addSubscriber",value:function(t,n){if(e.invalid(t))throw new V(t);this.subscribers=$($({},this.subscribers),{},{[t]:this.subscribers[t]?[...this.subscribers[t],n]:[n]})}},{key:"removeSubscriber",value:function(t,n){if(e.invalid(t))throw new V(t);this.subscribers[t]&&(this.subscribers[t]=this.subscribers[t].filter((e=>e!==n)))}},{key:"dispatch",value:function(e,t){var n;null===(n=this.subscribers[e])||void 0===n||n.forEach((e=>{e(t)}))}},{key:"listeners",get:function(){return 0!==Object.keys(this.subscribers).length}}])&&H(t.prototype,n),r&&H(t,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function W(e,t,n,r,i,o,s){try{var a=e[o](s),c=a.value}catch(e){return void n(e)}a.done?t(c):Promise.resolve(c).then(r,i)}function J(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{},t=yield fetch(this.endpoint,{method:"POST",headers:Zt.headers,body:JSON.stringify(ge({session:Zt.session},e))});return new ie(t.ok,t)},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){be(o,n,i,s,a,"next",e)}function a(e){be(o,n,i,s,a,"throw",e)}s(void 0)}))},function(){return i.apply(this,arguments)})}],null&&we(t.prototype,null),n&&we(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Se(e,t,n,r,i,o,s){try{var a=e[o](s),c=a.value}catch(e){return void n(e)}a.done?t(c):Promise.resolve(c).then(r,i)}function ke(e,t){for(var n=0;n{var[n,r]=e;t.searchParams.append("style[".concat(n,"]"),r)})),this.appendWebchatOverrides(t),t.searchParams.append("placement",L.webchat.placement);var n=yield fetch(t,{method:"GET",headers:Zt.headers}),r=yield n.json();return Zt.business.data||(Zt.business.setData(r.business),Zt.business.setLocale(r.locale)),(new DOMParser).parseFromString(r.html,"text/html").querySelector("article")},function(){var t=this,n=arguments;return new Promise((function(r,i){var o=e.apply(t,n);function s(e){Se(o,r,i,s,a,"next",e)}function a(e){Se(o,r,i,s,a,"throw",e)}s(void 0)}))});return function(e){return t.apply(this,arguments)}}()},{key:"appendWebchatOverrides",value:function(e){var t,n,{appearance:r,whatsapp:i}=L.webchat;this.appendIfSupplied(e,"webchat[appearance][header][name]",null===(t=r.header)||void 0===t?void 0:t.name),this.appendIfSupplied(e,"webchat[appearance][launcher][icon_url]",null===(n=r.launcher)||void 0===n?void 0:n.iconUrl),this.appendIfSupplied(e,"webchat[handoff][identifier]",i.number),this.appendIfSupplied(e,"webchat[handoff][restrict_to_channel]",i.restrictToChannel)}},{key:"appendIfSupplied",value:function(e,t,n){null!=n&&e.searchParams.append(t,String(n))}}],n&&ke(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();const je=Ee;function Pe(e,t,n,r,i,o,s){try{var a=e[o](s),c=a.value}catch(e){return void n(e)}a.done?t(c):Promise.resolve(c).then(r,i)}function Ce(e,t){for(var n=0;n{var[t,n]=e;return n})));qe.set("hello_utm",JSON.stringify(r))}}var t,n;return t=e,(n=[{key:"current",get:function(){try{return JSON.parse(qe.get("hello_utm"))||{}}catch(e){return{}}}}])&&Ne(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function De(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:null;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.utm=new Be,this._url=t}var t,n,r;return t=e,r=[{key:"getRootDomain",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;try{if(!e){var t;if("undefined"==typeof window||null===(t=window.location)||void 0===t||!t.hostname)return null;e=window.location.hostname}var n=e.split(".");if(n.length<=1)return e;for(var r of["myshopify.com","vtexcommercestable.com.br","myvtex.com","wixsite.com"]){var i=r.split(".");if(n.slice(-i.length).join(".")===r&&n.length>i.length)return".".concat(n.slice(-(i.length+1)).join("."))}var o=n[n.length-1],s=n[n.length-2];return n.length>2&&2===o.length&&s.length<=3?".".concat(n.slice(-3).join(".")):".".concat(n.slice(-2).join("."))}catch(e){return null}}}],(n=[{key:"url",get:function(){return null!==this._url&&void 0!==this._url?this._url:window.location.href}},{key:"title",get:function(){return document.title}},{key:"path",get:function(){if(this._url)try{return new URL(this._url).pathname}catch(e){return"/"}return window.location.pathname}},{key:"utmParams",get:function(){return this.utm.current}},{key:"trackingData",get:function(){return{page:{url:this.url,title:this.title,path:this.path},utm_params:this.utmParams}}},{key:"domain",get:function(){try{var t=this.url;if(!t)return null;var n=new URL(t).hostname;return e.getRootDomain(n)}catch(e){return null}}}])&&De(t.prototype,n),r&&De(t,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Ve(e,t){for(var n=0;n\n ".concat(Zt.business.locale.white_label.powered_by,'\n\n \n \n \n \n ')}});var ct=0;function lt(e){return"__private_"+ct+++"_"+e}var ut=lt("findOrCreateComponent"),ht=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),Object.defineProperty(this,ut,{value:dt}),this.data=t,this.element=n||document.querySelector('[data-hello-form="'.concat(this.id,'"]'))||document.createElement("form")}var t,n,r,i;return t=e,n=[{key:"mount",value:(r=function*(){var e,{ifCompleted:t=!0}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(t&&this.hasBeenCompleted)return null===(e=this.element)||void 0===e||e.remove(),Zt.eventEmitter.dispatch("form:completed",function(e){for(var t=1;t{this.element.setAttribute(e.name,e.value)})),document.contains(this.element)||document.body.appendChild(this.element),Zt.business.features.white_label||this.element.prepend(tt.build())},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){it(o,n,i,s,a,"next",e)}function a(e){it(o,n,i,s,a,"throw",e)}s(void 0)}))},function(){return i.apply(this,arguments)})},{key:"buildHeader",value:function(e){var t=at(this,ut)[ut]("[data-form-header]","header");t.innerHTML=e.content,this.element.querySelector("[data-form-header]")?this.element.querySelector("[data-form-header]").replaceWith(t):this.element.prepend(t)}},{key:"buildInputs",value:function(e){var t=at(this,ut)[ut]("[data-form-inputs]","main");e.map((e=>Ge.build(e))).forEach((e=>t.appendChild(e))),this.element.querySelector("[data-form-inputs]")?this.element.querySelector("[data-form-inputs]").replaceWith(t):this.element.querySelector("[data-form-header]").insertAdjacentHTML("afterend",t.outerHTML)}},{key:"buildButton",value:function(e){var t=at(this,ut)[ut]("[data-form-button]","button");t.innerText=e.text,t.setAttribute("data-action","click->hellotext--form#submit"),t.setAttribute("data-hellotext--form-target","button"),this.element.querySelector("[data-form-button]")?this.element.querySelector("[data-form-button]").replaceWith(t):this.element.querySelector("[data-form-inputs]").insertAdjacentHTML("afterend",t.outerHTML)}},{key:"buildFooter",value:function(e){var t=at(this,ut)[ut]("[data-form-footer]","footer");t.innerHTML=e.content,this.element.querySelector("[data-form-footer]")?this.element.querySelector("[data-form-footer]").replaceWith(t):this.element.appendChild(t)}},{key:"markAsCompleted",value:function(e){var t={state:"completed",id:this.id,data:e,completedAt:(new Date).getTime()};localStorage.setItem("hello-form-".concat(this.id),JSON.stringify(t)),Zt.eventEmitter.dispatch("form:completed",t)}},{key:"hasBeenCompleted",get:function(){return null!==localStorage.getItem("hello-form-".concat(this.id))}},{key:"id",get:function(){return this.data.id}},{key:"localeAuthKey",get:function(){var e=this.data.steps[0];return e.inputs.some((e=>"email"===e.kind))&&e.inputs.some((e=>"phone"===e.kind))?"phone_and_email":e.inputs.some((e=>"email"===e.kind))?"email":e.inputs.some((e=>"phone"===e.kind))?"phone":"none"}},{key:"elementAttributes",get:function(){return[{name:"data-controller",value:"hellotext--form"},{name:"data-hello-form",value:this.id},{name:"data-hellotext--form-data-value",value:JSON.stringify(this.data)}]}}],n&&ot(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function dt(e,t){var n=this.element.querySelector(e);if(n)return n.cloneNode(!0);var r=document.createElement(t);return r.setAttribute(e.replace("[","").replace("]",""),""),r}function ft(e){var t="function"==typeof Map?new Map:void 0;return ft=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return pt(e,arguments,gt(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),yt(r,e)},ft(e)}function pt(e,t,n){return pt=mt()?Reflect.construct.bind():function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&yt(i,n.prototype),i},pt.apply(null,arguments)}function mt(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function yt(e,t){return yt=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},yt(e,t)}function gt(e){return gt=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},gt(e)}var vt=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&yt(e,t)}(o,e);var t,n,r,i=(n=o,r=mt(),function(){var e,t=gt(n);if(r){var i=gt(this).constructor;e=Reflect.construct(t,arguments,i)}else e=t.apply(this,arguments);return function(e,t){if(t&&("object"==typeof t||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(this,e)});function o(){var e;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(e=i.call(this,"You need to initialize before tracking events. Call Hellotext.initialize and pass your public business id")).name="NotInitializedError",e}return t=o,Object.defineProperty(t,"prototype",{writable:!1}),t}(ft(Error));function bt(e,t,n,r,i,o,s){try{var a=e[o](s),c=a.value}catch(e){return void n(e)}a.done?t(c):Promise.resolve(c).then(r,i)}function wt(e,t){for(var n=0;n0&&this.collect()}},{key:"formMutationObserver",value:function(e){e.find((e=>"childList"===e.type&&e.addedNodes.length>0))&&Array.from(document.querySelectorAll("[data-hello-form]")).length>0&&this.collect()}},{key:"collect",value:(r=function*(){if(Zt.notInitialized)throw new vt;if(!this.fetching){if("undefined"==typeof document||!("querySelectorAll"in document))return console.warn("Document is not defined, collection is not possible. Please make sure to initialize the library after the document is loaded.");var e=function(e,t){if(!Object.prototype.hasOwnProperty.call(e,t))throw new TypeError("attempted to use private field on non-instance");return e}(this,St)[St];if(0!==e.length){var t=e.map((e=>me.get(e).then((e=>e.json()))));this.fetching=!0,yield Promise.all(t).then((e=>e.forEach(this.add))).then((()=>Zt.eventEmitter.dispatch("forms:collected",this))).then((()=>this.fetching=!1)),L.forms.autoMount&&this.forms.forEach((e=>e.mount()))}}},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){bt(o,n,i,s,a,"next",e)}function a(e){bt(o,n,i,s,a,"throw",e)}s(void 0)}))},function(){return i.apply(this,arguments)})},{key:"forEach",value:function(e){this.forms.forEach(e)}},{key:"map",value:function(e){return this.forms.map(e)}},{key:"add",value:function(e){this.includes(e.id)||(Zt.business.data||(Zt.business.setData(e.business),Zt.business.setLocale(P.toString())),Zt.business.enabledWhitelist||console.warn("No whitelist has been configured. It is advised to whitelist the domain to avoid bots from submitting forms."),this.forms.push(new ht(e)))}},{key:"getById",value:function(e){return this.forms.find((t=>t.id===e))}},{key:"getByIndex",value:function(e){return this.forms[e]}},{key:"includes",value:function(e){return this.forms.some((t=>t.id===e))}},{key:"excludes",value:function(e){return!this.includes(e)}},{key:"length",get:function(){return this.forms.length}}],n&&wt(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Et(){return Array.from(document.querySelectorAll("[data-hello-form]")).map((e=>e.dataset.helloForm)).filter(this.excludes)}function jt(e,t,n,r,i,o,s){try{var a=e[o](s),c=a.value}catch(e){return void n(e)}a.done?t(c):Promise.resolve(c).then(r,i)}function Pt(e,t){for(var n=0;nBt(e))).filter((e=>void 0!==e));if(e instanceof Date)return e.toISOString();if("object"==typeof e){var n=Object.keys(e).sort(((e,t)=>e.localeCompare(t))).reduce(((t,n)=>{var r=Bt(e[n]);return void 0!==r&&(t[n]=r),t}),{});return Object.keys(n).length>0?n:void 0}return"number"==typeof e||"boolean"==typeof e?e:void 0}}function Dt(e,t){var n=Bt(function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:{}))||{};return JSON.stringify(n)}function Rt(){return(Rt=It((function*(e){var t;if(null===(t=globalThis.crypto)||void 0===t||!t.subtle||"undefined"==typeof TextEncoder)return function(e){for(var t=5381,n=0;n>>0).toString(16))}(e);var n=yield globalThis.crypto.subtle.digest("SHA-256",(new TextEncoder).encode(e)),r=Array.from(new Uint8Array(n)).map((e=>e.toString(16).padStart(2,"0"))).join("");return"v1:".concat(r)}))).apply(this,arguments)}var Vt=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e)}var t,n,r;return t=e,null,n=[{key:"matches",value:function(e,t){return!!e&&e===t}},{key:"generate",value:(r=It((function*(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return yield function(e){return Rt.apply(this,arguments)}(Dt(e,t,n))})),function(e,t){return r.apply(this,arguments)})}],n&&Mt(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function qt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function $t(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};this.business=new Fe(e),L.assign(t),Je.initialize(),this.page=new Re,this.forms=new kt,this.query=new Q;var n=yield this.business.hydrate(),r=!1!==t.webchat&&this.mergeWebchatConfig(n&&n.webchat||{},t.webchat||{}),i=t.webchat&&!1!==t.webchat&&Object.prototype.hasOwnProperty.call(t.webchat,"behaviour");L.webchat.behaviourOverride=i,r&&r.id&&(L.webchat.assign(r),this.webchat=yield Ct.load(r.id)),"undefined"!=typeof MutationObserver&&this.forms.collectExistingFormsOnPage()})),function(e){return o.apply(this,arguments)})},{key:"mergeWebchatConfig",value:function(e,t){return this.deepMergePlainObjects(e,t)}},{key:"deepMergePlainObjects",value:function(e,t){var n=$t({},e);return Object.entries(t).forEach((e=>{var[t,r]=e;this.isPlainObject(r)&&this.isPlainObject(n[t])?n[t]=this.deepMergePlainObjects(n[t],r):n[t]=r})),n}},{key:"isPlainObject",value:function(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}},{key:"track",value:(i=Ut((function*(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(this.notInitialized)throw new vt;var n=$t($t({},t&&t.headers||{}),this.headers),r=$t($t({},At.identificationData),t.user_parameters||{}),i=t&&t.url?new Re(t.url):this.page,o=$t($t({session:this.session,user_parameters:r,action:e},t),i.trackingData);return delete o.headers,yield Me.events.create({headers:n,body:o})})),function(e){return i.apply(this,arguments)})},{key:"identify",value:(r=Ut((function*(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=yield Vt.generate(this.session,e,n);if(Vt.matches(At.fingerprint,r))return new ie(!0,{json:(t=Ut((function*(){return{already_identified:!0}})),function(){return t.apply(this,arguments)})});var i=yield Me.identifications.create($t({user_id:e},n));return i.succeeded&&At.remember(e,n.source,r),i})),function(e){return r.apply(this,arguments)})},{key:"forget",value:function(){At.forget()}},{key:"on",value:function(e,t){this.eventEmitter.addSubscriber(e,t)}},{key:"removeEventListener",value:function(e,t){this.eventEmitter.removeSubscriber(e,t)}},{key:"session",get:function(){return Je.session}},{key:"isInitialized",get:function(){return void 0!==Je.session}},{key:"notInitialized",get:function(){return!this.business||void 0===this.business.id}},{key:"headers",get:function(){if(this.notInitialized)throw new vt;return{Authorization:"Bearer ".concat(this.business.id),Accept:"application/json","Content-Type":"application/json"}}}],n&&zt(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();Jt.eventEmitter=new z,Jt.forms=void 0,Jt.business=void 0,Jt.webchat=void 0;const Zt=Jt;function Gt(e,t,n,r,i,o,s){try{var a=e[o](s),c=a.value}catch(e){return void n(e)}a.done?t(c):Promise.resolve(c).then(r,i)}function Qt(e,t){for(var n=0;n{var{type:t,parameter:n}=e,r=this.inputTargets.find((e=>e.name===n));r.setCustomValidity(Zt.business.locale.errors[t]),r.reportValidity(),r.addEventListener("input",(()=>{r.setCustomValidity(""),r.reportValidity()}))})),this.showErrorMessages();this.buttonTarget.style.display="none",this.element.querySelectorAll("input").forEach((e=>e.disabled=!0)),this.completed()},i=function(){var e=this,t=arguments;return new Promise((function(n,i){var o=r.apply(e,t);function s(e){Gt(o,n,i,s,a,"next",e)}function a(e){Gt(o,n,i,s,a,"throw",e)}s(void 0)}))},function(e){return i.apply(this,arguments)})},{key:"completed",value:function(){if(this.form.markAsCompleted(this.formData),!L.forms.shouldShowSuccessMessage)return this.element.remove();"string"==typeof L.forms.successMessage?this.element.innerHTML=L.forms.successMessage:this.element.innerHTML=Zt.business.locale.forms[this.form.localeAuthKey]}},{key:"showErrorMessages",value:function(){this.inputTargets.forEach((e=>{var t=e.closest("article").querySelector("[data-error-container]");e.validity.valid?t.innerText="":t.innerText=e.validationMessage}))}},{key:"clearErrorMessages",value:function(){this.inputTargets.forEach((e=>{e.setCustomValidity(""),e.closest("article").querySelector("[data-error-container]").innerText=""}))}},{key:"inputTargetConnected",value:function(e){e.getAttribute("data-default-value")&&(e.value=e.getAttribute("data-default-value"))}},{key:"requiredInputs",get:function(){return this.inputTargets.filter((e=>e.required))}},{key:"invalid",get:function(){return!this.element.checkValidity()}}],n&&Qt(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),c}(g.Qr);function nn(e,t){for(var n=0;n0?this.leftFadeTarget.classList.remove("hidden"):this.leftFadeTarget.classList.add("hidden"),ee.concat(t,t+"-"+an[0],t+"-"+an[1])),[]),ln=Math.min,un=Math.max,hn=Math.round,dn=Math.floor,fn=e=>({x:e,y:e}),pn={left:"right",right:"left",bottom:"top",top:"bottom"},mn={start:"end",end:"start"};function yn(e,t,n){return un(e,ln(t,n))}function gn(e,t){return"function"==typeof e?e(t):e}function vn(e){return e.split("-")[0]}function bn(e){return e.split("-")[1]}function wn(e){return"x"===e?"y":"x"}function On(e){return"y"===e?"height":"width"}const Tn=new Set(["top","bottom"]);function Sn(e){return Tn.has(vn(e))?"y":"x"}function kn(e){return wn(Sn(e))}function En(e,t,n){void 0===n&&(n=!1);const r=bn(e),i=kn(e),o=On(i);let s="x"===i?r===(n?"end":"start")?"right":"left":"start"===r?"bottom":"top";return t.reference[o]>t.floating[o]&&(s=Mn(s)),[s,Mn(s)]}function jn(e){return e.replace(/start|end/g,(e=>mn[e]))}const Pn=["left","right"],Cn=["right","left"],xn=["top","bottom"],An=["bottom","top"];function Mn(e){return e.replace(/left|right|bottom|top/g,(e=>pn[e]))}function _n(e){const{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function In(e,t,n){let{reference:r,floating:i}=e;const o=Sn(t),s=kn(t),a=On(s),c=vn(t),l="y"===o,u=r.x+r.width/2-i.width/2,h=r.y+r.height/2-i.height/2,d=r[a]/2-i[a]/2;let f;switch(c){case"top":f={x:u,y:r.y-i.height};break;case"bottom":f={x:u,y:r.y+r.height};break;case"right":f={x:r.x+r.width,y:h};break;case"left":f={x:r.x-i.width,y:h};break;default:f={x:r.x,y:r.y}}switch(bn(t)){case"start":f[s]-=d*(n&&l?-1:1);break;case"end":f[s]+=d*(n&&l?-1:1)}return f}async function Ln(e,t){var n;void 0===t&&(t={});const{x:r,y:i,platform:o,rects:s,elements:a,strategy:c}=e,{boundary:l="clippingAncestors",rootBoundary:u="viewport",elementContext:h="floating",altBoundary:d=!1,padding:f=0}=gn(t,e),p=function(e){return"number"!=typeof e?function(e){return{top:0,right:0,bottom:0,left:0,...e}}(e):{top:e,right:e,bottom:e,left:e}}(f),m=a[d?"floating"===h?"reference":"floating":h],y=_n(await o.getClippingRect({element:null==(n=await(null==o.isElement?void 0:o.isElement(m)))||n?m:m.contextElement||await(null==o.getDocumentElement?void 0:o.getDocumentElement(a.floating)),boundary:l,rootBoundary:u,strategy:c})),g="floating"===h?{x:r,y:i,width:s.floating.width,height:s.floating.height}:s.reference,v=await(null==o.getOffsetParent?void 0:o.getOffsetParent(a.floating)),b=await(null==o.isElement?void 0:o.isElement(v))&&await(null==o.getScale?void 0:o.getScale(v))||{x:1,y:1},w=_n(o.convertOffsetParentRelativeRectToViewportRelativeRect?await o.convertOffsetParentRelativeRectToViewportRelativeRect({elements:a,rect:g,offsetParent:v,strategy:c}):g);return{top:(y.top-w.top+p.top)/b.y,bottom:(w.bottom-y.bottom+p.bottom)/b.y,left:(y.left-w.left+p.left)/b.x,right:(w.right-y.right+p.right)/b.x}}const Fn=new Set(["left","top"]);function Nn(){return"undefined"!=typeof window}function Bn(e){return Vn(e)?(e.nodeName||"").toLowerCase():"#document"}function Dn(e){var t;return(null==e||null==(t=e.ownerDocument)?void 0:t.defaultView)||window}function Rn(e){var t;return null==(t=(Vn(e)?e.ownerDocument:e.document)||window.document)?void 0:t.documentElement}function Vn(e){return!!Nn()&&(e instanceof Node||e instanceof Dn(e).Node)}function qn(e){return!!Nn()&&(e instanceof Element||e instanceof Dn(e).Element)}function $n(e){return!!Nn()&&(e instanceof HTMLElement||e instanceof Dn(e).HTMLElement)}function Kn(e){return!(!Nn()||"undefined"==typeof ShadowRoot)&&(e instanceof ShadowRoot||e instanceof Dn(e).ShadowRoot)}const Hn=new Set(["inline","contents"]);function Un(e){const{overflow:t,overflowX:n,overflowY:r,display:i}=rr(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!Hn.has(i)}const zn=new Set(["table","td","th"]);function Wn(e){return zn.has(Bn(e))}const Jn=[":popover-open",":modal"];function Zn(e){return Jn.some((t=>{try{return e.matches(t)}catch(e){return!1}}))}const Gn=["transform","translate","scale","rotate","perspective"],Qn=["transform","translate","scale","rotate","perspective","filter"],Xn=["paint","layout","strict","content"];function Yn(e){const t=er(),n=qn(e)?rr(e):e;return Gn.some((e=>!!n[e]&&"none"!==n[e]))||!!n.containerType&&"normal"!==n.containerType||!t&&!!n.backdropFilter&&"none"!==n.backdropFilter||!t&&!!n.filter&&"none"!==n.filter||Qn.some((e=>(n.willChange||"").includes(e)))||Xn.some((e=>(n.contain||"").includes(e)))}function er(){return!("undefined"==typeof CSS||!CSS.supports)&&CSS.supports("-webkit-backdrop-filter","none")}const tr=new Set(["html","body","#document"]);function nr(e){return tr.has(Bn(e))}function rr(e){return Dn(e).getComputedStyle(e)}function ir(e){return qn(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function or(e){if("html"===Bn(e))return e;const t=e.assignedSlot||e.parentNode||Kn(e)&&e.host||Rn(e);return Kn(t)?t.host:t}function sr(e){const t=or(e);return nr(t)?e.ownerDocument?e.ownerDocument.body:e.body:$n(t)&&Un(t)?t:sr(t)}function ar(e,t,n){var r;void 0===t&&(t=[]),void 0===n&&(n=!0);const i=sr(e),o=i===(null==(r=e.ownerDocument)?void 0:r.body),s=Dn(i);if(o){const e=cr(s);return t.concat(s,s.visualViewport||[],Un(i)?i:[],e&&n?ar(e):[])}return t.concat(i,ar(i,[],n))}function cr(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function lr(e){const t=rr(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const i=$n(e),o=i?e.offsetWidth:n,s=i?e.offsetHeight:r,a=hn(n)!==o||hn(r)!==s;return a&&(n=o,r=s),{width:n,height:r,$:a}}function ur(e){return qn(e)?e:e.contextElement}function hr(e){const t=ur(e);if(!$n(t))return fn(1);const n=t.getBoundingClientRect(),{width:r,height:i,$:o}=lr(t);let s=(o?hn(n.width):n.width)/r,a=(o?hn(n.height):n.height)/i;return s&&Number.isFinite(s)||(s=1),a&&Number.isFinite(a)||(a=1),{x:s,y:a}}const dr=fn(0);function fr(e){const t=Dn(e);return er()&&t.visualViewport?{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}:dr}function pr(e,t,n,r){void 0===t&&(t=!1),void 0===n&&(n=!1);const i=e.getBoundingClientRect(),o=ur(e);let s=fn(1);t&&(r?qn(r)&&(s=hr(r)):s=hr(e));const a=function(e,t,n){return void 0===t&&(t=!1),!(!n||t&&n!==Dn(e))&&t}(o,n,r)?fr(o):fn(0);let c=(i.left+a.x)/s.x,l=(i.top+a.y)/s.y,u=i.width/s.x,h=i.height/s.y;if(o){const e=Dn(o),t=r&&qn(r)?Dn(r):r;let n=e,i=cr(n);for(;i&&r&&t!==n;){const e=hr(i),t=i.getBoundingClientRect(),r=rr(i),o=t.left+(i.clientLeft+parseFloat(r.paddingLeft))*e.x,s=t.top+(i.clientTop+parseFloat(r.paddingTop))*e.y;c*=e.x,l*=e.y,u*=e.x,h*=e.y,c+=o,l+=s,n=Dn(i),i=cr(n)}}return _n({width:u,height:h,x:c,y:l})}function mr(e,t){const n=ir(e).scrollLeft;return t?t.left+n:pr(Rn(e)).left+n}function yr(e,t,n){void 0===n&&(n=!1);const r=e.getBoundingClientRect();return{x:r.left+t.scrollLeft-(n?0:mr(e,r)),y:r.top+t.scrollTop}}const gr=new Set(["absolute","fixed"]);function vr(e,t,n){let r;if("viewport"===t)r=function(e,t){const n=Dn(e),r=Rn(e),i=n.visualViewport;let o=r.clientWidth,s=r.clientHeight,a=0,c=0;if(i){o=i.width,s=i.height;const e=er();(!e||e&&"fixed"===t)&&(a=i.offsetLeft,c=i.offsetTop)}return{width:o,height:s,x:a,y:c}}(e,n);else if("document"===t)r=function(e){const t=Rn(e),n=ir(e),r=e.ownerDocument.body,i=un(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),o=un(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight);let s=-n.scrollLeft+mr(e);const a=-n.scrollTop;return"rtl"===rr(r).direction&&(s+=un(t.clientWidth,r.clientWidth)-i),{width:i,height:o,x:s,y:a}}(Rn(e));else if(qn(t))r=function(e,t){const n=pr(e,!0,"fixed"===t),r=n.top+e.clientTop,i=n.left+e.clientLeft,o=$n(e)?hr(e):fn(1);return{width:e.clientWidth*o.x,height:e.clientHeight*o.y,x:i*o.x,y:r*o.y}}(t,n);else{const n=fr(e);r={x:t.x-n.x,y:t.y-n.y,width:t.width,height:t.height}}return _n(r)}function br(e,t){const n=or(e);return!(n===t||!qn(n)||nr(n))&&("fixed"===rr(n).position||br(n,t))}function wr(e,t,n){const r=$n(t),i=Rn(t),o="fixed"===n,s=pr(e,!0,o,t);let a={scrollLeft:0,scrollTop:0};const c=fn(0);function l(){c.x=mr(i)}if(r||!r&&!o)if(("body"!==Bn(t)||Un(i))&&(a=ir(t)),r){const e=pr(t,!0,o,t);c.x=e.x+t.clientLeft,c.y=e.y+t.clientTop}else i&&l();o&&!r&&i&&l();const u=!i||r||o?fn(0):yr(i,a);return{x:s.left+a.scrollLeft-c.x-u.x,y:s.top+a.scrollTop-c.y-u.y,width:s.width,height:s.height}}function Or(e){return"static"===rr(e).position}function Tr(e,t){if(!$n(e)||"fixed"===rr(e).position)return null;if(t)return t(e);let n=e.offsetParent;return Rn(e)===n&&(n=n.ownerDocument.body),n}function Sr(e,t){const n=Dn(e);if(Zn(e))return n;if(!$n(e)){let t=or(e);for(;t&&!nr(t);){if(qn(t)&&!Or(t))return t;t=or(t)}return n}let r=Tr(e,t);for(;r&&Wn(r)&&Or(r);)r=Tr(r,t);return r&&nr(r)&&Or(r)&&!Yn(r)?n:r||function(e){let t=or(e);for(;$n(t)&&!nr(t);){if(Yn(t))return t;if(Zn(t))return null;t=or(t)}return null}(e)||n}const kr={convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e;const o="fixed"===i,s=Rn(r),a=!!t&&Zn(t.floating);if(r===s||a&&o)return n;let c={scrollLeft:0,scrollTop:0},l=fn(1);const u=fn(0),h=$n(r);if((h||!h&&!o)&&(("body"!==Bn(r)||Un(s))&&(c=ir(r)),$n(r))){const e=pr(r);l=hr(r),u.x=e.x+r.clientLeft,u.y=e.y+r.clientTop}const d=!s||h||o?fn(0):yr(s,c,!0);return{width:n.width*l.x,height:n.height*l.y,x:n.x*l.x-c.scrollLeft*l.x+u.x+d.x,y:n.y*l.y-c.scrollTop*l.y+u.y+d.y}},getDocumentElement:Rn,getClippingRect:function(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e;const o=[..."clippingAncestors"===n?Zn(t)?[]:function(e,t){const n=t.get(e);if(n)return n;let r=ar(e,[],!1).filter((e=>qn(e)&&"body"!==Bn(e))),i=null;const o="fixed"===rr(e).position;let s=o?or(e):e;for(;qn(s)&&!nr(s);){const t=rr(s),n=Yn(s);n||"fixed"!==t.position||(i=null),(o?!n&&!i:!n&&"static"===t.position&&i&&gr.has(i.position)||Un(s)&&!n&&br(e,s))?r=r.filter((e=>e!==s)):i=t,s=or(s)}return t.set(e,r),r}(t,this._c):[].concat(n),r],s=o[0],a=o.reduce(((e,n)=>{const r=vr(t,n,i);return e.top=un(r.top,e.top),e.right=ln(r.right,e.right),e.bottom=ln(r.bottom,e.bottom),e.left=un(r.left,e.left),e}),vr(t,s,i));return{width:a.right-a.left,height:a.bottom-a.top,x:a.left,y:a.top}},getOffsetParent:Sr,getElementRects:async function(e){const t=this.getOffsetParent||Sr,n=this.getDimensions,r=await n(e.floating);return{reference:wr(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}},getClientRects:function(e){return Array.from(e.getClientRects())},getDimensions:function(e){const{width:t,height:n}=lr(e);return{width:t,height:n}},getScale:hr,isElement:qn,isRTL:function(e){return"rtl"===rr(e).direction}};function Er(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}const jr=function(e){return void 0===e&&(e=0),{name:"offset",options:e,async fn(t){var n,r;const{x:i,y:o,placement:s,middlewareData:a}=t,c=await async function(e,t){const{placement:n,platform:r,elements:i}=e,o=await(null==r.isRTL?void 0:r.isRTL(i.floating)),s=vn(n),a=bn(n),c="y"===Sn(n),l=Fn.has(s)?-1:1,u=o&&c?-1:1,h=gn(t,e);let{mainAxis:d,crossAxis:f,alignmentAxis:p}="number"==typeof h?{mainAxis:h,crossAxis:0,alignmentAxis:null}:{mainAxis:h.mainAxis||0,crossAxis:h.crossAxis||0,alignmentAxis:h.alignmentAxis};return a&&"number"==typeof p&&(f="end"===a?-1*p:p),c?{x:f*u,y:d*l}:{x:d*l,y:f*u}}(t,e);return s===(null==(n=a.offset)?void 0:n.placement)&&null!=(r=a.arrow)&&r.alignmentOffset?{}:{x:i+c.x,y:o+c.y,data:{...c,placement:s}}}}},Pr=function(e){return void 0===e&&(e={}),{name:"autoPlacement",options:e,async fn(t){var n,r,i;const{rects:o,middlewareData:s,placement:a,platform:c,elements:l}=t,{crossAxis:u=!1,alignment:h,allowedPlacements:d=cn,autoAlignment:f=!0,...p}=gn(e,t),m=void 0!==h||d===cn?function(e,t,n){return(e?[...n.filter((t=>bn(t)===e)),...n.filter((t=>bn(t)!==e))]:n.filter((e=>vn(e)===e))).filter((n=>!e||bn(n)===e||!!t&&jn(n)!==n))}(h||null,f,d):d,y=await Ln(t,p),g=(null==(n=s.autoPlacement)?void 0:n.index)||0,v=m[g];if(null==v)return{};const b=En(v,o,await(null==c.isRTL?void 0:c.isRTL(l.floating)));if(a!==v)return{reset:{placement:m[0]}};const w=[y[vn(v)],y[b[0]],y[b[1]]],O=[...(null==(r=s.autoPlacement)?void 0:r.overflows)||[],{placement:v,overflows:w}],T=m[g+1];if(T)return{data:{index:g+1,overflows:O},reset:{placement:T}};const S=O.map((e=>{const t=bn(e.placement);return[e.placement,t&&u?e.overflows.slice(0,2).reduce(((e,t)=>e+t),0):e.overflows[0],e.overflows]})).sort(((e,t)=>e[1]-t[1])),k=(null==(i=S.filter((e=>e[2].slice(0,bn(e[0])?2:3).every((e=>e<=0))))[0])?void 0:i[0])||S[0][0];return k!==a?{data:{index:g+1,overflows:O},reset:{placement:k}}:{}}}},Cr=function(e){return void 0===e&&(e={}),{name:"shift",options:e,async fn(t){const{x:n,y:r,placement:i}=t,{mainAxis:o=!0,crossAxis:s=!1,limiter:a={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...c}=gn(e,t),l={x:n,y:r},u=await Ln(t,c),h=Sn(vn(i)),d=wn(h);let f=l[d],p=l[h];if(o){const e="y"===d?"bottom":"right";f=yn(f+u["y"===d?"top":"left"],f,f-u[e])}if(s){const e="y"===h?"bottom":"right";p=yn(p+u["y"===h?"top":"left"],p,p-u[e])}const m=a.fn({...t,[d]:f,[h]:p});return{...m,data:{x:m.x-n,y:m.y-r,enabled:{[d]:o,[h]:s}}}}}},xr=function(e){return void 0===e&&(e={}),{name:"flip",options:e,async fn(t){var n,r;const{placement:i,middlewareData:o,rects:s,initialPlacement:a,platform:c,elements:l}=t,{mainAxis:u=!0,crossAxis:h=!0,fallbackPlacements:d,fallbackStrategy:f="bestFit",fallbackAxisSideDirection:p="none",flipAlignment:m=!0,...y}=gn(e,t);if(null!=(n=o.arrow)&&n.alignmentOffset)return{};const g=vn(i),v=Sn(a),b=vn(a)===a,w=await(null==c.isRTL?void 0:c.isRTL(l.floating)),O=d||(b||!m?[Mn(a)]:function(e){const t=Mn(e);return[jn(e),t,jn(t)]}(a)),T="none"!==p;!d&&T&&O.push(...function(e,t,n,r){const i=bn(e);let o=function(e,t,n){switch(e){case"top":case"bottom":return n?t?Cn:Pn:t?Pn:Cn;case"left":case"right":return t?xn:An;default:return[]}}(vn(e),"start"===n,r);return i&&(o=o.map((e=>e+"-"+i)),t&&(o=o.concat(o.map(jn)))),o}(a,m,p,w));const S=[a,...O],k=await Ln(t,y),E=[];let j=(null==(r=o.flip)?void 0:r.overflows)||[];if(u&&E.push(k[g]),h){const e=En(i,s,w);E.push(k[e[0]],k[e[1]])}if(j=[...j,{placement:i,overflows:E}],!E.every((e=>e<=0))){var P,C;const e=((null==(P=o.flip)?void 0:P.index)||0)+1,t=S[e];if(t&&("alignment"!==h||v===Sn(t)||j.every((e=>Sn(e.placement)!==v||e.overflows[0]>0))))return{data:{index:e,overflows:j},reset:{placement:t}};let n=null==(C=j.filter((e=>e.overflows[0]<=0)).sort(((e,t)=>e.overflows[1]-t.overflows[1]))[0])?void 0:C.placement;if(!n)switch(f){case"bestFit":{var x;const e=null==(x=j.filter((e=>{if(T){const t=Sn(e.placement);return t===v||"y"===t}return!0})).map((e=>[e.placement,e.overflows.filter((e=>e>0)).reduce(((e,t)=>e+t),0)])).sort(((e,t)=>e[1]-t[1]))[0])?void 0:x[0];e&&(n=e);break}case"initialPlacement":n=a}if(i!==n)return{reset:{placement:n}}}return{}}}};var Ar=e=>{Object.assign(e,{show(){var e;null===(e=this.cancelBehaviourOpen)||void 0===e||e.call(this),this.openValue=!0},hide(){this.openValue=!1},toggle(){var e;null===(e=this.cancelBehaviourOpen)||void 0===e||e.call(this),this.openValue=!this.openValue},setupFloatingUI(e){var{trigger:t,popover:n,strategy:r}=e;this.floatingUICleanup=function(e,t,n,r){void 0===r&&(r={});const{ancestorScroll:i=!0,ancestorResize:o=!0,elementResize:s="function"==typeof ResizeObserver,layoutShift:a="function"==typeof IntersectionObserver,animationFrame:c=!1}=r,l=ur(e),u=i||o?[...l?ar(l):[],...ar(t)]:[];u.forEach((e=>{i&&e.addEventListener("scroll",n,{passive:!0}),o&&e.addEventListener("resize",n)}));const h=l&&a?function(e,t){let n,r=null;const i=Rn(e);function o(){var e;clearTimeout(n),null==(e=r)||e.disconnect(),r=null}return function s(a,c){void 0===a&&(a=!1),void 0===c&&(c=1),o();const l=e.getBoundingClientRect(),{left:u,top:h,width:d,height:f}=l;if(a||t(),!d||!f)return;const p={rootMargin:-dn(h)+"px "+-dn(i.clientWidth-(u+d))+"px "+-dn(i.clientHeight-(h+f))+"px "+-dn(u)+"px",threshold:un(0,ln(1,c))||1};let m=!0;function y(t){const r=t[0].intersectionRatio;if(r!==c){if(!m)return s();r?s(!1,r):n=setTimeout((()=>{s(!1,1e-7)}),1e3)}1!==r||Er(l,e.getBoundingClientRect())||s(),m=!1}try{r=new IntersectionObserver(y,{...p,root:i.ownerDocument})}catch(e){r=new IntersectionObserver(y,p)}r.observe(e)}(!0),o}(l,n):null;let d,f=-1,p=null;s&&(p=new ResizeObserver((e=>{let[r]=e;r&&r.target===l&&p&&(p.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame((()=>{var e;null==(e=p)||e.observe(t)}))),n()})),l&&!c&&p.observe(l),p.observe(t));let m=c?pr(e):null;return c&&function t(){const r=pr(e);m&&!Er(m,r)&&n(),m=r,d=requestAnimationFrame(t)}(),n(),()=>{var e;u.forEach((e=>{i&&e.removeEventListener("scroll",n),o&&e.removeEventListener("resize",n)})),null==h||h(),null==(e=p)||e.disconnect(),p=null,c&&cancelAnimationFrame(d)}}(t,n,(()=>{((e,t,n)=>{const r=new Map,i={platform:kr,...n},o={...i.platform,_c:r};return(async(e,t,n)=>{const{placement:r="bottom",strategy:i="absolute",middleware:o=[],platform:s}=n,a=o.filter(Boolean),c=await(null==s.isRTL?void 0:s.isRTL(t));let l=await s.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:h}=In(l,r,c),d=r,f={},p=0;for(let n=0;n{var{x:t,y:r,strategy:i}=e,o={left:"".concat(t,"px"),top:"".concat(r,"px"),position:i};Object.assign(n.style,o)}))}))},openValueChanged(){this.disabledValue||(this.openValue?(this.popoverTarget.showPopover(),this.popoverTarget.setAttribute("aria-expanded","true"),this.onPopoverOpened&&this.onPopoverOpened()):(this.popoverTarget.hidePopover(),this.popoverTarget.removeAttribute("aria-expanded"),this.onPopoverClosed&&this.onPopoverClosed()))}})};function Mr(e,t,n,r,i,o,s){try{var a=e[o](s),c=a.value}catch(e){return void n(e)}a.done?t(c):Promise.resolve(c).then(r,i)}function _r(e){return function(){var t=this,n=arguments;return new Promise((function(r,i){var o=e.apply(t,n);function s(e){Mr(o,r,i,s,a,"next",e)}function a(e){Mr(o,r,i,s,a,"throw",e)}s(void 0)}))}}function Ir(e,t){for(var n=0;n{var[n,r]=e;t.searchParams.append(n,r)})),yield fetch(t,{method:"GET",headers:Zt.headers})})),function(e){return o.apply(this,arguments)})},{key:"create",value:(i=Rr((function*(e){var t=yield fetch(this.url,{method:"POST",headers:{Authorization:"Bearer ".concat(Zt.business.id)},body:e});return new ie(t.ok,t)})),function(e){return i.apply(this,arguments)})},{key:"markAsSeen",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=e?this.url+"/".concat(e):this.url+"/seen";fetch(t,{method:"PATCH",headers:Zt.headers,body:JSON.stringify({session:Zt.session})})}},{key:"url",get:function(){return e.endpoint.replace(":id",this.webchatId)}}],r=[{key:"endpoint",get:function(){return L.endpoint("public/webchats/:id/messages")}}],n&&Vr(t.prototype,n),r&&Vr(t,r),Object.defineProperty(t,"prototype",{writable:!1}),e}();function $r(e,t){for(var n=0;n{this.webSocket.send(JSON.stringify(i))}))}},{key:"onMessage",value:function(e){this.webSocket.addEventListener("message",(t=>{var n=JSON.parse(t.data),{type:r,message:i}=n;this.ignoredEvents.includes(r)||e(i)}))}},{key:"webSocket",get:function(){return e.webSocket?e.webSocket:e.webSocket=new WebSocket(L.actionCableUrl)}},{key:"ignoredEvents",get:function(){return["ping","confirm_subscription","welcome"]}}])&&$r(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();function Hr(e,t){for(var n=0;n{"message"===t.type&&e(t)}))}},{key:"onReaction",value:function(e){Ur(Wr(s.prototype),"onMessage",this).call(this,(t=>{"reaction.create"!==t.type&&"reaction.destroy"!==t.type||e(t)}))}},{key:"onTypingStart",value:function(e){Ur(Wr(s.prototype),"onMessage",this).call(this,(t=>{"started_typing"===t.type&&e(t)}))}},{key:"updateSubscriptionWith",value:function(e){this.unsubscribe(),setTimeout((()=>{this.conversation=e,this.subscribe()}),1e3)}}])&&Hr(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),s}(Kr);const Zr=Jr;var Gr=e=>{Object.assign(e,{scheduleBehaviourOpen(){if(this.shouldAutoOpenFromBehaviour()){var e=1e3*Number(this.behaviourValue.delay_seconds||0);this.behaviourOpenTimeout=window.setTimeout((()=>{this.behaviourOpenTimeout=null,this.openValue||(this.openValue=!0,this.markBehaviourAutoOpened())}),e)}},cancelBehaviourOpen(){window.clearTimeout(this.behaviourOpenTimeout),this.behaviourOpenTimeout=null},shouldAutoOpenFromBehaviour(){var e=this.behaviourValue;return!(!e||"on_load"!==e.trigger||e.first_visit_only&&localStorage.getItem(this.firstVisitKey())||e.once_per_session&&sessionStorage.getItem(this.sessionKey()))},markBehaviourAutoOpened(){this.behaviourValue.first_visit_only&&localStorage.setItem(this.firstVisitKey(),"1"),this.behaviourValue.once_per_session&&sessionStorage.setItem(this.sessionKey(),"1")},firstVisitKey(){return"hellotext--webchat--".concat(this.idValue,"--auto-opened")},sessionKey(){return"hellotext--webchat--".concat(this.idValue,"--auto-opened-session")}})},Qr=e=>{Object.assign(e,{setupOpeningSequence(){this.openingSequenceStarted=!1,this.openingSequenceCancelled=!1,this.openingSequenceTimeout=null,this.openingSequenceMessages=[],this.revealedOpeningSequenceMessageIds=[]},teardownOpeningSequence(){this.cancelOpeningSequence()},startOpeningSequence(){this.openingSequenceMessages=Array.from(this.openingSequenceMessageTargets||[]),this.openingSequenceCanStart()&&(this.openingSequenceStarted=!0,this.openingSequenceCancelled=!1,this.revealedOpeningSequenceMessageIds=[],this.playOpeningSequenceMessageAt(0))},openingSequenceCanStart(){return!this.conversationIdValue&&this.hasOpeningSequenceTarget&&this.openingSequenceMessages.length>0&&!this.openingSequenceStarted},playOpeningSequenceMessageAt(e){var t=this.openingSequenceMessages[e];if(t){var n=1e3*this.openingSequenceMessageDelay(t);this.openingSequenceTimeout=window.setTimeout((()=>{this.openingSequenceTimeout=null,this.openingSequenceCancelled||(this.revealOpeningSequenceMessage(t),this.playOpeningSequenceMessageAt(e+1))}),n)}},revealOpeningSequenceMessage(e){this.messagesContainerTarget.insertBefore(e,this.messageTemplateTarget),e.hidden=!1,this.recordOpeningSequenceMessage(e),this.scrollOpeningSequenceToBottom()},recordOpeningSequenceMessage(e){var t=e.dataset.openingSequenceMessageId;t&&!this.revealedOpeningSequenceMessageIds.includes(t)&&this.revealedOpeningSequenceMessageIds.push(t)},openingSequenceMessageDelay(e){var t=Number(e.dataset.delaySeconds||0);return Number.isFinite(t)?t:0},scrollOpeningSequenceToBottom(){this.messagesContainerTarget.scroll&&this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"smooth"})},cancelOpeningSequence(){this.openingSequenceCancelled=!0,null!==this.openingSequenceTimeout&&void 0!==this.openingSequenceTimeout&&(window.clearTimeout(this.openingSequenceTimeout),this.openingSequenceTimeout=null)},appendOpeningSequenceMessageIds(e){this.cancelOpeningSequence(),(this.revealedOpeningSequenceMessageIds||[]).forEach((t=>{e.append("message[opening_sequence_message_ids][]",t)}))},clearRevealedOpeningSequenceMessageIds(){this.revealedOpeningSequenceMessageIds=[]}})},Xr=e=>{Object.assign(e,{setupTeaser(){this.teaserCycleTimeout=null,this.teaserMessages=[],this.boundOnTeaserClick=this.boundOnTeaserClick||this.onTeaserClick.bind(this),this.hasTeaserTarget&&(this.teaserTarget.addEventListener("click",this.boundOnTeaserClick),this.startTeaserPresentation())},teardownTeaser(){this.stopTeaserCycle(),this.hasTeaserTarget&&this.boundOnTeaserClick&&this.teaserTarget.removeEventListener("click",this.boundOnTeaserClick)},collectTeaserMessages(){return this.hasTeaserTarget?Array.from(this.teaserTarget.querySelectorAll("[data-teaser-message]")):[]},startTeaserPresentation(){this.stopTeaserCycle(),this.teaserMessages=this.collectTeaserMessages(),this.hasTeaserTarget&&(0!==this.teaserMessages.length?this.openValue||this.conversationIdValue||this.hasRenderedConversationMessages()?this.dismissTeaserForSession():this.teaserSeenForSession()?this.hideTeaser():(this.teaserTarget.classList.remove("invisible"),this.showTeaserMessage(0),this.teaserMessages.length<2||this.scheduleNextTeaserMessage(0)):this.hideTeaser())},scheduleNextTeaserMessage(e){var t=e+1;if(!(t>=this.teaserMessages.length)){var n=this.teaserMessages[e],r=this.teaserPresentationDelay(n);this.teaserCycleTimeout=window.setTimeout((()=>{this.teaserCycleTimeout=null,this.showTeaserMessage(t),this.scheduleNextTeaserMessage(t)}),r)}},showTeaserMessage(e){this.teaserMessages.forEach(((t,n)=>{t.classList.toggle("hidden",n!==e)}))},stopTeaserCycle(){null!==this.teaserCycleTimeout&&void 0!==this.teaserCycleTimeout&&(window.clearTimeout(this.teaserCycleTimeout),this.teaserCycleTimeout=null)},teaserMessageDelay(e){var t=Number(e.dataset.delaySeconds||0);return Number.isFinite(t)?t:0},teaserPresentationDelay(e){return Math.max(1e3*this.teaserMessageDelay(e),250)},hasRenderedConversationMessages(){var e=[];try{e=Array.from(this.messageTargets||[])}catch(t){e=[]}return e.some((e=>e!==this.messageTemplateTarget))},teaserSeenKey(){return"hellotext:webchat:".concat(this.idValue||this.element.id,":teaser-seen")},teaserSeenForSession(){try{return"true"===window.sessionStorage.getItem(this.teaserSeenKey())}catch(e){return!1}},markTeaserSeenForSession(){try{window.sessionStorage.setItem(this.teaserSeenKey(),"true")}catch(e){}},dismissTeaserForSession(){this.markTeaserSeenForSession(),this.hideTeaser()},hideTeaser(){this.stopTeaserCycle(),this.hasTeaserTarget&&this.teaserTarget.classList.add("invisible")},onTeaserClick(e){e.target.closest("a")||(this.dismissTeaserForSession(),this.show())}})};function Yr(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function ei(e){for(var t=1;t{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})}));var t=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout((()=>{this.clearTypingIndicator()}),t)}},{key:"resetTypingIndicatorTimer",value:function(){if(this.typingIndicatorVisible){clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout);var e=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout((()=>{this.clearTypingIndicator()}),e)}}},{key:"clearTypingIndicator",value:function(){this.hasTypingIndicatorTarget&&this.typingIndicatorTarget.remove(),this.typingIndicatorVisible=!1,clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout)}},{key:"onMessageInputChange",value:function(){this.resizeInput(),clearTimeout(this.typingIndicatorTimeout),this.hasSentTypingIndicator||(this.webChatChannel.startTypingIndicator(),this.hasSentTypingIndicator=!0),this.typingIndicatorTimeout=setTimeout((()=>{this.hasSentTypingIndicator=!1}),3e3)}},{key:"onOutboundMessageSent",value:function(e){var{data:t}=e,n={"message:sent":e=>{var t=(new DOMParser).parseFromString(e.element,"text/html").body.firstElementChild;this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(t,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(t),t.scrollIntoView({behavior:"instant"})},"message:failed":e=>{var t;null===(t=this.messagesContainerTarget.querySelector("#".concat(e.id)))||void 0===t||t.classList.add("failed")}};n[t.type]?n[t.type](t):console.log("Unhandled message event: ".concat(t.type))}},{key:"onScroll",value:(s=ri((function*(){if(!(this.messagesContainerTarget.scrollTop>300||!this.nextPageValue||this.fetchingNextPage)){this.fetchingNextPage=!0;var e=yield this.messagesAPI.index({page:this.nextPageValue,session:Zt.session}),{next:t,messages:n}=yield e.json();this.nextPageValue=t,this.oldScrollHeight=this.messagesContainerTarget.scrollHeight,n.forEach((e=>{var{body:t,attachments:n}=e,r=document.createElement("div");r.innerHTML=t;var i=this.messageTemplateTarget.cloneNode(!0);i.setAttribute("data-hellotext--webchat-target","message"),i.style.removeProperty("display"),i.querySelector("[data-body]").innerHTML=r.innerHTML,"received"===e.state?i.classList.add("received"):i.classList.remove("received"),n&&n.forEach((e=>{var t,n=this.attachmentImageTarget.cloneNode(!0);n.removeAttribute("data-hellotext--webchat-target"),n.src=e,n.style.display="block",null===(t=this.messageAttachmentsContainer(i))||void 0===t||t.appendChild(n)})),i.setAttribute("data-body",t),this.messagesContainerTarget.prepend(i)})),this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight-this.oldScrollHeight,behavior:"instant"}),this.fetchingNextPage=!1}})),function(){return s.apply(this,arguments)})},{key:"onClickOutside",value:function(e){_.mode===M.POPOVER&&this.openValue&&e.target.nodeType&&!1===this.element.contains(e.target)&&(this.openValue=!1)}},{key:"closePopover",value:function(){this.popoverTarget.classList.add(...this.fadeOutClasses),setTimeout((()=>{this.openValue=!1}),250)}},{key:"onPopoverOpened",value:function(){var e;this.popoverTarget.classList.remove(...this.fadeOutClasses),null===(e=this.dismissTeaserForSession)||void 0===e||e.call(this),this.onMobile||this.inputTarget.focus(),this.scrolled||(requestAnimationFrame((()=>{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})})),this.scrolled=!0),Zt.eventEmitter.dispatch("webchat:opened"),localStorage.setItem("hellotext--webchat--".concat(this.idValue),"opened"),this.messageTeaserValue&&(this.messageTeaserValue=null),this.startOpeningSequence(),"none"!==this.unreadCounterTarget.style.display&&(this.unreadCounterTarget.style.display="none",this.unreadCounterTarget.innerText="0",this.messagesAPI.markAsSeen())}},{key:"onPopoverClosed",value:function(){Zt.eventEmitter.dispatch("webchat:closed"),localStorage.setItem("hellotext--webchat--".concat(this.idValue),"closed")}},{key:"onMessageReaction",value:function(e){var{message:t,reaction:n,type:r}=e,i=this.messageTargets.find((e=>e.dataset.id===t)).querySelector("[data-reactions]");if("reaction.destroy"===r)return i.querySelector('[data-id="'.concat(n.id,'"]')).remove();if(i.querySelector('[data-id="'.concat(n.id,'"]')))i.querySelector('[data-id="'.concat(n.id,'"]')).innerText=n.emoji;else{var o=document.createElement("span");o.innerText=n.emoji,o.setAttribute("data-id",n.id),i.appendChild(o)}}},{key:"onMessageReceived",value:function(e){var t,{id:n,body:r,attachments:i,teaser:o}=e;if(this.claimMessageId(n)){if(null===(t=this.hideTeaser)||void 0===t||t.call(this),e.carousel)return this.insertCarouselMessage(e);var s=document.createElement("div");s.innerHTML=r;var a=this.messageTemplateTarget.cloneNode(!0);a.style.display="flex",a.querySelector("[data-body]").innerHTML=s.innerHTML,a.setAttribute("data-id",n),a.setAttribute("data-hellotext--webchat-target","message"),i&&i.forEach((e=>{var t,n=this.attachmentImageTarget.cloneNode(!0);n.src=e,n.style.display="block",null===(t=this.messageAttachmentsContainer(a))||void 0===t||t.appendChild(n)})),this.clearTypingIndicator(),this.messagesContainerTarget.appendChild(a),Zt.eventEmitter.dispatch("webchat:message:received",ei(ei({},e),{},{body:a.querySelector("[data-body]").innerText})),a.scrollIntoView({behavior:"smooth"}),this.updateMessageTeaser(o),this.openValue?this.messagesAPI.markAsSeen(n):this.incrementUnreadCounter()}}},{key:"claimMessageId",value:function(e){var t=this.messageTargets||[];return!this.messageIds.has(e)&&(this.messageIds.add(e),!t.some((t=>t.dataset.id===e)))}},{key:"updateMessageTeaser",value:function(e){this.messageTeaserValue=e,this.messageTeaserValue&&this.hasTeaserTarget&&(this.teaserTarget.innerHTML=this.messageTeaserValue,this.teaserTarget.classList.toggle("invisible",this.openValue))}},{key:"insertCarouselMessage",value:function(e){var t,n=e.html,r=(new DOMParser).parseFromString(n,"text/html").body.firstElementChild;r.setAttribute("data-id",e.id),r.setAttribute("data-hellotext--webchat-target","message"),this.clearTypingIndicator(),this.messagesContainerTarget.appendChild(r),r.scrollIntoView({behavior:"smooth"}),Zt.eventEmitter.dispatch("webchat:message:received",ei(ei({},e),{},{body:(null===(t=r.querySelector("[data-body]"))||void 0===t?void 0:t.innerText)||""})),this.updateMessageTeaser(e.teaser),this.openValue?this.messagesAPI.markAsSeen(e.id):this.incrementUnreadCounter()}},{key:"resizeInput",value:function(){this.inputTarget.style.height="auto";var e=this.inputTarget.scrollHeight;this.inputTarget.style.height="".concat(Math.min(e,96),"px")}},{key:"sendQuickReplyMessage",value:(o=ri((function*(e){var t,n,{detail:{id:r,product:i,buttonId:o,body:s,cardElement:a}}=e;null===(t=this.dismissTeaserForSession)||void 0===t||t.call(this);var c=new FormData;c.append("message[body]",s),c.append("message[replied_to]",r),c.append("message[product]",i),c.append("message[button]",o),c.append("session",Zt.session),c.append("locale",P.toString()),this.appendOpeningSequenceMessageIds(c);var l,u=this.buildMessageElement(),h=null===(n=a.querySelector("img"))||void 0===n?void 0:n.cloneNode(!0);u.querySelector("[data-body]").innerText=s,h&&(h.removeAttribute("width"),h.removeAttribute("height"),null===(l=this.messageAttachmentsContainer(u))||void 0===l||l.appendChild(h)),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(u,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(u),u.scrollIntoView({behavior:"smooth"}),this.broadcastChannel.postMessage({type:"message:sent",element:u.outerHTML});var d=yield this.messagesAPI.create(c);if(d.failed)return clearTimeout(this.optimisticTypingTimeout),this.broadcastChannel.postMessage({type:"message:failed",id:u.id}),u.classList.add("failed");var f=yield d.json();this.dispatch("set:id",{target:u,detail:f.id}),this.clearRevealedOpeningSequenceMessageIds();var p={id:f.id,body:s,attachments:h?[h.src]:[],replied_to:r,product:i,button:o,type:"quick_reply"};Zt.eventEmitter.dispatch("webchat:message:sent",p)})),function(e){return o.apply(this,arguments)})},{key:"sendTeaserQuickReply",value:(i=ri((function*(e){var t;e.preventDefault(),e.stopPropagation();var n=e.currentTarget,r=(n.dataset.value||"").trim(),i=[n.dataset.text,n.textContent].map((e=>(e||"").trim())).find((e=>e.length>0)),o=r||i;if(o){null===(t=this.dismissTeaserForSession)||void 0===t||t.call(this),this.show();var s=(n.dataset.type||"").trim()||"quick_reply",a=new FormData;a.append("message[body]",o),a.append("session",Zt.session),a.append("locale",P.toString()),this.appendOpeningSequenceMessageIds(a);var c=this.buildMessageElement();c.querySelector("[data-body]").innerText=o,this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(c,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(c),c.scrollIntoView({behavior:"smooth"}),this.broadcastChannel.postMessage({type:"message:sent",element:c.outerHTML}),this.typingIndicatorVisible||(clearTimeout(this.optimisticTypingTimeout),this.optimisticTypingTimeout=setTimeout((()=>{this.showOptimisticTypingIndicator()}),this.optimisticTypingIndicatorWaitValue));var l=yield this.messagesAPI.create(a);if(l.failed)return clearTimeout(this.optimisticTypingTimeout),this.broadcastChannel.postMessage({type:"message:failed",id:c.id}),c.classList.add("failed");var u=yield l.json();c.setAttribute("data-id",u.id),this.clearRevealedOpeningSequenceMessageIds(),Zt.eventEmitter.dispatch("webchat:message:sent",{id:u.id,body:o,attachments:[],type:"quick_reply",teaser:{text:i||o,value:r||o,type:s}}),u.conversation&&u.conversation!==this.conversationIdValue&&(this.conversationIdValue=u.conversation,this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)),this.typingIndicatorVisible&&this.resetTypingIndicatorTimer()}})),function(e){return i.apply(this,arguments)})},{key:"sendMessage",value:(r=ri((function*(e){var t,n={body:this.inputTarget.value,attachments:this.files};if(0!==this.inputTarget.value.trim().length||0!==this.files.length){null===(t=this.dismissTeaserForSession)||void 0===t||t.call(this);var r=new FormData;this.inputTarget.value.trim().length>0?r.append("message[body]",this.inputTarget.value):delete n.body,this.files.forEach((e=>{r.append("message[attachments][]",e)})),r.append("session",Zt.session),r.append("locale",P.toString()),this.appendOpeningSequenceMessageIds(r);var i=this.buildMessageElement();this.inputTarget.value.trim().length>0?i.querySelector("[data-body]").innerText=this.inputTarget.value:i.querySelector("[data-message-bubble]").remove();var o=this.attachmentContainerTarget.querySelectorAll("img");o.length>0&&o.forEach((e=>{var t;null===(t=this.messageAttachmentsContainer(i))||void 0===t||t.appendChild(e.cloneNode(!0))})),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(i,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(i),i.scrollIntoView({behavior:"smooth"}),this.broadcastChannel.postMessage({type:"message:sent",element:i.outerHTML}),this.inputTarget.value="",this.resizeInput(),this.files=[],this.attachmentInputTarget.value="",this.attachmentContainerTarget.innerHTML="",this.attachmentContainerTarget.style.display="none",this.errorMessageContainerTarget.style.display="none",this.inputTarget.focus(),this.typingIndicatorVisible||(clearTimeout(this.optimisticTypingTimeout),this.optimisticTypingTimeout=setTimeout((()=>{this.showOptimisticTypingIndicator()}),this.optimisticTypingIndicatorWaitValue));var s=yield this.messagesAPI.create(r);if(s.failed)return clearTimeout(this.optimisticTypingTimeout),this.broadcastChannel.postMessage({type:"message:failed",id:i.id}),i.classList.add("failed");var a=yield s.json();i.setAttribute("data-id",a.id),n.id=a.id,this.clearRevealedOpeningSequenceMessageIds(),Zt.eventEmitter.dispatch("webchat:message:sent",n),a.conversation!==this.conversationIdValue&&(this.conversationIdValue=a.conversation,this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)),this.typingIndicatorVisible&&this.resetTypingIndicatorTimer(),this.attachmentContainerTarget.style.display=""}else e&&e.target&&e.preventDefault()})),function(e){return r.apply(this,arguments)})},{key:"buildMessageElement",value:function(){var e=this.messageTemplateTarget.cloneNode(!0);return e.id="hellotext--webchat--".concat(this.idValue,"--message--").concat(Date.now()),e.classList.add("received"),e.style.removeProperty("display"),e.setAttribute("data-controller","hellotext--message"),e.setAttribute("data-hellotext--webchat-target","message"),e}},{key:"messageAttachmentsContainer",value:function(e){return e.querySelector("[data-attachments-container], [data-attachment-container]")}},{key:"incrementUnreadCounter",value:function(){this.unreadCounterTarget.style.display="flex";var e=(parseInt(this.unreadCounterTarget.innerText)||0)+1;this.unreadCounterTarget.innerText=Math.min(e,9)}},{key:"openAttachment",value:function(){this.attachmentInputTarget.click()}},{key:"onFileInputChange",value:function(){this.errorMessageContainerTarget.style.display="none";var e=Array.from(this.attachmentInputTarget.files);this.attachmentInputTarget.value="";var t=e.find((e=>{var t=e.type.split("/")[0];return["image","video","audio"].includes(t)?this.mediaValue[t].max_sizethis.createAttachmentElement(e))),this.inputTarget.focus()}},{key:"createAttachmentElement",value:function(e){var t=this.attachmentElement();if(this.attachmentContainerTarget.style.display="",t.setAttribute("data-name",e.name),e.type.startsWith("image/")){var n=this.attachmentImageTarget.cloneNode(!0);n.src=URL.createObjectURL(e),n.style.display="block",t.appendChild(n),this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}else{var r=t.querySelector("main");r.style.height="5rem",r.style.borderRadius="0.375rem",r.style.backgroundColor="#e5e7eb",r.style.padding="0.25rem",t.querySelector("p[data-attachment-name]").innerText=e.name,this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}}},{key:"removeAttachment",value:function(e){var{currentTarget:t}=e,n=t.closest("[data-hellotext--webchat-target='attachment']");this.files=this.files.filter((e=>e.name!==n.dataset.name)),this.attachmentInputTarget.value="",n.remove(),this.inputTarget.focus()}},{key:"attachmentTargetDisconnected",value:function(){0===this.attachmentTargets.length&&(this.attachmentContainerTarget.innerHTML="",this.attachmentContainerTarget.style.display="none")}},{key:"attachmentElement",value:function(){var e=this.attachmentTemplateTarget.cloneNode(!0);return e.removeAttribute("hidden"),e.style.display="flex",e.setAttribute("data-hellotext--webchat-target","attachment"),e}},{key:"onEmojiSelect",value:function(e){var{detail:t}=e,n=this.inputTarget.value,r=this.inputTarget.selectionStart,i=this.inputTarget.selectionEnd;this.inputTarget.value=n.slice(0,r)+t+n.slice(i),this.inputTarget.selectionStart=this.inputTarget.selectionEnd=r+t.length,this.inputTarget.focus()}},{key:"byteToMegabyte",value:function(e){return Math.ceil(e/1024/1024)}},{key:"middlewares",get:function(){return[jr(this.offsetValue),Cr({padding:this.paddingValue}),xr()]}},{key:"shouldOpenOnMount",get:function(){return"opened"===localStorage.getItem("hellotext--webchat--".concat(this.idValue))&&!this.onMobile}},{key:"onMobile",get:function(){return window.matchMedia("(max-width: ".concat(this.fullScreenThresholdValue,"px)")).matches}}],n&&ii(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),u}(g.Qr);li.values={id:String,conversationId:String,media:Object,fileSizeErrorMessage:String,placement:{type:String,default:"bottom-end"},open:{type:Boolean,default:!1},autoPlacement:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},nextPage:{type:Number,default:void 0},fullScreenThreshold:{type:Number,default:1024},typingIndicatorKeepAlive:{type:Number,default:3e4},offset:{type:Number,default:24},padding:{type:Number,default:24},optimisticTypingIndicatorWait:{type:Number,default:1e3},teaser:Object,messageTeaser:String,behaviour:Object},li.classes=["fadeOut"],li.targets=["trigger","popover","input","attachmentInput","attachmentButton","errorMessageContainer","attachmentTemplate","attachmentContainer","attachment","messageTemplate","messagesContainer","title","attachmentImage","footer","toolbar","message","unreadCounter","typingIndicator","typingIndicatorTemplate","teaser","openingSequence","openingSequenceMessage"];var ui=g.Mx.start();ui.register("hellotext--form",tn),ui.register("hellotext--webchat",li),ui.register("hellotext--webchat--emoji",Br),ui.register("hellotext--message",sn),window.Hellotext=Zt;const hi=Zt},989:(e,t,n)=>{n.d(t,{Z:()=>a});var r=n(81),i=n.n(r),o=n(645),s=n.n(o)()(i());s.push([e.id,"form[data-hello-form] {\n position: relative;\n}\n\nform[data-hello-form] article [data-error-container] {\n font-size: 0.875rem;\n line-height: 1.25rem;\n display: none;\n}\n\nform[data-hello-form] article:has(input:invalid) [data-error-container] {\n display: block;\n}\n\nform[data-hello-form] [data-logo-container] {\n display: flex;\n justify-content: center;\n align-items: flex-end;\n position: absolute;\n right: 1rem;\n bottom: 1rem;\n}\n\nform[data-hello-form] [data-logo-container] small {\n margin: 0 0.3rem;\n}\n\nform[data-hello-form] [data-logo-container] [data-hello-brand] {\n width: 4rem;\n}\n",""]);const a=s},645:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,i,o){"string"==typeof e&&(e=[[null,e,void 0]]);var s={};if(r)for(var a=0;a0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=o),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),i&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=i):u[4]="".concat(i)),t.push(u))}},t}},81:e=>{e.exports=function(e){return e[1]}},379:e=>{var t=[];function n(e){for(var n=-1,r=0;r{var t={};e.exports=function(e,n){var r=function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}t[e]=n}return t[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(n)}},216:e=>{e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},565:(e,t,n)=>{e.exports=function(e){var t=n.nc;t&&e.setAttribute("nonce",t)}},795:e=>{e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var t=e.insertStyleElement(e);return{update:function(n){!function(e,t,n){var r="";n.supports&&(r+="@supports (".concat(n.supports,") {")),n.media&&(r+="@media ".concat(n.media," {"));var i=void 0!==n.layer;i&&(r+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),r+=n.css,i&&(r+="}"),n.media&&(r+="}"),n.supports&&(r+="}");var o=n.sourceMap;o&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),t.styleTagTransform(r,e,t.options)}(t,e,n)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},589:e=>{e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}}},o={};function s(e){var t=o[e];if(void 0!==t)return t.exports;var n=o[e]={id:e,exports:{}};return i[e](n,n.exports,s),n.exports}s.m=i,s.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,s.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var i=Object.create(null);s.r(i);var o={};e=e||[null,t({}),t([]),t(t)];for(var a=2&r&&n;"object"==typeof a&&!~e.indexOf(a);a=t(a))Object.getOwnPropertyNames(a).forEach((e=>o[e]=()=>n[e]));return o.default=()=>n,s.d(i,o),i},s.d=(e,t)=>{for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.f={},s.e=e=>Promise.all(Object.keys(s.f).reduce(((t,n)=>(s.f[n](e,t),t)),[])),s.u=e=>({34:"webchat-emoji-en",309:"webchat-emoji-es",853:"webchat-emoji"}[e]+".js"),s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n={},r="Hellotext:",s.l=(e,t,i,o)=>{if(n[e])n[e].push(t);else{var a,c;if(void 0!==i)for(var l=document.getElementsByTagName("script"),u=0;u{a.onerror=a.onload=null,clearTimeout(f);var i=n[e];if(delete n[e],a.parentNode&&a.parentNode.removeChild(a),i&&i.forEach((e=>e(r))),t)return t(r)},f=setTimeout(d.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=d.bind(null,a.onerror),a.onload=d.bind(null,a.onload),c&&document.head.appendChild(a)}},s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;s.g.importScripts&&(e=s.g.location+"");var t=s.g.document;if(!e&&t&&(t.currentScript&&(e=t.currentScript.src),!e)){var n=t.getElementsByTagName("script");n.length&&(e=n[n.length-1].src)}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),s.p=e})(),(()=>{var e={179:0};s.f.j=(t,n)=>{var r=s.o(e,t)?e[t]:void 0;if(0!==r)if(r)n.push(r[2]);else{var i=new Promise(((n,i)=>r=e[t]=[n,i]));n.push(r[2]=i);var o=s.p+s.u(t),a=new Error;s.l(o,(n=>{if(s.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var i=n&&("load"===n.type?"missing":n.type),o=n&&n.target&&n.target.src;a.message="Loading chunk "+t+" failed.\n("+i+": "+o+")",a.name="ChunkLoadError",a.type=i,a.request=o,r[1](a)}}),"chunk-"+t,t)}};var t=(t,n)=>{var r,i,[o,a,c]=n,l=0;if(o.some((t=>0!==e[t]))){for(r in a)s.o(a,r)&&(s.m[r]=a[r]);c&&c(s)}for(t&&t(n);l{e.exports=JSON.parse('{"search":"Search","search_no_results_1":"Oh no!","search_no_results_2":"That emoji couldn’t be found","pick":"Pick an emoji…","add_custom":"Add custom emoji","categories":{"activity":"Activity","custom":"Custom","flags":"Flags","foods":"Food & Drink","frequent":"Frequently used","nature":"Animals & Nature","objects":"Objects","people":"Smileys & People","places":"Travel & Places","search":"Search Results","symbols":"Symbols"},"skins":{"1":"Default","2":"Light","3":"Medium-Light","4":"Medium","5":"Medium-Dark","6":"Dark","choose":"Choose default skin tone"}}')}}]);
\ No newline at end of file
diff --git a/dist/webchat-emoji-es.js b/dist/webchat-emoji-es.js
new file mode 100644
index 0000000..f631558
--- /dev/null
+++ b/dist/webchat-emoji-es.js
@@ -0,0 +1 @@
+"use strict";(Object("undefined"!=typeof self?self:this).webpackChunkHellotext=Object("undefined"!=typeof self?self:this).webpackChunkHellotext||[]).push([[309],{378:e=>{e.exports=JSON.parse('{"search":"Buscar","search_no_results_1":"Vaya!","search_no_results_2":"Ese emoji no se pudo encontrar","pick":"Elige un emoji…","add_custom":"Añadir emoji personalizado","categories":{"activity":"Actividades","custom":"Personalizados","flags":"Banderas","foods":"Comida y Bebida","frequent":"Usados con frecuencia","nature":"Animales y Naturaleza","objects":"Objetos","people":"Emoticonos y Personas","places":"Viajes y Destinos","search":"Resultados de la búsqueda","symbols":"Símbolos"},"skins":{"1":"Sin tono","2":"Claro","3":"Medio-Claro","4":"Medio","5":"Medio-Oscuro","6":"Oscuro","choose":"Elige el tono de piel predeterminado"}}')}}]);
\ No newline at end of file
diff --git a/dist/webchat-emoji.js b/dist/webchat-emoji.js
new file mode 100644
index 0000000..d859a45
--- /dev/null
+++ b/dist/webchat-emoji.js
@@ -0,0 +1 @@
+"use strict";(Object("undefined"!=typeof self?self:this).webpackChunkHellotext=Object("undefined"!=typeof self?self:this).webpackChunkHellotext||[]).push([[853],{431:(e,t,n)=>{function o(e){return e&&e.__esModule?e.default:e}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n.r(t),n.d(t,{Data:()=>Z,Emoji:()=>ve,FrequentlyUsed:()=>G,I18n:()=>Y,Picker:()=>et,SafeFlags:()=>ce,SearchIndex:()=>le,Store:()=>V,getEmojiDataFromNative:()=>he,init:()=>ie});var r,s,a,l,c,d,h={},u=[],p=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function f(e,t){for(var n in t)e[n]=t[n];return e}function _(e){var t=e.parentNode;t&&t.removeChild(e)}function g(e,t,n){var o,i,s,a={};for(s in t)"key"==s?o=t[s]:"ref"==s?i=t[s]:a[s]=t[s];if(arguments.length>2&&(a.children=arguments.length>3?r.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(s in e.defaultProps)void 0===a[s]&&(a[s]=e.defaultProps[s]);return m(e,a,o,i,null)}function m(e,t,n,o,i){var r={type:e,props:t,key:n,ref:o,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==i?++a:i};return null==i&&null!=s.vnode&&s.vnode(r),r}function v(e){return e.children}function b(e,t){this.props=e,this.context=t}function k(e,t){if(null==t)return e.__?k(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?m(_.type,_.props,_.key,null,_.__v):_)){if(_.__=n,_.__b=n.__b+1,null===(f=y[d])||f&&_.key==f.key&&_.type===f.type)y[d]=void 0;else for(p=0;p{let e=null;try{navigator.userAgent.includes("jsdom")||(e=document.createElement("canvas").getContext("2d",{willReadFrequently:!0}))}catch{}if(!e)return()=>!1;const t=20,n=Math.floor(12.5);return e.font=n+"px Arial, Sans-Serif",e.textBaseline="top",e.canvas.width=40,e.canvas.height=25,n=>{e.clearRect(0,0,40,25),e.fillStyle="#FF0000",e.fillText(n,0,22),e.fillStyle="#0000FF",e.fillText(n,t,22);const o=e.getImageData(0,0,t,25).data,i=o.length;let r=0;for(;r=i)return!1;const s=t+r/4%t,a=Math.floor(r/4/t),l=e.getImageData(s,a,1,1).data;return o[r]===l[0]&&o[r+2]===l[2]&&!(e.measureText(n).width>=t)}})();var q={latestVersion:function(){for(const{v:e,emoji:t}of F)if(U(t))return e},noCountryFlags:function(){return!U("🇨🇦")}};const W=["+1","grinning","kissing_heart","heart_eyes","laughing","stuck_out_tongue_winking_eye","sweat_smile","joy","scream","disappointed","unamused","weary","sob","sunglasses","heart"];let K=null;var G={add:function(e){K||(K=V.get("frequently")||{});const t=e.id||e;t&&(K[t]||(K[t]=0),K[t]+=1,V.set("last",t),V.set("frequently",K))},get:function({maxFrequentRows:e,perLine:t}){if(!e)return[];K||(K=V.get("frequently"));let n=[];if(!K){K={};for(let e in W.slice(0,t)){const o=W[e];K[o]=t-e,n.push(o)}return n}const o=e*t,i=V.get("last");for(let e in K)n.push(e);if(n.sort(((e,t)=>{const n=K[t],o=K[e];return n==o?e.localeCompare(t):n-o})),n.length>o){const e=n.slice(o);n=n.slice(0,o);for(let t of e)t!=i&&delete K[t];i&&-1==n.indexOf(i)&&(delete K[n[n.length-1]],n.splice(-1,1,i)),V.set("frequently",K)}return n},DEFAULTS:W},X={};X=JSON.parse('{"search":"Search","search_no_results_1":"Oh no!","search_no_results_2":"That emoji couldn’t be found","pick":"Pick an emoji…","add_custom":"Add custom emoji","categories":{"activity":"Activity","custom":"Custom","flags":"Flags","foods":"Food & Drink","frequent":"Frequently used","nature":"Animals & Nature","objects":"Objects","people":"Smileys & People","places":"Travel & Places","search":"Search Results","symbols":"Symbols"},"skins":{"1":"Default","2":"Light","3":"Medium-Light","4":"Medium","5":"Medium-Dark","6":"Dark","choose":"Choose default skin tone"}}');var J={autoFocus:{value:!1},dynamicWidth:{value:!1},emojiButtonColors:{value:null},emojiButtonRadius:{value:"100%"},emojiButtonSize:{value:36},emojiSize:{value:24},emojiVersion:{value:15,choices:[1,2,3,4,5,11,12,12.1,13,13.1,14,15]},exceptEmojis:{value:[]},icons:{value:"auto",choices:["auto","outline","solid"]},locale:{value:"en",choices:["en","ar","be","cs","de","es","fa","fi","fr","hi","it","ja","ko","nl","pl","pt","ru","sa","tr","uk","vi","zh"]},maxFrequentRows:{value:4},navPosition:{value:"top",choices:["top","bottom","none"]},noCountryFlags:{value:!1},noResultsEmoji:{value:null},perLine:{value:9},previewEmoji:{value:null},previewPosition:{value:"bottom",choices:["top","bottom","none"]},searchPosition:{value:"sticky",choices:["sticky","static","none"]},set:{value:"native",choices:["native","apple","facebook","google","twitter"]},skin:{value:1,choices:[1,2,3,4,5,6]},skinTonePosition:{value:"preview",choices:["preview","search","none"]},theme:{value:"auto",choices:["auto","light","dark"]},categories:null,categoryIcons:null,custom:null,data:null,i18n:null,getImageURL:null,getSpritesheetURL:null,onAddCustomEmoji:null,onClickOutside:null,onEmojiSelect:null,stickySearch:{deprecated:!0,value:!0}};let Y=null,Z=null;const Q={};async function ee(e){if(Q[e])return Q[e];const t=await fetch(e),n=await t.json();return Q[e]=n,n}let te=null,ne=null,oe=!1;function ie(e,{caller:t}={}){return te||(te=new Promise((e=>{ne=e}))),e?async function(e){oe=!0;let{emojiVersion:t,set:n,locale:i}=e;if(t||(t=J.emojiVersion.value),n||(n=J.set.value),i||(i=J.locale.value),Z)Z.categories=Z.categories.filter((e=>!e.name));else{Z=("function"==typeof e.data?await e.data():e.data)||await ee(`https://cdn.jsdelivr.net/npm/@emoji-mart/data@latest/sets/${t}/${n}.json`),Z.emoticons={},Z.natives={},Z.categories.unshift({id:"frequent",emojis:[]});for(const e in Z.aliases){const t=Z.aliases[e],n=Z.emojis[t];n&&(n.aliases||(n.aliases=[]),n.aliases.push(e))}Z.originalCategories=Z.categories}if(Y=("function"==typeof e.i18n?await e.i18n():e.i18n)||("en"==i?o(X):await ee(`https://cdn.jsdelivr.net/npm/@emoji-mart/data@latest/i18n/${i}.json`)),e.custom)for(let t in e.custom){t=parseInt(t);const n=e.custom[t],o=e.custom[t-1];if(n.emojis&&n.emojis.length){n.id||(n.id=`custom_${t+1}`),n.name||(n.name=Y.categories.custom),o&&!n.icon&&(n.target=o.target||o),Z.categories.push(n);for(const e of n.emojis)Z.emojis[e.id]=e}}e.categories&&(Z.categories=Z.originalCategories.filter((t=>-1!=e.categories.indexOf(t.id))).sort(((t,n)=>e.categories.indexOf(t.id)-e.categories.indexOf(n.id))));let r=null,s=null;"native"==n&&(r=q.latestVersion(),s=e.noCountryFlags||q.noCountryFlags());let a=Z.categories.length,l=!1;for(;a--;){const t=Z.categories[a];if("frequent"==t.id){let{maxFrequentRows:n,perLine:o}=e;n=n>=0?n:J.maxFrequentRows.value,o||(o=J.perLine.value),t.emojis=G.get({maxFrequentRows:n,perLine:o})}if(!t.emojis||!t.emojis.length){Z.categories.splice(a,1);continue}const{categoryIcons:n}=e;if(n){const e=n[t.id];e&&!t.icon&&(t.icon=e)}let o=t.emojis.length;for(;o--;){const n=t.emojis[o],i=n.id?n:Z.emojis[n],a=()=>{t.emojis.splice(o,1)};if(!i||e.exceptEmojis&&e.exceptEmojis.includes(i.id))a();else if(r&&i.version>r)a();else if(!s||"flags"!=t.id||ce.includes(i.id)){if(!i.search){if(l=!0,i.search=","+[[i.id,!1],[i.name,!0],[i.keywords,!1],[i.emoticons,!1]].map((([e,t])=>{if(e)return(Array.isArray(e)?e:[e]).map((e=>(t?e.split(/[-|_|\s]+/):[e]).map((e=>e.toLowerCase())))).flat()})).flat().filter((e=>e&&e.trim())).join(","),i.emoticons)for(const e of i.emoticons)Z.emoticons[e]||(Z.emoticons[e]=i.id);let e=0;for(const t of i.skins){if(!t)continue;e++;const{native:n}=t;n&&(Z.natives[n]=i.id,i.search+=`,${n}`);const o=1==e?"":`:skin-tone-${e}:`;t.shortcodes=`:${i.id}:${o}`}}}else a()}}l&&le.reset(),ne()}(e):t&&!oe&&console.warn(`\`${t}\` requires data to be initialized first. Promise will be pending until \`init\` is called.`),te}function re(e,t,n){e||(e={});const o={};for(let i in t)o[i]=se(i,e,t,n);return o}function se(e,t,n,o){const i=n[e];let r=o&&o.getAttribute(e)||(null!=t[e]&&null!=t[e]?t[e]:null);return i?(null!=r&&i.value&&typeof i.value!=typeof r&&(r="boolean"==typeof i.value?"false"!=r:i.value.constructor(r)),i.transform&&r&&(r=i.transform(r)),(null==r||i.choices&&-1==i.choices.indexOf(r))&&(r=i.value),r):r}let ae=null;var le={search:async function(e,{maxResults:t,caller:n}={}){if(!e||!e.trim().length)return null;t||(t=90),await ie(null,{caller:n||"SearchIndex.search"});const o=e.toLowerCase().replace(/(\w)-/,"$1 ").split(/[\s|,]+/).filter(((e,t,n)=>e.trim()&&n.indexOf(e)==t));if(!o.length)return;let i,r,s=ae||(ae=Object.values(Z.emojis));for(const e of o){if(!s.length)break;i=[],r={};for(const t of s){if(!t.search)continue;const n=t.search.indexOf(`,${e}`);-1!=n&&(i.push(t),r[t.id]||(r[t.id]=0),r[t.id]+=t.id==e?0:n+1)}s=i}return i.length<2||(i.sort(((e,t)=>{const n=r[e.id],o=r[t.id];return n==o?e.id.localeCompare(t.id):n-o})),i.length>t&&(i=i.slice(0,t))),i},get:function(e){return e.id?e:Z.emojis[e]||Z.emojis[Z.aliases[e]]||Z.emojis[Z.natives[e]]},reset:function(){ae=null},SHORTCODES_REGEX:/^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/};const ce=["checkered_flag","crossed_flags","pirate_flag","rainbow-flag","transgender_flag","triangular_flag_on_post","waving_black_flag","waving_white_flag"];function de(e,{skinIndex:t=0}={}){const n=e.skins[t]||(t=0,e.skins[t]),o={id:e.id,name:e.name,native:n.native,unified:n.unified,keywords:e.keywords,shortcodes:n.shortcodes||e.shortcodes};return e.skins.length>1&&(o.skin=t+1),n.src&&(o.src=n.src),e.aliases&&e.aliases.length&&(o.aliases=e.aliases),e.emoticons&&e.emoticons.length&&(o.emoticons=e.emoticons),o}async function he(e){const t=await le.search(e,{maxResults:1,caller:"getEmojiDataFromNative"});if(!t||!t.length)return null;const n=t[0];let o=0;for(let t of n.skins){if(t.native==e)break;o++}return de(n,{skinIndex:o})}var ue={categories:{activity:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:D("path",{d:"M12 0C5.373 0 0 5.372 0 12c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.628-5.372-12-12-12m9.949 11H17.05c.224-2.527 1.232-4.773 1.968-6.113A9.966 9.966 0 0 1 21.949 11M13 11V2.051a9.945 9.945 0 0 1 4.432 1.564c-.858 1.491-2.156 4.22-2.392 7.385H13zm-2 0H8.961c-.238-3.165-1.536-5.894-2.393-7.385A9.95 9.95 0 0 1 11 2.051V11zm0 2v8.949a9.937 9.937 0 0 1-4.432-1.564c.857-1.492 2.155-4.221 2.393-7.385H11zm4.04 0c.236 3.164 1.534 5.893 2.392 7.385A9.92 9.92 0 0 1 13 21.949V13h2.04zM4.982 4.887C5.718 6.227 6.726 8.473 6.951 11h-4.9a9.977 9.977 0 0 1 2.931-6.113M2.051 13h4.9c-.226 2.527-1.233 4.771-1.969 6.113A9.972 9.972 0 0 1 2.051 13m16.967 6.113c-.735-1.342-1.744-3.586-1.968-6.113h4.899a9.961 9.961 0 0 1-2.931 6.113"})}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:D("path",{d:"M16.17 337.5c0 44.98 7.565 83.54 13.98 107.9C35.22 464.3 50.46 496 174.9 496c9.566 0 19.59-.4707 29.84-1.271L17.33 307.3C16.53 317.6 16.17 327.7 16.17 337.5zM495.8 174.5c0-44.98-7.565-83.53-13.98-107.9c-4.688-17.54-18.34-31.23-36.04-35.95C435.5 27.91 392.9 16 337 16c-9.564 0-19.59 .4707-29.84 1.271l187.5 187.5C495.5 194.4 495.8 184.3 495.8 174.5zM26.77 248.8l236.3 236.3c142-36.1 203.9-150.4 222.2-221.1L248.9 26.87C106.9 62.96 45.07 177.2 26.77 248.8zM256 335.1c0 9.141-7.474 16-16 16c-4.094 0-8.188-1.564-11.31-4.689L164.7 283.3C161.6 280.2 160 276.1 160 271.1c0-8.529 6.865-16 16-16c4.095 0 8.189 1.562 11.31 4.688l64.01 64C254.4 327.8 256 331.9 256 335.1zM304 287.1c0 9.141-7.474 16-16 16c-4.094 0-8.188-1.564-11.31-4.689L212.7 235.3C209.6 232.2 208 228.1 208 223.1c0-9.141 7.473-16 16-16c4.094 0 8.188 1.562 11.31 4.688l64.01 64.01C302.5 279.8 304 283.9 304 287.1zM256 175.1c0-9.141 7.473-16 16-16c4.094 0 8.188 1.562 11.31 4.688l64.01 64.01c3.125 3.125 4.688 7.219 4.688 11.31c0 9.133-7.468 16-16 16c-4.094 0-8.189-1.562-11.31-4.688l-64.01-64.01C257.6 184.2 256 180.1 256 175.1z"})})},custom:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512",children:D("path",{d:"M417.1 368c-5.937 10.27-16.69 16-27.75 16c-5.422 0-10.92-1.375-15.97-4.281L256 311.4V448c0 17.67-14.33 32-31.1 32S192 465.7 192 448V311.4l-118.3 68.29C68.67 382.6 63.17 384 57.75 384c-11.06 0-21.81-5.734-27.75-16c-8.828-15.31-3.594-34.88 11.72-43.72L159.1 256L41.72 187.7C26.41 178.9 21.17 159.3 29.1 144C36.63 132.5 49.26 126.7 61.65 128.2C65.78 128.7 69.88 130.1 73.72 132.3L192 200.6V64c0-17.67 14.33-32 32-32S256 46.33 256 64v136.6l118.3-68.29c3.838-2.213 7.939-3.539 12.07-4.051C398.7 126.7 411.4 132.5 417.1 144c8.828 15.31 3.594 34.88-11.72 43.72L288 256l118.3 68.28C421.6 333.1 426.8 352.7 417.1 368z"})}),flags:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:D("path",{d:"M0 0l6.084 24H8L1.916 0zM21 5h-4l-1-4H4l3 12h3l1 4h13L21 5zM6.563 3h7.875l2 8H8.563l-2-8zm8.832 10l-2.856 1.904L12.063 13h3.332zM19 13l-1.5-6h1.938l2 8H16l3-2z"})}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:D("path",{d:"M64 496C64 504.8 56.75 512 48 512h-32C7.25 512 0 504.8 0 496V32c0-17.75 14.25-32 32-32s32 14.25 32 32V496zM476.3 0c-6.365 0-13.01 1.35-19.34 4.233c-45.69 20.86-79.56 27.94-107.8 27.94c-59.96 0-94.81-31.86-163.9-31.87C160.9 .3055 131.6 4.867 96 15.75v350.5c32-9.984 59.87-14.1 84.85-14.1c73.63 0 124.9 31.78 198.6 31.78c31.91 0 68.02-5.971 111.1-23.09C504.1 355.9 512 344.4 512 332.1V30.73C512 11.1 495.3 0 476.3 0z"})})},foods:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:D("path",{d:"M17 4.978c-1.838 0-2.876.396-3.68.934.513-1.172 1.768-2.934 4.68-2.934a1 1 0 0 0 0-2c-2.921 0-4.629 1.365-5.547 2.512-.064.078-.119.162-.18.244C11.73 1.838 10.798.023 9.207.023 8.579.022 7.85.306 7 .978 5.027 2.54 5.329 3.902 6.492 4.999 3.609 5.222 0 7.352 0 12.969c0 4.582 4.961 11.009 9 11.009 1.975 0 2.371-.486 3-1 .629.514 1.025 1 3 1 4.039 0 9-6.418 9-11 0-5.953-4.055-8-7-8M8.242 2.546c.641-.508.943-.523.965-.523.426.169.975 1.405 1.357 3.055-1.527-.629-2.741-1.352-2.98-1.846.059-.112.241-.356.658-.686M15 21.978c-1.08 0-1.21-.109-1.559-.402l-.176-.146c-.367-.302-.816-.452-1.266-.452s-.898.15-1.266.452l-.176.146c-.347.292-.477.402-1.557.402-2.813 0-7-5.389-7-9.009 0-5.823 4.488-5.991 5-5.991 1.939 0 2.484.471 3.387 1.251l.323.276a1.995 1.995 0 0 0 2.58 0l.323-.276c.902-.78 1.447-1.251 3.387-1.251.512 0 5 .168 5 6 0 3.617-4.187 9-7 9"})}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:D("path",{d:"M481.9 270.1C490.9 279.1 496 291.3 496 304C496 316.7 490.9 328.9 481.9 337.9C472.9 346.9 460.7 352 448 352H64C51.27 352 39.06 346.9 30.06 337.9C21.06 328.9 16 316.7 16 304C16 291.3 21.06 279.1 30.06 270.1C39.06 261.1 51.27 256 64 256H448C460.7 256 472.9 261.1 481.9 270.1zM475.3 388.7C478.3 391.7 480 395.8 480 400V416C480 432.1 473.3 449.3 461.3 461.3C449.3 473.3 432.1 480 416 480H96C79.03 480 62.75 473.3 50.75 461.3C38.74 449.3 32 432.1 32 416V400C32 395.8 33.69 391.7 36.69 388.7C39.69 385.7 43.76 384 48 384H464C468.2 384 472.3 385.7 475.3 388.7zM50.39 220.8C45.93 218.6 42.03 215.5 38.97 211.6C35.91 207.7 33.79 203.2 32.75 198.4C31.71 193.5 31.8 188.5 32.99 183.7C54.98 97.02 146.5 32 256 32C365.5 32 457 97.02 479 183.7C480.2 188.5 480.3 193.5 479.2 198.4C478.2 203.2 476.1 207.7 473 211.6C469.1 215.5 466.1 218.6 461.6 220.8C457.2 222.9 452.3 224 447.3 224H64.67C59.73 224 54.84 222.9 50.39 220.8zM372.7 116.7C369.7 119.7 368 123.8 368 128C368 131.2 368.9 134.3 370.7 136.9C372.5 139.5 374.1 141.6 377.9 142.8C380.8 143.1 384 144.3 387.1 143.7C390.2 143.1 393.1 141.6 395.3 139.3C397.6 137.1 399.1 134.2 399.7 131.1C400.3 128 399.1 124.8 398.8 121.9C397.6 118.1 395.5 116.5 392.9 114.7C390.3 112.9 387.2 111.1 384 111.1C379.8 111.1 375.7 113.7 372.7 116.7V116.7zM244.7 84.69C241.7 87.69 240 91.76 240 96C240 99.16 240.9 102.3 242.7 104.9C244.5 107.5 246.1 109.6 249.9 110.8C252.8 111.1 256 112.3 259.1 111.7C262.2 111.1 265.1 109.6 267.3 107.3C269.6 105.1 271.1 102.2 271.7 99.12C272.3 96.02 271.1 92.8 270.8 89.88C269.6 86.95 267.5 84.45 264.9 82.7C262.3 80.94 259.2 79.1 256 79.1C251.8 79.1 247.7 81.69 244.7 84.69V84.69zM116.7 116.7C113.7 119.7 112 123.8 112 128C112 131.2 112.9 134.3 114.7 136.9C116.5 139.5 118.1 141.6 121.9 142.8C124.8 143.1 128 144.3 131.1 143.7C134.2 143.1 137.1 141.6 139.3 139.3C141.6 137.1 143.1 134.2 143.7 131.1C144.3 128 143.1 124.8 142.8 121.9C141.6 118.1 139.5 116.5 136.9 114.7C134.3 112.9 131.2 111.1 128 111.1C123.8 111.1 119.7 113.7 116.7 116.7L116.7 116.7z"})})},frequent:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[D("path",{d:"M13 4h-2l-.001 7H9v2h2v2h2v-2h4v-2h-4z"}),D("path",{d:"M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0m0 22C6.486 22 2 17.514 2 12S6.486 2 12 2s10 4.486 10 10-4.486 10-10 10"})]}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:D("path",{d:"M256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512zM232 256C232 264 236 271.5 242.7 275.1L338.7 339.1C349.7 347.3 364.6 344.3 371.1 333.3C379.3 322.3 376.3 307.4 365.3 300L280 243.2V120C280 106.7 269.3 96 255.1 96C242.7 96 231.1 106.7 231.1 120L232 256z"})})},nature:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[D("path",{d:"M15.5 8a1.5 1.5 0 1 0 .001 3.001A1.5 1.5 0 0 0 15.5 8M8.5 8a1.5 1.5 0 1 0 .001 3.001A1.5 1.5 0 0 0 8.5 8"}),D("path",{d:"M18.933 0h-.027c-.97 0-2.138.787-3.018 1.497-1.274-.374-2.612-.51-3.887-.51-1.285 0-2.616.133-3.874.517C7.245.79 6.069 0 5.093 0h-.027C3.352 0 .07 2.67.002 7.026c-.039 2.479.276 4.238 1.04 5.013.254.258.882.677 1.295.882.191 3.177.922 5.238 2.536 6.38.897.637 2.187.949 3.2 1.102C8.04 20.6 8 20.795 8 21c0 1.773 2.35 3 4 3 1.648 0 4-1.227 4-3 0-.201-.038-.393-.072-.586 2.573-.385 5.435-1.877 5.925-7.587.396-.22.887-.568 1.104-.788.763-.774 1.079-2.534 1.04-5.013C23.929 2.67 20.646 0 18.933 0M3.223 9.135c-.237.281-.837 1.155-.884 1.238-.15-.41-.368-1.349-.337-3.291.051-3.281 2.478-4.972 3.091-5.031.256.015.731.27 1.265.646-1.11 1.171-2.275 2.915-2.352 5.125-.133.546-.398.858-.783 1.313M12 22c-.901 0-1.954-.693-2-1 0-.654.475-1.236 1-1.602V20a1 1 0 1 0 2 0v-.602c.524.365 1 .947 1 1.602-.046.307-1.099 1-2 1m3-3.48v.02a4.752 4.752 0 0 0-1.262-1.02c1.092-.516 2.239-1.334 2.239-2.217 0-1.842-1.781-2.195-3.977-2.195-2.196 0-3.978.354-3.978 2.195 0 .883 1.148 1.701 2.238 2.217A4.8 4.8 0 0 0 9 18.539v-.025c-1-.076-2.182-.281-2.973-.842-1.301-.92-1.838-3.045-1.853-6.478l.023-.041c.496-.826 1.49-1.45 1.804-3.102 0-2.047 1.357-3.631 2.362-4.522C9.37 3.178 10.555 3 11.948 3c1.447 0 2.685.192 3.733.57 1 .9 2.316 2.465 2.316 4.48.313 1.651 1.307 2.275 1.803 3.102.035.058.068.117.102.178-.059 5.967-1.949 7.01-4.902 7.19m6.628-8.202c-.037-.065-.074-.13-.113-.195a7.587 7.587 0 0 0-.739-.987c-.385-.455-.648-.768-.782-1.313-.076-2.209-1.241-3.954-2.353-5.124.531-.376 1.004-.63 1.261-.647.636.071 3.044 1.764 3.096 5.031.027 1.81-.347 3.218-.37 3.235"})]}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 576 512",children:D("path",{d:"M332.7 19.85C334.6 8.395 344.5 0 356.1 0C363.6 0 370.6 3.52 375.1 9.502L392 32H444.1C456.8 32 469.1 37.06 478.1 46.06L496 64H552C565.3 64 576 74.75 576 88V112C576 156.2 540.2 192 496 192H426.7L421.6 222.5L309.6 158.5L332.7 19.85zM448 64C439.2 64 432 71.16 432 80C432 88.84 439.2 96 448 96C456.8 96 464 88.84 464 80C464 71.16 456.8 64 448 64zM416 256.1V480C416 497.7 401.7 512 384 512H352C334.3 512 320 497.7 320 480V364.8C295.1 377.1 268.8 384 240 384C211.2 384 184 377.1 160 364.8V480C160 497.7 145.7 512 128 512H96C78.33 512 64 497.7 64 480V249.8C35.23 238.9 12.64 214.5 4.836 183.3L.9558 167.8C-3.331 150.6 7.094 133.2 24.24 128.1C41.38 124.7 58.76 135.1 63.05 152.2L66.93 167.8C70.49 182 83.29 191.1 97.97 191.1H303.8L416 256.1z"})})},objects:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[D("path",{d:"M12 0a9 9 0 0 0-5 16.482V21s2.035 3 5 3 5-3 5-3v-4.518A9 9 0 0 0 12 0zm0 2c3.86 0 7 3.141 7 7s-3.14 7-7 7-7-3.141-7-7 3.14-7 7-7zM9 17.477c.94.332 1.946.523 3 .523s2.06-.19 3-.523v.834c-.91.436-1.925.689-3 .689a6.924 6.924 0 0 1-3-.69v-.833zm.236 3.07A8.854 8.854 0 0 0 12 21c.965 0 1.888-.167 2.758-.451C14.155 21.173 13.153 22 12 22c-1.102 0-2.117-.789-2.764-1.453z"}),D("path",{d:"M14.745 12.449h-.004c-.852-.024-1.188-.858-1.577-1.824-.421-1.061-.703-1.561-1.182-1.566h-.009c-.481 0-.783.497-1.235 1.537-.436.982-.801 1.811-1.636 1.791l-.276-.043c-.565-.171-.853-.691-1.284-1.794-.125-.313-.202-.632-.27-.913-.051-.213-.127-.53-.195-.634C7.067 9.004 7.039 9 6.99 9A1 1 0 0 1 7 7h.01c1.662.017 2.015 1.373 2.198 2.134.486-.981 1.304-2.058 2.797-2.075 1.531.018 2.28 1.153 2.731 2.141l.002-.008C14.944 8.424 15.327 7 16.979 7h.032A1 1 0 1 1 17 9h-.011c-.149.076-.256.474-.319.709a6.484 6.484 0 0 1-.311.951c-.429.973-.79 1.789-1.614 1.789"})]}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 384 512",children:D("path",{d:"M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"})})},people:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[D("path",{d:"M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0m0 22C6.486 22 2 17.514 2 12S6.486 2 12 2s10 4.486 10 10-4.486 10-10 10"}),D("path",{d:"M8 7a2 2 0 1 0-.001 3.999A2 2 0 0 0 8 7M16 7a2 2 0 1 0-.001 3.999A2 2 0 0 0 16 7M15.232 15c-.693 1.195-1.87 2-3.349 2-1.477 0-2.655-.805-3.347-2H15m3-2H6a6 6 0 1 0 12 0"})]}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:D("path",{d:"M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM256 432C332.1 432 396.2 382 415.2 314.1C419.1 300.4 407.8 288 393.6 288H118.4C104.2 288 92.92 300.4 96.76 314.1C115.8 382 179.9 432 256 432V432zM176.4 160C158.7 160 144.4 174.3 144.4 192C144.4 209.7 158.7 224 176.4 224C194 224 208.4 209.7 208.4 192C208.4 174.3 194 160 176.4 160zM336.4 224C354 224 368.4 209.7 368.4 192C368.4 174.3 354 160 336.4 160C318.7 160 304.4 174.3 304.4 192C304.4 209.7 318.7 224 336.4 224z"})})},places:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:[D("path",{d:"M6.5 12C5.122 12 4 13.121 4 14.5S5.122 17 6.5 17 9 15.879 9 14.5 7.878 12 6.5 12m0 3c-.275 0-.5-.225-.5-.5s.225-.5.5-.5.5.225.5.5-.225.5-.5.5M17.5 12c-1.378 0-2.5 1.121-2.5 2.5s1.122 2.5 2.5 2.5 2.5-1.121 2.5-2.5-1.122-2.5-2.5-2.5m0 3c-.275 0-.5-.225-.5-.5s.225-.5.5-.5.5.225.5.5-.225.5-.5.5"}),D("path",{d:"M22.482 9.494l-1.039-.346L21.4 9h.6c.552 0 1-.439 1-.992 0-.006-.003-.008-.003-.008H23c0-1-.889-2-1.984-2h-.642l-.731-1.717C19.262 3.012 18.091 2 16.764 2H7.236C5.909 2 4.738 3.012 4.357 4.283L3.626 6h-.642C1.889 6 1 7 1 8h.003S1 8.002 1 8.008C1 8.561 1.448 9 2 9h.6l-.043.148-1.039.346a2.001 2.001 0 0 0-1.359 2.097l.751 7.508a1 1 0 0 0 .994.901H3v1c0 1.103.896 2 2 2h2c1.104 0 2-.897 2-2v-1h6v1c0 1.103.896 2 2 2h2c1.104 0 2-.897 2-2v-1h1.096a.999.999 0 0 0 .994-.901l.751-7.508a2.001 2.001 0 0 0-1.359-2.097M6.273 4.857C6.402 4.43 6.788 4 7.236 4h9.527c.448 0 .834.43.963.857L19.313 9H4.688l1.585-4.143zM7 21H5v-1h2v1zm12 0h-2v-1h2v1zm2.189-3H2.811l-.662-6.607L3 11h18l.852.393L21.189 18z"})]}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:D("path",{d:"M39.61 196.8L74.8 96.29C88.27 57.78 124.6 32 165.4 32H346.6C387.4 32 423.7 57.78 437.2 96.29L472.4 196.8C495.6 206.4 512 229.3 512 256V448C512 465.7 497.7 480 480 480H448C430.3 480 416 465.7 416 448V400H96V448C96 465.7 81.67 480 64 480H32C14.33 480 0 465.7 0 448V256C0 229.3 16.36 206.4 39.61 196.8V196.8zM109.1 192H402.9L376.8 117.4C372.3 104.6 360.2 96 346.6 96H165.4C151.8 96 139.7 104.6 135.2 117.4L109.1 192zM96 256C78.33 256 64 270.3 64 288C64 305.7 78.33 320 96 320C113.7 320 128 305.7 128 288C128 270.3 113.7 256 96 256zM416 320C433.7 320 448 305.7 448 288C448 270.3 433.7 256 416 256C398.3 256 384 270.3 384 288C384 305.7 398.3 320 416 320z"})})},symbols:{outline:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:D("path",{d:"M0 0h11v2H0zM4 11h3V6h4V4H0v2h4zM15.5 17c1.381 0 2.5-1.116 2.5-2.493s-1.119-2.493-2.5-2.493S13 13.13 13 14.507 14.119 17 15.5 17m0-2.986c.276 0 .5.222.5.493 0 .272-.224.493-.5.493s-.5-.221-.5-.493.224-.493.5-.493M21.5 19.014c-1.381 0-2.5 1.116-2.5 2.493S20.119 24 21.5 24s2.5-1.116 2.5-2.493-1.119-2.493-2.5-2.493m0 2.986a.497.497 0 0 1-.5-.493c0-.271.224-.493.5-.493s.5.222.5.493a.497.497 0 0 1-.5.493M22 13l-9 9 1.513 1.5 8.99-9.009zM17 11c2.209 0 4-1.119 4-2.5V2s.985-.161 1.498.949C23.01 4.055 23 6 23 6s1-1.119 1-3.135C24-.02 21 0 21 0h-2v6.347A5.853 5.853 0 0 0 17 6c-2.209 0-4 1.119-4 2.5s1.791 2.5 4 2.5M10.297 20.482l-1.475-1.585a47.54 47.54 0 0 1-1.442 1.129c-.307-.288-.989-1.016-2.045-2.183.902-.836 1.479-1.466 1.729-1.892s.376-.871.376-1.336c0-.592-.273-1.178-.818-1.759-.546-.581-1.329-.871-2.349-.871-1.008 0-1.79.293-2.344.879-.556.587-.832 1.181-.832 1.784 0 .813.419 1.748 1.256 2.805-.847.614-1.444 1.208-1.794 1.784a3.465 3.465 0 0 0-.523 1.833c0 .857.308 1.56.924 2.107.616.549 1.423.823 2.42.823 1.173 0 2.444-.379 3.813-1.137L8.235 24h2.819l-2.09-2.383 1.333-1.135zm-6.736-6.389a1.02 1.02 0 0 1 .73-.286c.31 0 .559.085.747.254a.849.849 0 0 1 .283.659c0 .518-.419 1.112-1.257 1.784-.536-.651-.805-1.231-.805-1.742a.901.901 0 0 1 .302-.669M3.74 22c-.427 0-.778-.116-1.057-.349-.279-.232-.418-.487-.418-.766 0-.594.509-1.288 1.527-2.083.968 1.134 1.717 1.946 2.248 2.438-.921.507-1.686.76-2.3.76"})}),solid:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",children:D("path",{d:"M500.3 7.251C507.7 13.33 512 22.41 512 31.1V175.1C512 202.5 483.3 223.1 447.1 223.1C412.7 223.1 383.1 202.5 383.1 175.1C383.1 149.5 412.7 127.1 447.1 127.1V71.03L351.1 90.23V207.1C351.1 234.5 323.3 255.1 287.1 255.1C252.7 255.1 223.1 234.5 223.1 207.1C223.1 181.5 252.7 159.1 287.1 159.1V63.1C287.1 48.74 298.8 35.61 313.7 32.62L473.7 .6198C483.1-1.261 492.9 1.173 500.3 7.251H500.3zM74.66 303.1L86.5 286.2C92.43 277.3 102.4 271.1 113.1 271.1H174.9C185.6 271.1 195.6 277.3 201.5 286.2L213.3 303.1H239.1C266.5 303.1 287.1 325.5 287.1 351.1V463.1C287.1 490.5 266.5 511.1 239.1 511.1H47.1C21.49 511.1-.0019 490.5-.0019 463.1V351.1C-.0019 325.5 21.49 303.1 47.1 303.1H74.66zM143.1 359.1C117.5 359.1 95.1 381.5 95.1 407.1C95.1 434.5 117.5 455.1 143.1 455.1C170.5 455.1 191.1 434.5 191.1 407.1C191.1 381.5 170.5 359.1 143.1 359.1zM440.3 367.1H496C502.7 367.1 508.6 372.1 510.1 378.4C513.3 384.6 511.6 391.7 506.5 396L378.5 508C372.9 512.1 364.6 513.3 358.6 508.9C352.6 504.6 350.3 496.6 353.3 489.7L391.7 399.1H336C329.3 399.1 323.4 395.9 321 389.6C318.7 383.4 320.4 376.3 325.5 371.1L453.5 259.1C459.1 255 467.4 254.7 473.4 259.1C479.4 263.4 481.6 271.4 478.7 278.3L440.3 367.1zM116.7 219.1L19.85 119.2C-8.112 90.26-6.614 42.31 24.85 15.34C51.82-8.137 93.26-3.642 118.2 21.83L128.2 32.32L137.7 21.83C162.7-3.642 203.6-8.137 231.6 15.34C262.6 42.31 264.1 90.26 236.1 119.2L139.7 219.1C133.2 225.6 122.7 225.6 116.7 219.1H116.7z"})})}},search:{loupe:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:D("path",{d:"M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"})}),delete:D("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:D("path",{d:"M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z"})})}};function pe(e){let{id:t,skin:n,emoji:o}=e;if(e.shortcodes){const o=e.shortcodes.match(le.SHORTCODES_REGEX);o&&(t=o[1],o[2]&&(n=o[2]))}if(o||(o=le.get(t||e.native)),!o)return e.fallback;const i=o.skins[n-1]||o.skins[0],r=i.src||("native"==e.set||e.spritesheet?void 0:"function"==typeof e.getImageURL?e.getImageURL(e.set,i.unified):`https://cdn.jsdelivr.net/npm/emoji-datasource-${e.set}@15.0.1/img/${e.set}/64/${i.unified}.png`),s="function"==typeof e.getSpritesheetURL?e.getSpritesheetURL(e.set):`https://cdn.jsdelivr.net/npm/emoji-datasource-${e.set}@15.0.1/img/${e.set}/sheets-256/64.png`;return D("span",{class:"emoji-mart-emoji","data-emoji-set":e.set,children:r?D("img",{style:{maxWidth:e.size||"1em",maxHeight:e.size||"1em",display:"inline-block"},alt:i.native||i.shortcodes,src:r}):"native"==e.set?D("span",{style:{fontSize:e.size,fontFamily:'"EmojiMart", "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji"'},children:i.native}):D("span",{style:{display:"block",width:e.size,height:e.size,backgroundImage:`url(${s})`,backgroundSize:`${100*Z.sheet.cols}% ${100*Z.sheet.rows}%`,backgroundPosition:`${100/(Z.sheet.cols-1)*i.x}% ${100/(Z.sheet.rows-1)*i.y}%`}})})}const fe="undefined"!=typeof window&&window.HTMLElement?window.HTMLElement:Object;class _e extends fe{static get observedAttributes(){return Object.keys(this.Props)}update(e={}){for(let t in e)this.attributeChangedCallback(t,null,e[t])}attributeChangedCallback(e,t,n){if(!this.component)return;const o=se(e,{[e]:n},this.constructor.Props,this);this.component.componentWillReceiveProps?this.component.componentWillReceiveProps({[e]:o}):(this.component.props[e]=o,this.component.forceUpdate())}disconnectedCallback(){this.disconnected=!0,this.component&&this.component.unregister&&this.component.unregister()}constructor(e={}){if(super(),this.props=e,e.parent||e.ref){let t=null;const n=e.parent||(t=e.ref&&e.ref.current);t&&(t.innerHTML=""),n&&n.appendChild(this)}}}class ge extends _e{setShadow(){this.attachShadow({mode:"open"})}injectStyles(e){if(!e)return;const t=document.createElement("style");t.textContent=e,this.shadowRoot.insertBefore(t,this.shadowRoot.firstChild)}constructor(e,{styles:t}={}){super(e),this.setShadow(),this.injectStyles(t)}}var me={fallback:"",id:"",native:"",shortcodes:"",size:{value:"",transform:e=>/\D/.test(e)?e:`${e}px`},set:J.set,skin:J.skin};class ve extends _e{async connectedCallback(){const e=re(this.props,me,this);e.element=this,e.ref=e=>{this.component=e},await ie(),this.disconnected||I(D(pe,{...e}),this)}constructor(e){super(e)}}i(ve,"Props",me),"undefined"==typeof customElements||customElements.get("em-emoji")||customElements.define("em-emoji",ve);var be,ke,we=[],ye=s.__b,Ce=s.__r,xe=s.diffed,Se=s.__c,je=s.unmount;function ze(){var e;for(we.sort((function(e,t){return e.__v.__b-t.__v.__b}));e=we.pop();)if(e.__P)try{e.__H.__h.forEach(Le),e.__H.__h.forEach(Ee),e.__H.__h=[]}catch(t){e.__H.__h=[],s.__e(t,e.__v)}}s.__b=function(e){be=null,ye&&ye(e)},s.__r=function(e){Ce&&Ce(e);var t=(be=e.__c).__H;t&&(t.__h.forEach(Le),t.__h.forEach(Ee),t.__h=[])},s.diffed=function(e){xe&&xe(e);var t=e.__c;t&&t.__H&&t.__H.__h.length&&(1!==we.push(t)&&ke===s.requestAnimationFrame||((ke=s.requestAnimationFrame)||function(e){var t,n=function(){clearTimeout(o),Me&&cancelAnimationFrame(t),setTimeout(e)},o=setTimeout(n,100);Me&&(t=requestAnimationFrame(n))})(ze)),be=null},s.__c=function(e,t){t.some((function(e){try{e.__h.forEach(Le),e.__h=e.__h.filter((function(e){return!e.__||Ee(e)}))}catch(n){t.some((function(e){e.__h&&(e.__h=[])})),t=[],s.__e(n,e.__v)}})),Se&&Se(e,t)},s.unmount=function(e){je&&je(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.forEach((function(e){try{Le(e)}catch(e){t=e}})),t&&s.__e(t,n.__v))};var Me="function"==typeof requestAnimationFrame;function Le(e){var t=be,n=e.__c;"function"==typeof n&&(e.__c=void 0,n()),be=t}function Ee(e){var t=be;e.__c=e.__(),be=t}function Pe(e,t){for(var n in e)if("__source"!==n&&!(n in t))return!0;for(var o in t)if("__source"!==o&&e[o]!==t[o])return!0;return!1}function Re(e){this.props=e}(Re.prototype=new b).isPureReactComponent=!0,Re.prototype.shouldComponentUpdate=function(e,t){return Pe(this.props,e)||Pe(this.state,t)};var Be=s.__b;s.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),Be&&Be(e)},"undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.forward_ref");var He=s.__e;s.__e=function(e,t,n){if(e.then)for(var o,i=t;i=i.__;)if((o=i.__c)&&o.__c)return null==t.__e&&(t.__e=n.__e,t.__k=n.__k),o.__c(e,t);He(e,t,n)};var Ae=s.unmount;function Te(){this.__u=0,this.t=null,this.__b=null}function Ie(e){var t=e.__.__c;return t&&t.__e&&t.__e(e)}function Oe(){this.u=null,this.o=null}s.unmount=function(e){var t=e.__c;t&&t.__R&&t.__R(),t&&!0===e.__h&&(e.type=null),Ae&&Ae(e)},(Te.prototype=new b).__c=function(e,t){var n=t.__c,o=this;null==o.t&&(o.t=[]),o.t.push(n);var i=Ie(o.__v),r=!1,s=function(){r||(r=!0,n.__R=null,i?i(a):a())};n.__R=s;var a=function(){if(!--o.__u){if(o.state.__e){var e=o.state.__e;o.__v.__k[0]=function e(t,n,o){return t&&(t.__v=null,t.__k=t.__k&&t.__k.map((function(t){return e(t,n,o)})),t.__c&&t.__c.__P===n&&(t.__e&&o.insertBefore(t.__e,t.__d),t.__c.__e=!0,t.__c.__P=o)),t}(e,e.__c.__P,e.__c.__O)}var t;for(o.setState({__e:o.__b=null});t=o.t.pop();)t.forceUpdate()}},l=!0===t.__h;o.__u++||l||o.setState({__e:o.__b=o.__v.__k[0]}),e.then(s,s)},Te.prototype.componentWillUnmount=function(){this.t=[]},Te.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var n=document.createElement("div"),o=this.__v.__k[0].__c;this.__v.__k[0]=function e(t,n,o){return t&&(t.__c&&t.__c.__H&&(t.__c.__H.__.forEach((function(e){"function"==typeof e.__c&&e.__c()})),t.__c.__H=null),null!=(t=function(e,t){for(var n in t)e[n]=t[n];return e}({},t)).__c&&(t.__c.__P===o&&(t.__c.__P=n),t.__c=null),t.__k=t.__k&&t.__k.map((function(t){return e(t,n,o)}))),t}(this.__b,n,o.__O=o.__P)}this.__b=null}var i=t.__e&&g(v,null,e.fallback);return i&&(i.__h=null),[g(v,null,t.__e?null:e.children),i]};var De=function(e,t,n){if(++n[1]===n[0]&&e.o.delete(t),e.props.revealOrder&&("t"!==e.props.revealOrder[0]||!e.o.size))for(n=e.u;n;){for(;n.length>3;)n.pop()();if(n[1]{const o=t.name||Y.categories[t.id],i=!this.props.unfocused&&t.id==this.state.categoryId;return i&&(e=n),D("button",{"aria-label":o,"aria-selected":i||void 0,title:o,type:"button",class:"flex flex-grow flex-center",onMouseDown:e=>e.preventDefault(),onClick:()=>{this.props.onClick({category:t,i:n})},children:this.renderIcon(t)})})),D("div",{class:"bar",style:{width:100/this.categories.length+"%",opacity:null==e?0:1,transform:"rtl"===this.props.dir?`scaleX(-1) translateX(${100*e}%)`:`translateX(${100*e}%)`}})]})})}constructor(){super(),this.categories=Z.categories.filter((e=>!e.target)),this.state={categoryId:this.categories[0].id}}}class Ze extends Re{shouldComponentUpdate(e){for(let t in e)if("children"!=t&&e[t]!=this.props[t])return!0;return!1}render(){return this.props.children}}class Qe extends b{getInitialState(e=this.props){return{skin:V.get("skin")||e.skin,theme:this.initTheme(e.theme)}}componentWillMount(){this.dir=Y.rtl?"rtl":"ltr",this.refs={menu:{current:null},navigation:{current:null},scroll:{current:null},search:{current:null},searchInput:{current:null},skinToneButton:{current:null},skinToneRadio:{current:null}},this.initGrid(),0==this.props.stickySearch&&"sticky"==this.props.searchPosition&&(console.warn("[EmojiMart] Deprecation warning: `stickySearch` has been renamed `searchPosition`."),this.props.searchPosition="static")}componentDidMount(){if(this.register(),this.shadowRoot=this.base.parentNode,this.props.autoFocus){const{searchInput:e}=this.refs;e.current&&e.current.focus()}}componentWillReceiveProps(e){this.nextState||(this.nextState={});for(const t in e)this.nextState[t]=e[t];clearTimeout(this.nextStateTimer),this.nextStateTimer=setTimeout((()=>{let e=!1;for(const t in this.nextState)this.props[t]=this.nextState[t],"custom"!==t&&"categories"!==t||(e=!0);delete this.nextState;const t=this.getInitialState();if(e)return this.reset(t);this.setState(t)}))}componentWillUnmount(){this.unregister()}async reset(e={}){await ie(this.props),this.initGrid(),this.unobserve(),this.setState(e,(()=>{this.observeCategories(),this.observeRows()}))}register(){document.addEventListener("click",this.handleClickOutside),this.observe()}unregister(){document.removeEventListener("click",this.handleClickOutside),this.darkMedia?.removeEventListener("change",this.darkMediaCallback),this.unobserve()}observe(){this.observeCategories(),this.observeRows()}unobserve({except:e=[]}={}){Array.isArray(e)||(e=[e]);for(const t of this.observers)e.includes(t)||t.disconnect();this.observers=[].concat(e)}initGrid(){const{categories:e}=Z;this.refs.categories=new Map;const t=Z.categories.map((e=>e.id)).join(",");this.navKey&&this.navKey!=t&&this.refs.scroll.current&&(this.refs.scroll.current.scrollTop=0),this.navKey=t,this.grid=[],this.grid.setsize=0;const n=(e,t)=>{const n=[];n.__categoryId=t.id,n.__index=e.length,this.grid.push(n);const o=this.grid.length-1,i=o%10?{}:{current:null};return i.index=o,i.posinset=this.grid.setsize+1,e.push(i),n};for(let t of e){const e=[];let o=n(e,t);for(let i of t.emojis)o.length==this.getPerLine()&&(o=n(e,t)),this.grid.setsize+=1,o.push(i);this.refs.categories.set(t.id,{root:{current:null},rows:e})}}initTheme(e){if("auto"!=e)return e;if(!this.darkMedia){if(this.darkMedia=matchMedia("(prefers-color-scheme: dark)"),this.darkMedia.media.match(/^not/))return"light";this.darkMedia.addEventListener("change",this.darkMediaCallback)}return this.darkMedia.matches?"dark":"light"}initDynamicPerLine(e=this.props){if(!e.dynamicWidth)return;const{element:t,emojiButtonSize:n}=e,o=()=>{const{width:e}=t.getBoundingClientRect();return Math.floor(e/n)},i=new ResizeObserver((()=>{this.unobserve({except:i}),this.setState({perLine:o()},(()=>{this.initGrid(),this.forceUpdate((()=>{this.observeCategories(),this.observeRows()}))}))}));return i.observe(t),this.observers.push(i),o()}getPerLine(){return this.state.perLine||this.props.perLine}getEmojiByPos([e,t]){const n=this.state.searchResults||this.grid,o=n[e]&&n[e][t];if(o)return le.get(o)}observeCategories(){const e=this.refs.navigation.current;if(!e)return;const t=new Map,n={root:this.refs.scroll.current,threshold:[0,1]},o=new IntersectionObserver((n=>{for(const e of n){const n=e.target.dataset.id;t.set(n,e.intersectionRatio)}const o=[...t];for(const[t,n]of o)if(n){(i=t)!=e.state.categoryId&&e.setState({categoryId:i});break}var i}),n);for(const{root:e}of this.refs.categories.values())o.observe(e.current);this.observers.push(o)}observeRows(){const e={...this.state.visibleRows},t=new IntersectionObserver((t=>{for(const n of t){const t=parseInt(n.target.dataset.index);n.isIntersecting?e[t]=!0:delete e[t]}this.setState({visibleRows:e})}),{root:this.refs.scroll.current,rootMargin:`${15*this.props.emojiButtonSize}px 0px ${10*this.props.emojiButtonSize}px`});for(const{rows:e}of this.refs.categories.values())for(const n of e)n.current&&t.observe(n.current);this.observers.push(t)}preventDefault(e){e.preventDefault()}unfocusSearch(){const e=this.refs.searchInput.current;e&&e.blur()}navigate({e,input:t,left:n,right:o,up:i,down:r}){const s=this.state.searchResults||this.grid;if(!s.length)return;let[a,l]=this.state.pos;const c=(()=>{if(0==a&&0==l&&!e.repeat&&(n||i))return null;if(-1==a)return e.repeat||!o&&!r||t.selectionStart!=t.value.length?null:[0,0];if(n||o){let e=s[a];const t=n?-1:1;if(l+=t,!e[l]){if(a+=t,e=s[a],!e)return a=n?0:s.length-1,l=n?0:s[a].length-1,[a,l];l=n?e.length-1:0}return[a,l]}if(i||r){a+=i?-1:1;const e=s[a];return e?(e[l]||(l=e.length-1),[a,l]):(a=i?0:s.length-1,l=i?0:s[a].length-1,[a,l])}})();c?(e.preventDefault(),this.setState({pos:c,keyboard:!0},(()=>{this.scrollTo({row:c[0]})}))):this.state.pos[0]>-1&&this.setState({pos:[-1,-1]})}scrollTo({categoryId:e,row:t}){const n=this.state.searchResults||this.grid;if(!n.length)return;const o=this.refs.scroll.current,i=o.getBoundingClientRect();let r=0;if(t>=0&&(e=n[t].__categoryId),e&&(r=(this.refs[e]||this.refs.categories.get(e).root).current.getBoundingClientRect().top-(i.top-o.scrollTop)+1),t>=0)if(t){const e=r+n[t].__index*this.props.emojiButtonSize,s=e+this.props.emojiButtonSize+.88*this.props.emojiButtonSize;if(eo.scrollTop+i.height))return;r=s-i.height}}else r=0;this.ignoreMouse(),o.scrollTop=r}ignoreMouse(){this.mouseIsIgnored=!0,clearTimeout(this.ignoreMouseTimer),this.ignoreMouseTimer=setTimeout((()=>{delete this.mouseIsIgnored}),100)}handleEmojiOver(e){this.mouseIsIgnored||this.state.showSkins||this.setState({pos:e||[-1,-1],keyboard:!1})}handleEmojiClick({e,emoji:t,pos:n}){if(this.props.onEmojiSelect&&(!t&&n&&(t=this.getEmojiByPos(n)),t)){const n=de(t,{skinIndex:this.state.skin-1});this.props.maxFrequentRows&&G.add(n,this.props),this.props.onEmojiSelect(n,e)}}closeSkins(){this.state.showSkins&&(this.setState({showSkins:null,tempSkin:null}),this.base.removeEventListener("click",this.handleBaseClick),this.base.removeEventListener("keydown",this.handleBaseKeydown))}handleSkinMouseOver(e){this.setState({tempSkin:e})}handleSkinClick(e){this.ignoreMouse(),this.closeSkins(),this.setState({skin:e,tempSkin:null}),V.set("skin",e)}renderNav(){return D(Ye,{ref:this.refs.navigation,icons:this.props.icons,theme:this.state.theme,dir:this.dir,unfocused:!!this.state.searchResults,position:this.props.navPosition,onClick:this.handleCategoryClick},this.navKey)}renderPreview(){const e=this.getEmojiByPos(this.state.pos),t=this.state.searchResults&&!this.state.searchResults.length;return D("div",{id:"preview",class:"flex flex-middle",dir:this.dir,"data-position":this.props.previewPosition,children:[D("div",{class:"flex flex-middle flex-grow",children:[D("div",{class:"flex flex-auto flex-middle flex-center",style:{height:this.props.emojiButtonSize,fontSize:this.props.emojiButtonSize},children:D(pe,{emoji:e,id:t?this.props.noResultsEmoji||"cry":this.props.previewEmoji||("top"==this.props.previewPosition?"point_down":"point_up"),set:this.props.set,size:this.props.emojiButtonSize,skin:this.state.tempSkin||this.state.skin,spritesheet:!0,getSpritesheetURL:this.props.getSpritesheetURL})}),D("div",{class:`margin-${this.dir[0]}`,children:D("div",e||t?{class:`padding-${this.dir[2]} align-${this.dir[0]}`,children:[D("div",{class:"preview-title ellipsis",children:e?e.name:Y.search_no_results_1}),D("div",{class:"preview-subtitle ellipsis color-c",children:e?e.skins[0].shortcodes:Y.search_no_results_2})]}:{class:"preview-placeholder color-c",children:Y.pick})})]}),!e&&"preview"==this.props.skinTonePosition&&this.renderSkinToneButton()]})}renderEmojiButton(e,{pos:t,posinset:n,grid:o}){const i=this.props.emojiButtonSize,r=this.state.tempSkin||this.state.skin,s=(e.skins[r-1]||e.skins[0]).native,a=(l=this.state.pos,c=t,Array.isArray(l)&&Array.isArray(c)&&l.length===c.length&&l.every(((e,t)=>e==c[t])));var l,c;const d=t.concat(e.id).join("");return D(Ze,{selected:a,skin:r,size:i,children:D("button",{"aria-label":s,"aria-selected":a||void 0,"aria-posinset":n,"aria-setsize":o.setsize,"data-keyboard":this.state.keyboard,title:"none"==this.props.previewPosition?e.name:void 0,type:"button",class:"flex flex-center flex-middle",tabindex:"-1",onClick:t=>this.handleEmojiClick({e:t,emoji:e}),onMouseEnter:()=>this.handleEmojiOver(t),onMouseLeave:()=>this.handleEmojiOver(),style:{width:this.props.emojiButtonSize,height:this.props.emojiButtonSize,fontSize:this.props.emojiSize,lineHeight:0},children:[D("div",{"aria-hidden":"true",class:"background",style:{borderRadius:this.props.emojiButtonRadius,backgroundColor:this.props.emojiButtonColors?this.props.emojiButtonColors[(n-1)%this.props.emojiButtonColors.length]:void 0}}),D(pe,{emoji:e,set:this.props.set,size:this.props.emojiSize,skin:r,spritesheet:!0,getSpritesheetURL:this.props.getSpritesheetURL})]})},d)}renderSearch(){const e="none"==this.props.previewPosition||"search"==this.props.skinTonePosition;return D("div",{children:[D("div",{class:"spacer"}),D("div",{class:"flex flex-middle",children:[D("div",{class:"search relative flex-grow",children:[D("input",{type:"search",ref:this.refs.searchInput,placeholder:Y.search,onClick:this.handleSearchClick,onInput:this.handleSearchInput,onKeyDown:this.handleSearchKeyDown,autoComplete:"off"}),D("span",{class:"icon loupe flex",children:ue.search.loupe}),this.state.searchResults&&D("button",{title:"Clear","aria-label":"Clear",type:"button",class:"icon delete flex",onClick:this.clearSearch,onMouseDown:this.preventDefault,children:ue.search.delete})]}),e&&this.renderSkinToneButton()]})]})}renderSearchResults(){const{searchResults:e}=this.state;return e?D("div",{class:"category",ref:this.refs.search,children:[D("div",{class:`sticky padding-small align-${this.dir[0]}`,children:Y.categories.search}),D("div",{children:e.length?e.map(((t,n)=>D("div",{class:"flex",children:t.map(((t,o)=>this.renderEmojiButton(t,{pos:[n,o],posinset:n*this.props.perLine+o+1,grid:e})))}))):D("div",{class:`padding-small align-${this.dir[0]}`,children:this.props.onAddCustomEmoji&&D("a",{onClick:this.props.onAddCustomEmoji,children:Y.add_custom})})})]}):null}renderCategories(){const{categories:e}=Z,t=!!this.state.searchResults,n=this.getPerLine();return D("div",{style:{visibility:t?"hidden":void 0,display:t?"none":void 0,height:"100%"},children:e.map((e=>{const{root:t,rows:o}=this.refs.categories.get(e.id);return D("div",{"data-id":e.target?e.target.id:e.id,class:"category",ref:t,children:[D("div",{class:`sticky padding-small align-${this.dir[0]}`,children:e.name||Y.categories[e.id]}),D("div",{class:"relative",style:{height:o.length*this.props.emojiButtonSize},children:o.map(((t,o)=>{const i=t.index-t.index%10,r=this.state.visibleRows[i],s="current"in t?t:void 0;if(!r&&!s)return null;const a=o*n,l=a+n,c=e.emojis.slice(a,l);return c.length{if(!e)return D("div",{style:{width:this.props.emojiButtonSize,height:this.props.emojiButtonSize}});const o=le.get(e);return this.renderEmojiButton(o,{pos:[t.index,n],posinset:t.posinset+n,grid:this.grid})}))},t.index)}))})]})}))})}renderSkinToneButton(){return"none"==this.props.skinTonePosition?null:D("div",{class:"flex flex-auto flex-center flex-middle",style:{position:"relative",width:this.props.emojiButtonSize,height:this.props.emojiButtonSize},children:D("button",{type:"button",ref:this.refs.skinToneButton,class:"skin-tone-button flex flex-auto flex-center flex-middle","aria-selected":this.state.showSkins?"":void 0,"aria-label":Y.skins.choose,title:Y.skins.choose,onClick:this.openSkins,style:{width:this.props.emojiSize,height:this.props.emojiSize},children:D("span",{class:`skin-tone skin-tone-${this.state.skin}`})})})}renderLiveRegion(){const e=this.getEmojiByPos(this.state.pos);return D("div",{"aria-live":"polite",class:"sr-only",children:e?e.name:""})}renderSkins(){const e=this.refs.skinToneButton.current.getBoundingClientRect(),t=this.base.getBoundingClientRect(),n={};return"ltr"==this.dir?n.right=t.right-e.right-3:n.left=e.left-t.left-3,"bottom"==this.props.previewPosition&&"preview"==this.props.skinTonePosition?n.bottom=t.bottom-e.top+6:(n.top=e.bottom-t.top+3,n.bottom="auto"),D("div",{ref:this.refs.menu,role:"radiogroup",dir:this.dir,"aria-label":Y.skins.choose,class:"menu hidden","data-position":n.top?"top":"bottom",style:n,children:[...Array(6).keys()].map((e=>{const t=e+1,n=this.state.skin==t;return D("div",{children:[D("input",{type:"radio",name:"skin-tone",value:t,"aria-label":Y.skins[t],ref:n?this.refs.skinToneRadio:null,defaultChecked:n,onChange:()=>this.handleSkinMouseOver(t),onKeyDown:e=>{"Enter"!=e.code&&"Space"!=e.code&&"Tab"!=e.code||(e.preventDefault(),this.handleSkinClick(t))}}),D("button",{"aria-hidden":"true",tabindex:"-1",onClick:()=>this.handleSkinClick(t),onMouseEnter:()=>this.handleSkinMouseOver(t),onMouseLeave:()=>this.handleSkinMouseOver(),class:"option flex flex-grow flex-middle",children:[D("span",{class:`skin-tone skin-tone-${t}`}),D("span",{class:"margin-small-lr",children:Y.skins[t]})]})]})}))})}render(){const e=this.props.perLine*this.props.emojiButtonSize;return D("section",{id:"root",class:"flex flex-column",dir:this.dir,style:{width:this.props.dynamicWidth?"100%":`calc(${e}px + (var(--padding) + var(--sidebar-width)))`},"data-emoji-set":this.props.set,"data-theme":this.state.theme,"data-menu":this.state.showSkins?"":void 0,children:["top"==this.props.previewPosition&&this.renderPreview(),"top"==this.props.navPosition&&this.renderNav(),"sticky"==this.props.searchPosition&&D("div",{class:"padding-lr",children:this.renderSearch()}),D("div",{ref:this.refs.scroll,class:"scroll flex-grow padding-lr",children:D("div",{style:{width:this.props.dynamicWidth?"100%":e,height:"100%"},children:["static"==this.props.searchPosition&&this.renderSearch(),this.renderSearchResults(),this.renderCategories()]})}),"bottom"==this.props.navPosition&&this.renderNav(),"bottom"==this.props.previewPosition&&this.renderPreview(),this.state.showSkins&&this.renderSkins(),this.renderLiveRegion()]})}constructor(e){super(),i(this,"darkMediaCallback",(()=>{"auto"==this.props.theme&&this.setState({theme:this.darkMedia.matches?"dark":"light"})})),i(this,"handleClickOutside",(e=>{const{element:t}=this.props;e.target!=t&&(this.state.showSkins&&this.closeSkins(),this.props.onClickOutside&&this.props.onClickOutside(e))})),i(this,"handleBaseClick",(e=>{this.state.showSkins&&(e.target.closest(".menu")||(e.preventDefault(),e.stopImmediatePropagation(),this.closeSkins()))})),i(this,"handleBaseKeydown",(e=>{this.state.showSkins&&"Escape"==e.key&&(e.preventDefault(),e.stopImmediatePropagation(),this.closeSkins())})),i(this,"handleSearchClick",(()=>{this.getEmojiByPos(this.state.pos)&&this.setState({pos:[-1,-1]})})),i(this,"handleSearchInput",(async()=>{const e=this.refs.searchInput.current;if(!e)return;const{value:t}=e,n=await le.search(t),o=()=>{this.refs.scroll.current&&(this.refs.scroll.current.scrollTop=0)};if(!n)return this.setState({searchResults:n,pos:[-1,-1]},o);const i=e.selectionStart==e.value.length?[0,0]:[-1,-1],r=[];r.setsize=n.length;let s=null;for(let e of n)r.length&&s.length!=this.getPerLine()||(s=[],s.__categoryId="search",s.__index=r.length,r.push(s)),s.push(e);this.ignoreMouse(),this.setState({searchResults:r,pos:i},o)})),i(this,"handleSearchKeyDown",(e=>{const t=e.currentTarget;switch(e.stopImmediatePropagation(),e.key){case"ArrowLeft":this.navigate({e,input:t,left:!0});break;case"ArrowRight":this.navigate({e,input:t,right:!0});break;case"ArrowUp":this.navigate({e,input:t,up:!0});break;case"ArrowDown":this.navigate({e,input:t,down:!0});break;case"Enter":e.preventDefault(),this.handleEmojiClick({e,pos:this.state.pos});break;case"Escape":e.preventDefault(),this.state.searchResults?this.clearSearch():this.unfocusSearch()}})),i(this,"clearSearch",(()=>{const e=this.refs.searchInput.current;e&&(e.value="",e.focus(),this.handleSearchInput())})),i(this,"handleCategoryClick",(({category:e,i:t})=>{this.scrollTo(0==t?{row:-1}:{categoryId:e.id})})),i(this,"openSkins",(e=>{const{currentTarget:t}=e,n=t.getBoundingClientRect();this.setState({showSkins:n},(async()=>{await async function(e=1){for(let t in[...Array(e).keys()])await new Promise(requestAnimationFrame)}(2);const e=this.refs.menu.current;e&&(e.classList.remove("hidden"),this.refs.skinToneRadio.current.focus(),this.base.addEventListener("click",this.handleBaseClick,!0),this.base.addEventListener("keydown",this.handleBaseKeydown,!0))}))})),this.observers=[],this.state={pos:[-1,-1],perLine:this.initDynamicPerLine(e),visibleRows:{0:!0},...this.getInitialState(e)}}}class et extends ge{async connectedCallback(){const e=re(this.props,J,this);e.element=this,e.ref=e=>{this.component=e},await ie(e),this.disconnected||I(D(Qe,{...e}),this.shadowRoot)}constructor(e){super(e,{styles:o(tt)})}}i(et,"Props",J),"undefined"==typeof customElements||customElements.get("em-emoji-picker")||customElements.define("em-emoji-picker",et);var tt={};tt=':host {\n width: min-content;\n height: 435px;\n min-height: 230px;\n border-radius: var(--border-radius);\n box-shadow: var(--shadow);\n --border-radius: 10px;\n --category-icon-size: 18px;\n --font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;\n --font-size: 15px;\n --preview-placeholder-size: 21px;\n --preview-title-size: 1.1em;\n --preview-subtitle-size: .9em;\n --shadow-color: 0deg 0% 0%;\n --shadow: .3px .5px 2.7px hsl(var(--shadow-color) / .14), .4px .8px 1px -3.2px hsl(var(--shadow-color) / .14), 1px 2px 2.5px -4.5px hsl(var(--shadow-color) / .14);\n display: flex;\n}\n\n[data-theme="light"] {\n --em-rgb-color: var(--rgb-color, 34, 36, 39);\n --em-rgb-accent: var(--rgb-accent, 34, 102, 237);\n --em-rgb-background: var(--rgb-background, 255, 255, 255);\n --em-rgb-input: var(--rgb-input, 255, 255, 255);\n --em-color-border: var(--color-border, rgba(0, 0, 0, .05));\n --em-color-border-over: var(--color-border-over, rgba(0, 0, 0, .1));\n}\n\n[data-theme="dark"] {\n --em-rgb-color: var(--rgb-color, 222, 222, 221);\n --em-rgb-accent: var(--rgb-accent, 58, 130, 247);\n --em-rgb-background: var(--rgb-background, 21, 22, 23);\n --em-rgb-input: var(--rgb-input, 0, 0, 0);\n --em-color-border: var(--color-border, rgba(255, 255, 255, .1));\n --em-color-border-over: var(--color-border-over, rgba(255, 255, 255, .2));\n}\n\n#root {\n --color-a: rgb(var(--em-rgb-color));\n --color-b: rgba(var(--em-rgb-color), .65);\n --color-c: rgba(var(--em-rgb-color), .45);\n --padding: 12px;\n --padding-small: calc(var(--padding) / 2);\n --sidebar-width: 16px;\n --duration: 225ms;\n --duration-fast: 125ms;\n --duration-instant: 50ms;\n --easing: cubic-bezier(.4, 0, .2, 1);\n width: 100%;\n text-align: left;\n border-radius: var(--border-radius);\n background-color: rgb(var(--em-rgb-background));\n position: relative;\n}\n\n@media (prefers-reduced-motion) {\n #root {\n --duration: 0;\n --duration-fast: 0;\n --duration-instant: 0;\n }\n}\n\n#root[data-menu] button {\n cursor: auto;\n}\n\n#root[data-menu] .menu button {\n cursor: pointer;\n}\n\n:host, #root, input, button {\n color: rgb(var(--em-rgb-color));\n font-family: var(--font-family);\n font-size: var(--font-size);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n line-height: normal;\n}\n\n*, :before, :after {\n box-sizing: border-box;\n min-width: 0;\n margin: 0;\n padding: 0;\n}\n\n.relative {\n position: relative;\n}\n\n.flex {\n display: flex;\n}\n\n.flex-auto {\n flex: none;\n}\n\n.flex-center {\n justify-content: center;\n}\n\n.flex-column {\n flex-direction: column;\n}\n\n.flex-grow {\n flex: auto;\n}\n\n.flex-middle {\n align-items: center;\n}\n\n.flex-wrap {\n flex-wrap: wrap;\n}\n\n.padding {\n padding: var(--padding);\n}\n\n.padding-t {\n padding-top: var(--padding);\n}\n\n.padding-lr {\n padding-left: var(--padding);\n padding-right: var(--padding);\n}\n\n.padding-r {\n padding-right: var(--padding);\n}\n\n.padding-small {\n padding: var(--padding-small);\n}\n\n.padding-small-b {\n padding-bottom: var(--padding-small);\n}\n\n.padding-small-lr {\n padding-left: var(--padding-small);\n padding-right: var(--padding-small);\n}\n\n.margin {\n margin: var(--padding);\n}\n\n.margin-r {\n margin-right: var(--padding);\n}\n\n.margin-l {\n margin-left: var(--padding);\n}\n\n.margin-small-l {\n margin-left: var(--padding-small);\n}\n\n.margin-small-lr {\n margin-left: var(--padding-small);\n margin-right: var(--padding-small);\n}\n\n.align-l {\n text-align: left;\n}\n\n.align-r {\n text-align: right;\n}\n\n.color-a {\n color: var(--color-a);\n}\n\n.color-b {\n color: var(--color-b);\n}\n\n.color-c {\n color: var(--color-c);\n}\n\n.ellipsis {\n white-space: nowrap;\n max-width: 100%;\n width: auto;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.sr-only {\n width: 1px;\n height: 1px;\n position: absolute;\n top: auto;\n left: -10000px;\n overflow: hidden;\n}\n\na {\n cursor: pointer;\n color: rgb(var(--em-rgb-accent));\n}\n\na:hover {\n text-decoration: underline;\n}\n\n.spacer {\n height: 10px;\n}\n\n[dir="rtl"] .scroll {\n padding-left: 0;\n padding-right: var(--padding);\n}\n\n.scroll {\n padding-right: 0;\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.scroll::-webkit-scrollbar {\n width: var(--sidebar-width);\n height: var(--sidebar-width);\n}\n\n.scroll::-webkit-scrollbar-track {\n border: 0;\n}\n\n.scroll::-webkit-scrollbar-button {\n width: 0;\n height: 0;\n display: none;\n}\n\n.scroll::-webkit-scrollbar-corner {\n background-color: rgba(0, 0, 0, 0);\n}\n\n.scroll::-webkit-scrollbar-thumb {\n min-height: 20%;\n min-height: 65px;\n border: 4px solid rgb(var(--em-rgb-background));\n border-radius: 8px;\n}\n\n.scroll::-webkit-scrollbar-thumb:hover {\n background-color: var(--em-color-border-over) !important;\n}\n\n.scroll:hover::-webkit-scrollbar-thumb {\n background-color: var(--em-color-border);\n}\n\n.sticky {\n z-index: 1;\n background-color: rgba(var(--em-rgb-background), .9);\n -webkit-backdrop-filter: blur(4px);\n backdrop-filter: blur(4px);\n font-weight: 500;\n position: sticky;\n top: -1px;\n}\n\n[dir="rtl"] .search input[type="search"] {\n padding: 10px 2.2em 10px 2em;\n}\n\n[dir="rtl"] .search .loupe {\n left: auto;\n right: .7em;\n}\n\n[dir="rtl"] .search .delete {\n left: .7em;\n right: auto;\n}\n\n.search {\n z-index: 2;\n position: relative;\n}\n\n.search input, .search button {\n font-size: calc(var(--font-size) - 1px);\n}\n\n.search input[type="search"] {\n width: 100%;\n background-color: var(--em-color-border);\n transition-duration: var(--duration);\n transition-property: background-color, box-shadow;\n transition-timing-function: var(--easing);\n border: 0;\n border-radius: 10px;\n outline: 0;\n padding: 10px 2em 10px 2.2em;\n display: block;\n}\n\n.search input[type="search"]::-ms-input-placeholder {\n color: inherit;\n opacity: .6;\n}\n\n.search input[type="search"]::placeholder {\n color: inherit;\n opacity: .6;\n}\n\n.search input[type="search"], .search input[type="search"]::-webkit-search-decoration, .search input[type="search"]::-webkit-search-cancel-button, .search input[type="search"]::-webkit-search-results-button, .search input[type="search"]::-webkit-search-results-decoration {\n -webkit-appearance: none;\n -ms-appearance: none;\n appearance: none;\n}\n\n.search input[type="search"]:focus {\n background-color: rgb(var(--em-rgb-input));\n box-shadow: inset 0 0 0 1px rgb(var(--em-rgb-accent)), 0 1px 3px rgba(65, 69, 73, .2);\n}\n\n.search .icon {\n z-index: 1;\n color: rgba(var(--em-rgb-color), .7);\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n}\n\n.search .loupe {\n pointer-events: none;\n left: .7em;\n}\n\n.search .delete {\n right: .7em;\n}\n\nsvg {\n fill: currentColor;\n width: 1em;\n height: 1em;\n}\n\nbutton {\n -webkit-appearance: none;\n -ms-appearance: none;\n appearance: none;\n cursor: pointer;\n color: currentColor;\n background-color: rgba(0, 0, 0, 0);\n border: 0;\n}\n\n#nav {\n z-index: 2;\n padding-top: 12px;\n padding-bottom: 12px;\n padding-right: var(--sidebar-width);\n position: relative;\n}\n\n#nav button {\n color: var(--color-b);\n transition: color var(--duration) var(--easing);\n}\n\n#nav button:hover {\n color: var(--color-a);\n}\n\n#nav svg, #nav img {\n width: var(--category-icon-size);\n height: var(--category-icon-size);\n}\n\n#nav[dir="rtl"] .bar {\n left: auto;\n right: 0;\n}\n\n#nav .bar {\n width: 100%;\n height: 3px;\n background-color: rgb(var(--em-rgb-accent));\n transition: transform var(--duration) var(--easing);\n border-radius: 3px 3px 0 0;\n position: absolute;\n bottom: -12px;\n left: 0;\n}\n\n#nav button[aria-selected] {\n color: rgb(var(--em-rgb-accent));\n}\n\n#preview {\n z-index: 2;\n padding: calc(var(--padding) + 4px) var(--padding);\n padding-right: var(--sidebar-width);\n position: relative;\n}\n\n#preview .preview-placeholder {\n font-size: var(--preview-placeholder-size);\n}\n\n#preview .preview-title {\n font-size: var(--preview-title-size);\n}\n\n#preview .preview-subtitle {\n font-size: var(--preview-subtitle-size);\n}\n\n#nav:before, #preview:before {\n content: "";\n height: 2px;\n position: absolute;\n left: 0;\n right: 0;\n}\n\n#nav[data-position="top"]:before, #preview[data-position="top"]:before {\n background: linear-gradient(to bottom, var(--em-color-border), transparent);\n top: 100%;\n}\n\n#nav[data-position="bottom"]:before, #preview[data-position="bottom"]:before {\n background: linear-gradient(to top, var(--em-color-border), transparent);\n bottom: 100%;\n}\n\n.category:last-child {\n min-height: calc(100% + 1px);\n}\n\n.category button {\n font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;\n position: relative;\n}\n\n.category button > * {\n position: relative;\n}\n\n.category button .background {\n opacity: 0;\n background-color: var(--em-color-border);\n transition: opacity var(--duration-fast) var(--easing) var(--duration-instant);\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n}\n\n.category button:hover .background {\n transition-duration: var(--duration-instant);\n transition-delay: 0s;\n}\n\n.category button[aria-selected] .background {\n opacity: 1;\n}\n\n.category button[data-keyboard] .background {\n transition: none;\n}\n\n.row {\n width: 100%;\n position: absolute;\n top: 0;\n left: 0;\n}\n\n.skin-tone-button {\n border: 1px solid rgba(0, 0, 0, 0);\n border-radius: 100%;\n}\n\n.skin-tone-button:hover {\n border-color: var(--em-color-border);\n}\n\n.skin-tone-button:active .skin-tone {\n transform: scale(.85) !important;\n}\n\n.skin-tone-button .skin-tone {\n transition: transform var(--duration) var(--easing);\n}\n\n.skin-tone-button[aria-selected] {\n background-color: var(--em-color-border);\n border-top-color: rgba(0, 0, 0, .05);\n border-bottom-color: rgba(0, 0, 0, 0);\n border-left-width: 0;\n border-right-width: 0;\n}\n\n.skin-tone-button[aria-selected] .skin-tone {\n transform: scale(.9);\n}\n\n.menu {\n z-index: 2;\n white-space: nowrap;\n border: 1px solid var(--em-color-border);\n background-color: rgba(var(--em-rgb-background), .9);\n -webkit-backdrop-filter: blur(4px);\n backdrop-filter: blur(4px);\n transition-property: opacity, transform;\n transition-duration: var(--duration);\n transition-timing-function: var(--easing);\n border-radius: 10px;\n padding: 4px;\n position: absolute;\n box-shadow: 1px 1px 5px rgba(0, 0, 0, .05);\n}\n\n.menu.hidden {\n opacity: 0;\n}\n\n.menu[data-position="bottom"] {\n transform-origin: 100% 100%;\n}\n\n.menu[data-position="bottom"].hidden {\n transform: scale(.9)rotate(-3deg)translateY(5%);\n}\n\n.menu[data-position="top"] {\n transform-origin: 100% 0;\n}\n\n.menu[data-position="top"].hidden {\n transform: scale(.9)rotate(3deg)translateY(-5%);\n}\n\n.menu input[type="radio"] {\n clip: rect(0 0 0 0);\n width: 1px;\n height: 1px;\n border: 0;\n margin: 0;\n padding: 0;\n position: absolute;\n overflow: hidden;\n}\n\n.menu input[type="radio"]:checked + .option {\n box-shadow: 0 0 0 2px rgb(var(--em-rgb-accent));\n}\n\n.option {\n width: 100%;\n border-radius: 6px;\n padding: 4px 6px;\n}\n\n.option:hover {\n color: #fff;\n background-color: rgb(var(--em-rgb-accent));\n}\n\n.skin-tone {\n width: 16px;\n height: 16px;\n border-radius: 100%;\n display: inline-block;\n position: relative;\n overflow: hidden;\n}\n\n.skin-tone:after {\n content: "";\n mix-blend-mode: overlay;\n background: linear-gradient(rgba(255, 255, 255, .2), rgba(0, 0, 0, 0));\n border: 1px solid rgba(0, 0, 0, .8);\n border-radius: 100%;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n box-shadow: inset 0 -2px 3px #000, inset 0 1px 2px #fff;\n}\n\n.skin-tone-1 {\n background-color: #ffc93a;\n}\n\n.skin-tone-2 {\n background-color: #ffdab7;\n}\n\n.skin-tone-3 {\n background-color: #e7b98f;\n}\n\n.skin-tone-4 {\n background-color: #c88c61;\n}\n\n.skin-tone-5 {\n background-color: #a46134;\n}\n\n.skin-tone-6 {\n background-color: #5d4437;\n}\n\n[data-index] {\n justify-content: space-between;\n}\n\n[data-emoji-set="twitter"] .skin-tone:after {\n box-shadow: none;\n border-color: rgba(0, 0, 0, .5);\n}\n\n[data-emoji-set="twitter"] .skin-tone-1 {\n background-color: #fade72;\n}\n\n[data-emoji-set="twitter"] .skin-tone-2 {\n background-color: #f3dfd0;\n}\n\n[data-emoji-set="twitter"] .skin-tone-3 {\n background-color: #eed3a8;\n}\n\n[data-emoji-set="twitter"] .skin-tone-4 {\n background-color: #cfad8d;\n}\n\n[data-emoji-set="twitter"] .skin-tone-5 {\n background-color: #a8805d;\n}\n\n[data-emoji-set="twitter"] .skin-tone-6 {\n background-color: #765542;\n}\n\n[data-emoji-set="google"] .skin-tone:after {\n box-shadow: inset 0 0 2px 2px rgba(0, 0, 0, .4);\n}\n\n[data-emoji-set="google"] .skin-tone-1 {\n background-color: #f5c748;\n}\n\n[data-emoji-set="google"] .skin-tone-2 {\n background-color: #f1d5aa;\n}\n\n[data-emoji-set="google"] .skin-tone-3 {\n background-color: #d4b48d;\n}\n\n[data-emoji-set="google"] .skin-tone-4 {\n background-color: #aa876b;\n}\n\n[data-emoji-set="google"] .skin-tone-5 {\n background-color: #916544;\n}\n\n[data-emoji-set="google"] .skin-tone-6 {\n background-color: #61493f;\n}\n\n[data-emoji-set="facebook"] .skin-tone:after {\n border-color: rgba(0, 0, 0, .4);\n box-shadow: inset 0 -2px 3px #000, inset 0 1px 4px #fff;\n}\n\n[data-emoji-set="facebook"] .skin-tone-1 {\n background-color: #f5c748;\n}\n\n[data-emoji-set="facebook"] .skin-tone-2 {\n background-color: #f1d5aa;\n}\n\n[data-emoji-set="facebook"] .skin-tone-3 {\n background-color: #d4b48d;\n}\n\n[data-emoji-set="facebook"] .skin-tone-4 {\n background-color: #aa876b;\n}\n\n[data-emoji-set="facebook"] .skin-tone-5 {\n background-color: #916544;\n}\n\n[data-emoji-set="facebook"] .skin-tone-6 {\n background-color: #61493f;\n}\n\n'}}]);
\ No newline at end of file
diff --git a/docs/webchat.md b/docs/webchat.md
index 6950815..d5e24ed 100644
--- a/docs/webchat.md
+++ b/docs/webchat.md
@@ -7,7 +7,7 @@ When a business has a webchat configured through the dashboard, this is enough:
Hellotext.initialize('PUBLIC_BUSINESS_ID')
```
-Install-level configuration can override dashboard settings. Passing `webchat: false` disables the automatic webchat mount.
+Install-level configuration can override dashboard settings. Passing `webchat: false` disables the automatic webchat mount. Overrides are sparse and do not update dashboard settings.
```js
Hellotext.initialize('PUBLIC_BUSINESS_ID', { webchat: false })
@@ -19,13 +19,19 @@ Hellotext.initialize('PUBLIC_BUSINESS_ID', {
id,
container,
placement,
- classes,
- triggerClasses,
- style: {
- primaryColor,
- secondaryColor,
- typography,
+ appearance: {
+ header: {
+ name,
+ },
+ launcher: {
+ iconUrl,
+ },
},
+ whatsapp: {
+ number,
+ restrictToChannel,
+ },
+ mode,
behaviour,
strategy,
},
@@ -37,10 +43,10 @@ Hellotext.initialize('PUBLIC_BUSINESS_ID', {
| id | The id of the webchat to load. Overrides the dashboard webchat id when provided. | String | Dashboard id |
| container | The container to append the webchat to, must be a valid CSS selector. If none specified, the webchat is appended at the end of the body. | String | `body` |
| placement | The placement of the webchat, determined according to the parent `container`. | Enum | `bottom-right` |
-| classes | An array or comma separated String of additional CSS classes to apply to the webchat popover. | String | null |
-| triggerClasses | An array or comma separated String of additional CSS classes to apply to the webchat trigger. | String | null |
-| style | Style overrides to the WebChat's style configuration as created on the dashboard. | Object | null |
-| behaviour | The behaviour of the webchat when it is open and a click was made outside of it | Enum | `popover` |
+| appearance | Appearance overrides for the configured Webchat. | Object | Dashboard |
+| whatsapp | WhatsApp handoff overrides for the configured Webchat. | Object | Dashboard |
+| mode | The mode of the webchat when it is open and a click was made outside of it | Enum | `popover` |
+| behaviour | The runtime opening behaviour of the webchat | Object | Dashboard |
| strategy | The positioning strategy for the webchat when it is open and the ancestor is scrolled | Enum | `absolute` |
### Position
@@ -52,38 +58,153 @@ The default position for a webchat is `bottom-right`, but you can specify any of
- `top-left`
- `top-right`
-### Style
+### Appearance
+
+The `appearance` object provides sparse overrides for the configured Webchat. These values customize the mounted widget and do not update dashboard settings.
+
+```js
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ appearance: {
+ header: {
+ name: 'Acme Support',
+ },
+ launcher: {
+ iconUrl: 'https://example.com/webchat-icon.png',
+ },
+ },
+ },
+})
+```
-The following properties are accepted for the `style` object.
+The following properties are accepted.
-- `primaryColor` - The primary color of the Webchat. Must be either in hex or rgb/a formats. Affects the following elements:
- - Trigger background
- - Popover header background
- - Agent icon color
- - Toolbar button hover
- - Incoming message background
+| Property | Description | Type |
+| ---------------- | ------------------------------------------------------------------------------------------------ | ------ |
+| header.name | Business name shown in the Webchat header. | String |
+| launcher.iconUrl | Image URL used for the launcher icon. When present, it is shown in minimized and expanded states. | String |
-The primary color is controlled via a `--webchat-primary-color` CSS variable.
+### WhatsApp
-- `secondaryColor` - The secondary color of the webchat. Must be either in hex or rgb/a formats. Affects the following elements:
- - Trigger icon color
- - Popover header text color
- - Agent icon background
- - Incoming message text color
+The `whatsapp` object configures optional WhatsApp handoff. These values customize the mounted widget and do not update dashboard settings.
-The secondary color is controlled via a `--webchat-secondary-color` CSS variable.
+```js
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ whatsapp: {
+ number: '+15551234567',
+ restrictToChannel: true,
+ },
+ },
+})
+```
-- `typography` - The font family to use for the webchat.
+The following properties are accepted.
-All properties accept a valid CSS value, for example, `primaryColor: '#EEEEEE'` or `secondaryColor: '#ff0000'`.
+| Property | Description | Type |
+| ----------------- | -------------------------------------------------------------------------------------------- | ------- |
+| number | WhatsApp phone number used by the handoff action. | String |
+| restrictToChannel | Whether the Webchat should restrict the conversation path to the configured WhatsApp channel. | Boolean |
-### behaviour
+### Mode
Determines how the webchat functions when it is open and a click is made outside of it. The following values are accepted.
- `popover` - Closes the webchat when a click is made outside of it. This is the default behaviour.
- `modal` - Prevents the webchat from closing when a click is made outside of it. The webchat can only be closed by clicking on the trigger.
+```js
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ mode: 'popover',
+ },
+})
+```
+
+### Behaviour
+
+Determines what opens the webchat at runtime. The public JavaScript configuration uses camelCase.
+
+```js
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ behaviour: {
+ trigger: 'onLoad',
+ delaySeconds: 5,
+ firstVisitOnly: true,
+ oncePerSession: true,
+ },
+ },
+})
+```
+
+The following properties are accepted.
+
+| Property | Description | Type |
+| -------------- | --------------------------------------------------------------------------------------------------- | ------- |
+| trigger | `onClick` keeps the current click-only behaviour. `onLoad` opens the webchat after `delaySeconds`. | Enum |
+| delaySeconds | Delay before opening on load. Accepted values are `0`, `5`, `10`, and `30`. | Number |
+| firstVisitOnly | When true, the automatic open only happens once for the visitor. | Boolean |
+| oncePerSession | When true, the automatic open only happens once per browser session. | Boolean |
+
+Examples:
+
+```js
+// Click-only, no automatic open.
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ behaviour: {
+ trigger: 'onClick',
+ delaySeconds: 0,
+ firstVisitOnly: false,
+ oncePerSession: false,
+ },
+ },
+})
+```
+
+```js
+// Open immediately after mount.
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ behaviour: {
+ trigger: 'onLoad',
+ delaySeconds: 0,
+ firstVisitOnly: false,
+ oncePerSession: false,
+ },
+ },
+})
+```
+
+```js
+// Open after five seconds, once per browser session.
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ behaviour: {
+ trigger: 'onLoad',
+ delaySeconds: 5,
+ firstVisitOnly: false,
+ oncePerSession: true,
+ },
+ },
+})
+```
+
+```js
+// Open only on the first visit.
+Hellotext.initialize('PUBLIC_BUSINESS_ID', {
+ webchat: {
+ behaviour: {
+ trigger: 'onLoad',
+ delaySeconds: 5,
+ firstVisitOnly: true,
+ oncePerSession: false,
+ },
+ },
+})
+```
+
### Strategy
Determines the positioning strategy for the webchat when the page is scrolled.
diff --git a/index.d.ts b/index.d.ts
index b047c4b..5e1216f 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -10,19 +10,45 @@ export interface HellotextConfig {
autoGenerateSession?: boolean
}
+export type HellotextWebchatMode = 'modal' | 'popover'
+export type HellotextWebchatStrategy = 'absolute' | 'fixed'
+export type HellotextWebchatBehaviourTrigger = 'onClick' | 'onLoad'
+
+export interface HellotextWebchatBehaviour {
+ trigger: HellotextWebchatBehaviourTrigger
+ delaySeconds: 0 | 5 | 10 | 30
+ firstVisitOnly: boolean
+ oncePerSession: boolean
+}
+
+export interface HellotextWebchatAppearance {
+ header?: {
+ name?: string | null
+ }
+ launcher?: {
+ iconUrl?: string | null
+ }
+}
+
+export interface HellotextWebchatWhatsApp {
+ number?: string | null
+ restrictToChannel?: boolean | null
+}
+
export interface HellotextWebchatConfig {
id?: string
container?: string
placement?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
- classes?: string | string[]
- triggerClasses?: string | string[]
- behaviour?: 'modal' | 'popover'
+ mode?: HellotextWebchatMode
+ behaviour?: HellotextWebchatBehaviour
style?: {
primaryColor?: string
secondaryColor?: string
typography?: string
}
- strategy?: 'absolute' | 'fixed'
+ appearance?: HellotextWebchatAppearance
+ whatsapp?: HellotextWebchatWhatsApp
+ strategy?: HellotextWebchatStrategy
}
export interface HellotextBusinessCountry {
diff --git a/lib/api/webchats.cjs b/lib/api/webchats.cjs
index d8e0388..b6789f0 100644
--- a/lib/api/webchats.cjs
+++ b/lib/api/webchats.cjs
@@ -30,6 +30,7 @@ let WebchatsAPI = /*#__PURE__*/function () {
Object.entries(_core.Configuration.webchat.style).forEach(([key, value]) => {
url.searchParams.append(`style[${key}]`, value);
});
+ this.appendWebchatOverrides(url);
url.searchParams.append('placement', _core.Configuration.webchat.placement);
const response = await fetch(url, {
method: 'GET',
@@ -42,6 +43,25 @@ let WebchatsAPI = /*#__PURE__*/function () {
}
return new DOMParser().parseFromString(data.html, 'text/html').querySelector('article');
}
+ }, {
+ key: "appendWebchatOverrides",
+ value: function appendWebchatOverrides(url) {
+ var _appearance$header, _appearance$launcher;
+ const {
+ appearance,
+ whatsapp
+ } = _core.Configuration.webchat;
+ this.appendIfSupplied(url, 'webchat[appearance][header][name]', (_appearance$header = appearance.header) === null || _appearance$header === void 0 ? void 0 : _appearance$header.name);
+ this.appendIfSupplied(url, 'webchat[appearance][launcher][icon_url]', (_appearance$launcher = appearance.launcher) === null || _appearance$launcher === void 0 ? void 0 : _appearance$launcher.iconUrl);
+ this.appendIfSupplied(url, 'webchat[handoff][identifier]', whatsapp.number);
+ this.appendIfSupplied(url, 'webchat[handoff][restrict_to_channel]', whatsapp.restrictToChannel);
+ }
+ }, {
+ key: "appendIfSupplied",
+ value: function appendIfSupplied(url, key, value) {
+ if (value === undefined || value === null) return;
+ url.searchParams.append(key, String(value));
+ }
}]);
return WebchatsAPI;
}();
diff --git a/lib/api/webchats.js b/lib/api/webchats.js
index f0a85e5..b68c93f 100644
--- a/lib/api/webchats.js
+++ b/lib/api/webchats.js
@@ -27,6 +27,7 @@ var WebchatsAPI = /*#__PURE__*/function () {
var [key, value] = _ref;
url.searchParams.append("style[".concat(key, "]"), value);
});
+ this.appendWebchatOverrides(url);
url.searchParams.append('placement', Configuration.webchat.placement);
var response = yield fetch(url, {
method: 'GET',
@@ -44,6 +45,25 @@ var WebchatsAPI = /*#__PURE__*/function () {
}
return get;
}()
+ }, {
+ key: "appendWebchatOverrides",
+ value: function appendWebchatOverrides(url) {
+ var _appearance$header, _appearance$launcher;
+ var {
+ appearance,
+ whatsapp
+ } = Configuration.webchat;
+ this.appendIfSupplied(url, 'webchat[appearance][header][name]', (_appearance$header = appearance.header) === null || _appearance$header === void 0 ? void 0 : _appearance$header.name);
+ this.appendIfSupplied(url, 'webchat[appearance][launcher][icon_url]', (_appearance$launcher = appearance.launcher) === null || _appearance$launcher === void 0 ? void 0 : _appearance$launcher.iconUrl);
+ this.appendIfSupplied(url, 'webchat[handoff][identifier]', whatsapp.number);
+ this.appendIfSupplied(url, 'webchat[handoff][restrict_to_channel]', whatsapp.restrictToChannel);
+ }
+ }, {
+ key: "appendIfSupplied",
+ value: function appendIfSupplied(url, key, value) {
+ if (value === undefined || value === null) return;
+ url.searchParams.append(key, String(value));
+ }
}]);
return WebchatsAPI;
}();
diff --git a/lib/controllers/mixins/usePopover.cjs b/lib/controllers/mixins/usePopover.cjs
index 4bbcf63..0b6eb54 100644
--- a/lib/controllers/mixins/usePopover.cjs
+++ b/lib/controllers/mixins/usePopover.cjs
@@ -9,12 +9,16 @@ var _core = require("../../core");
const usePopover = controller => {
Object.assign(controller, {
show() {
+ var _this$cancelBehaviour;
+ (_this$cancelBehaviour = this.cancelBehaviourOpen) === null || _this$cancelBehaviour === void 0 ? void 0 : _this$cancelBehaviour.call(this);
this.openValue = true;
},
hide() {
this.openValue = false;
},
toggle() {
+ var _this$cancelBehaviour2;
+ (_this$cancelBehaviour2 = this.cancelBehaviourOpen) === null || _this$cancelBehaviour2 === void 0 ? void 0 : _this$cancelBehaviour2.call(this);
this.openValue = !this.openValue;
},
setupFloatingUI({
diff --git a/lib/controllers/mixins/usePopover.js b/lib/controllers/mixins/usePopover.js
index 0d795d9..effdad2 100644
--- a/lib/controllers/mixins/usePopover.js
+++ b/lib/controllers/mixins/usePopover.js
@@ -3,12 +3,16 @@ import { Configuration } from '../../core';
export var usePopover = controller => {
Object.assign(controller, {
show() {
+ var _this$cancelBehaviour;
+ (_this$cancelBehaviour = this.cancelBehaviourOpen) === null || _this$cancelBehaviour === void 0 ? void 0 : _this$cancelBehaviour.call(this);
this.openValue = true;
},
hide() {
this.openValue = false;
},
toggle() {
+ var _this$cancelBehaviour2;
+ (_this$cancelBehaviour2 = this.cancelBehaviourOpen) === null || _this$cancelBehaviour2 === void 0 ? void 0 : _this$cancelBehaviour2.call(this);
this.openValue = !this.openValue;
},
setupFloatingUI(_ref) {
diff --git a/lib/controllers/webchat/emoji_picker_controller.cjs b/lib/controllers/webchat/emoji_picker_controller.cjs
index 99dd971..384915a 100644
--- a/lib/controllers/webchat/emoji_picker_controller.cjs
+++ b/lib/controllers/webchat/emoji_picker_controller.cjs
@@ -6,11 +6,9 @@ Object.defineProperty(exports, "__esModule", {
exports.default = void 0;
var _dom = require("@floating-ui/dom");
var _stimulus = require("@hotwired/stimulus");
-var _en = _interopRequireDefault(require("@emoji-mart/data/i18n/en.json"));
-var _es = _interopRequireDefault(require("@emoji-mart/data/i18n/es.json"));
-var _emojiMart = require("emoji-mart");
var _usePopover = require("../mixins/usePopover");
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
@@ -36,23 +34,28 @@ let _default = /*#__PURE__*/function (_Controller) {
key: "initialize",
value: function initialize() {
this.onEmojiSelect = this.onEmojiSelect.bind(this);
+ this.pickerLoaded = false;
+ this.pickerLoadPromise = null;
+ this.connected = false;
_get(_getPrototypeOf(_default.prototype), "initialize", this).call(this);
}
}, {
key: "connect",
value: function connect() {
+ this.connected = true;
(0, _usePopover.usePopover)(this);
this.setupFloatingUI({
trigger: this.buttonTarget,
popover: this.popoverTarget,
strategy: 'absolute'
});
- this.popoverTarget.appendChild(this.pickerObject);
_get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
}
}, {
key: "disconnect",
value: function disconnect() {
+ this.connected = false;
+ this.pickerLoadPromise = null;
this.floatingUICleanup();
_get(_getPrototypeOf(_default.prototype), "disconnect", this).call(this);
}
@@ -72,9 +75,44 @@ let _default = /*#__PURE__*/function (_Controller) {
}
}
}, {
- key: "pickerObject",
- get: function () {
- return new _emojiMart.Picker({
+ key: "onPopoverOpened",
+ value: async function onPopoverOpened() {
+ await this.loadPicker();
+ }
+ }, {
+ key: "loadPicker",
+ value: async function loadPicker() {
+ if (this.pickerLoaded) return;
+ this.pickerLoadPromise || (this.pickerLoadPromise = this.loadPickerDependencies());
+ const {
+ Picker,
+ i18n
+ } = await this.pickerLoadPromise;
+ if (!this.connected || this.pickerLoaded) return;
+ this.popoverTarget.appendChild(this.buildPicker(Picker, i18n));
+ this.pickerLoaded = true;
+ }
+ }, {
+ key: "loadPickerDependencies",
+ value: async function loadPickerDependencies() {
+ const [pickerModule, i18nModule] = await Promise.all([Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "webchat-emoji" */'emoji-mart'))), this.loadI18n()]);
+ return {
+ Picker: pickerModule.Picker,
+ i18n: i18nModule.default || i18nModule
+ };
+ }
+ }, {
+ key: "loadI18n",
+ value: function loadI18n() {
+ if (Hellotext.business.locale === 'es') {
+ return Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "webchat-emoji-es" */'@emoji-mart/data/i18n/es.json')));
+ }
+ return Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "webchat-emoji-en" */'@emoji-mart/data/i18n/en.json')));
+ }
+ }, {
+ key: "buildPicker",
+ value: function buildPicker(Picker, i18n) {
+ return new Picker({
onEmojiSelect: this.onEmojiSelect,
theme: 'light',
dynamicWidth: true,
@@ -82,7 +120,7 @@ let _default = /*#__PURE__*/function (_Controller) {
skinTonePosition: 'none',
emojiSize: this.sizeValue,
perLine: this.perLineValue,
- i18n: Hellotext.business.locale === 'es' ? _es.default : _en.default
+ i18n
});
}
}, {
diff --git a/lib/controllers/webchat/emoji_picker_controller.js b/lib/controllers/webchat/emoji_picker_controller.js
index a4d5ebf..037f42f 100644
--- a/lib/controllers/webchat/emoji_picker_controller.js
+++ b/lib/controllers/webchat/emoji_picker_controller.js
@@ -1,3 +1,5 @@
+function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
+function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
@@ -14,9 +16,6 @@ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Re
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
import { autoPlacement, offset, shift } from '@floating-ui/dom';
import { Controller } from '@hotwired/stimulus';
-import en from '@emoji-mart/data/i18n/en.json';
-import es from '@emoji-mart/data/i18n/es.json';
-import { Picker } from 'emoji-mart';
import { usePopover } from '../mixins/usePopover';
var _default = /*#__PURE__*/function (_Controller) {
_inherits(_default, _Controller);
@@ -29,23 +28,28 @@ var _default = /*#__PURE__*/function (_Controller) {
key: "initialize",
value: function initialize() {
this.onEmojiSelect = this.onEmojiSelect.bind(this);
+ this.pickerLoaded = false;
+ this.pickerLoadPromise = null;
+ this.connected = false;
_get(_getPrototypeOf(_default.prototype), "initialize", this).call(this);
}
}, {
key: "connect",
value: function connect() {
+ this.connected = true;
usePopover(this);
this.setupFloatingUI({
trigger: this.buttonTarget,
popover: this.popoverTarget,
strategy: 'absolute'
});
- this.popoverTarget.appendChild(this.pickerObject);
_get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
}
}, {
key: "disconnect",
value: function disconnect() {
+ this.connected = false;
+ this.pickerLoadPromise = null;
this.floatingUICleanup();
_get(_getPrototypeOf(_default.prototype), "disconnect", this).call(this);
}
@@ -65,8 +69,61 @@ var _default = /*#__PURE__*/function (_Controller) {
}
}
}, {
- key: "pickerObject",
- get: function get() {
+ key: "onPopoverOpened",
+ value: function () {
+ var _onPopoverOpened = _asyncToGenerator(function* () {
+ yield this.loadPicker();
+ });
+ function onPopoverOpened() {
+ return _onPopoverOpened.apply(this, arguments);
+ }
+ return onPopoverOpened;
+ }()
+ }, {
+ key: "loadPicker",
+ value: function () {
+ var _loadPicker = _asyncToGenerator(function* () {
+ if (this.pickerLoaded) return;
+ this.pickerLoadPromise || (this.pickerLoadPromise = this.loadPickerDependencies());
+ var {
+ Picker,
+ i18n
+ } = yield this.pickerLoadPromise;
+ if (!this.connected || this.pickerLoaded) return;
+ this.popoverTarget.appendChild(this.buildPicker(Picker, i18n));
+ this.pickerLoaded = true;
+ });
+ function loadPicker() {
+ return _loadPicker.apply(this, arguments);
+ }
+ return loadPicker;
+ }()
+ }, {
+ key: "loadPickerDependencies",
+ value: function () {
+ var _loadPickerDependencies = _asyncToGenerator(function* () {
+ var [pickerModule, i18nModule] = yield Promise.all([import( /* webpackChunkName: "webchat-emoji" */'emoji-mart'), this.loadI18n()]);
+ return {
+ Picker: pickerModule.Picker,
+ i18n: i18nModule.default || i18nModule
+ };
+ });
+ function loadPickerDependencies() {
+ return _loadPickerDependencies.apply(this, arguments);
+ }
+ return loadPickerDependencies;
+ }()
+ }, {
+ key: "loadI18n",
+ value: function loadI18n() {
+ if (Hellotext.business.locale === 'es') {
+ return import( /* webpackChunkName: "webchat-emoji-es" */'@emoji-mart/data/i18n/es.json');
+ }
+ return import( /* webpackChunkName: "webchat-emoji-en" */'@emoji-mart/data/i18n/en.json');
+ }
+ }, {
+ key: "buildPicker",
+ value: function buildPicker(Picker, i18n) {
return new Picker({
onEmojiSelect: this.onEmojiSelect,
theme: 'light',
@@ -75,7 +132,7 @@ var _default = /*#__PURE__*/function (_Controller) {
skinTonePosition: 'none',
emojiSize: this.sizeValue,
perLine: this.perLineValue,
- i18n: Hellotext.business.locale === 'es' ? es : en
+ i18n
});
}
}, {
diff --git a/lib/controllers/webchat/useBehaviour.cjs b/lib/controllers/webchat/useBehaviour.cjs
new file mode 100644
index 0000000..eb6daa7
--- /dev/null
+++ b/lib/controllers/webchat/useBehaviour.cjs
@@ -0,0 +1,74 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.useBehaviour = void 0;
+// This mixin owns automatic opening only. The webchat controller still owns the
+// Stimulus lifecycle and `usePopover` still owns the mechanics of opening and
+// closing the popover. Keeping this policy here gives the configured behaviour
+// value one place to translate into timers, storage gates, and cleanup.
+const useBehaviour = controller => {
+ Object.assign(controller, {
+ // Called from `connect` after the controller has wired channels, targets,
+ // and popover state. Even a zero-second delay goes through a timeout so the
+ // automatic open happens after the mount pass rather than during setup.
+ scheduleBehaviourOpen() {
+ if (!this.shouldAutoOpenFromBehaviour()) return;
+ const delay = Number(this.behaviourValue.delay_seconds || 0) * 1000;
+ this.behaviourOpenTimeout = window.setTimeout(() => {
+ this.behaviourOpenTimeout = null;
+ if (this.openValue) return;
+ this.openValue = true;
+ this.markBehaviourAutoOpened();
+ }, delay);
+ },
+ // This is the shared escape hatch for lifecycle teardown and manual user
+ // intent. `disconnect` calls it to avoid orphaned timers; `usePopover`
+ // calls it before show/toggle so a delayed behaviour cannot reopen the chat
+ // or consume gates after the user has already acted.
+ cancelBehaviourOpen() {
+ window.clearTimeout(this.behaviourOpenTimeout);
+ this.behaviourOpenTimeout = null;
+ },
+ // Eligibility is deliberately read-only. A visit should only be marked as
+ // auto-opened after the timer opens the widget, not when we merely discover
+ // that it would be allowed. That keeps blocked, cancelled, and manually
+ // opened visits from being counted as behaviour-driven opens.
+ shouldAutoOpenFromBehaviour() {
+ const behaviour = this.behaviourValue;
+ if (!behaviour || behaviour.trigger !== 'on_load') return false;
+ if (behaviour.first_visit_only && localStorage.getItem(this.firstVisitKey())) {
+ return false;
+ }
+ if (behaviour.once_per_session && sessionStorage.getItem(this.sessionKey())) {
+ return false;
+ }
+ return true;
+ },
+ // The gates are independent because they answer different product
+ // questions. `first_visit_only` is a long-lived visitor gate in
+ // localStorage; `once_per_session` is intentionally softer and resets with
+ // sessionStorage.
+ markBehaviourAutoOpened() {
+ if (this.behaviourValue.first_visit_only) {
+ localStorage.setItem(this.firstVisitKey(), '1');
+ }
+ if (this.behaviourValue.once_per_session) {
+ sessionStorage.setItem(this.sessionKey(), '1');
+ }
+ },
+ // Records that this widget has already used its long-lived first-visit
+ // automatic open. This is the durable gate behind `first_visit_only`.
+ firstVisitKey() {
+ return `hellotext--webchat--${this.idValue}--auto-opened`;
+ },
+ // Records that this widget has already used its automatic open during the
+ // current browser session. This is the shorter-lived gate behind
+ // `once_per_session`.
+ sessionKey() {
+ return `hellotext--webchat--${this.idValue}--auto-opened-session`;
+ }
+ });
+};
+exports.useBehaviour = useBehaviour;
\ No newline at end of file
diff --git a/lib/controllers/webchat/useBehaviour.js b/lib/controllers/webchat/useBehaviour.js
new file mode 100644
index 0000000..da41ffd
--- /dev/null
+++ b/lib/controllers/webchat/useBehaviour.js
@@ -0,0 +1,67 @@
+// This mixin owns automatic opening only. The webchat controller still owns the
+// Stimulus lifecycle and `usePopover` still owns the mechanics of opening and
+// closing the popover. Keeping this policy here gives the configured behaviour
+// value one place to translate into timers, storage gates, and cleanup.
+export var useBehaviour = controller => {
+ Object.assign(controller, {
+ // Called from `connect` after the controller has wired channels, targets,
+ // and popover state. Even a zero-second delay goes through a timeout so the
+ // automatic open happens after the mount pass rather than during setup.
+ scheduleBehaviourOpen() {
+ if (!this.shouldAutoOpenFromBehaviour()) return;
+ var delay = Number(this.behaviourValue.delay_seconds || 0) * 1000;
+ this.behaviourOpenTimeout = window.setTimeout(() => {
+ this.behaviourOpenTimeout = null;
+ if (this.openValue) return;
+ this.openValue = true;
+ this.markBehaviourAutoOpened();
+ }, delay);
+ },
+ // This is the shared escape hatch for lifecycle teardown and manual user
+ // intent. `disconnect` calls it to avoid orphaned timers; `usePopover`
+ // calls it before show/toggle so a delayed behaviour cannot reopen the chat
+ // or consume gates after the user has already acted.
+ cancelBehaviourOpen() {
+ window.clearTimeout(this.behaviourOpenTimeout);
+ this.behaviourOpenTimeout = null;
+ },
+ // Eligibility is deliberately read-only. A visit should only be marked as
+ // auto-opened after the timer opens the widget, not when we merely discover
+ // that it would be allowed. That keeps blocked, cancelled, and manually
+ // opened visits from being counted as behaviour-driven opens.
+ shouldAutoOpenFromBehaviour() {
+ var behaviour = this.behaviourValue;
+ if (!behaviour || behaviour.trigger !== 'on_load') return false;
+ if (behaviour.first_visit_only && localStorage.getItem(this.firstVisitKey())) {
+ return false;
+ }
+ if (behaviour.once_per_session && sessionStorage.getItem(this.sessionKey())) {
+ return false;
+ }
+ return true;
+ },
+ // The gates are independent because they answer different product
+ // questions. `first_visit_only` is a long-lived visitor gate in
+ // localStorage; `once_per_session` is intentionally softer and resets with
+ // sessionStorage.
+ markBehaviourAutoOpened() {
+ if (this.behaviourValue.first_visit_only) {
+ localStorage.setItem(this.firstVisitKey(), '1');
+ }
+ if (this.behaviourValue.once_per_session) {
+ sessionStorage.setItem(this.sessionKey(), '1');
+ }
+ },
+ // Records that this widget has already used its long-lived first-visit
+ // automatic open. This is the durable gate behind `first_visit_only`.
+ firstVisitKey() {
+ return "hellotext--webchat--".concat(this.idValue, "--auto-opened");
+ },
+ // Records that this widget has already used its automatic open during the
+ // current browser session. This is the shorter-lived gate behind
+ // `once_per_session`.
+ sessionKey() {
+ return "hellotext--webchat--".concat(this.idValue, "--auto-opened-session");
+ }
+ });
+};
\ No newline at end of file
diff --git a/lib/controllers/webchat/useOpeningSequence.cjs b/lib/controllers/webchat/useOpeningSequence.cjs
new file mode 100644
index 0000000..3ce4ea4
--- /dev/null
+++ b/lib/controllers/webchat/useOpeningSequence.cjs
@@ -0,0 +1,103 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.useOpeningSequence = void 0;
+// Opening sequence markup is already present in the document; this mixin only
+// manages when those staged messages become visible and which ids should travel
+// with the first customer message. It never creates a conversation or posts the
+// staged messages by itself.
+const useOpeningSequence = controller => {
+ Object.assign(controller, {
+ // Called from `connect` to prepare lifecycle state before the webchat opens.
+ // The sequence may not exist on every widget, so the rest of the runtime can
+ // safely call these helpers even when there are no sequence targets.
+ setupOpeningSequence() {
+ this.openingSequenceStarted = false;
+ this.openingSequenceCancelled = false;
+ this.openingSequenceTimeout = null;
+ this.openingSequenceMessages = [];
+ this.revealedOpeningSequenceMessageIds = [];
+ },
+ // Disconnect shares the same cancellation path as a user send. Both cases
+ // must stop pending timers so hidden staged messages cannot reveal later.
+ teardownOpeningSequence() {
+ this.cancelOpeningSequence();
+ },
+ // The sequence is a first-conversation affordance only. A present
+ // conversation id means the transcript already exists, so staged messages
+ // should remain untouched.
+ startOpeningSequence() {
+ this.openingSequenceMessages = Array.from(this.openingSequenceMessageTargets || []);
+ if (!this.openingSequenceCanStart()) return;
+ this.openingSequenceStarted = true;
+ this.openingSequenceCancelled = false;
+ this.revealedOpeningSequenceMessageIds = [];
+ this.playOpeningSequenceMessageAt(0);
+ },
+ openingSequenceCanStart() {
+ return !this.conversationIdValue && this.hasOpeningSequenceTarget && this.openingSequenceMessages.length > 0 && !this.openingSequenceStarted;
+ },
+ // Each staged message owns the delay before it appears. A zero-second delay
+ // still uses a timeout so reveal work stays outside the popover-open call
+ // stack and cancellation has one consistent path.
+ playOpeningSequenceMessageAt(index) {
+ const message = this.openingSequenceMessages[index];
+ if (!message) return;
+ const delay = this.openingSequenceMessageDelay(message) * 1000;
+ this.openingSequenceTimeout = window.setTimeout(() => {
+ this.openingSequenceTimeout = null;
+ if (this.openingSequenceCancelled) return;
+ this.revealOpeningSequenceMessage(message);
+ this.playOpeningSequenceMessageAt(index + 1);
+ }, delay);
+ },
+ // Revealing moves the staged node into the live message list. The message
+ // markup itself is preserved; JS only relocates it, unhides it, and remembers
+ // the hashed id if the visitor actually saw it.
+ revealOpeningSequenceMessage(message) {
+ this.messagesContainerTarget.insertBefore(message, this.messageTemplateTarget);
+ message.hidden = false;
+ this.recordOpeningSequenceMessage(message);
+ this.scrollOpeningSequenceToBottom();
+ },
+ recordOpeningSequenceMessage(message) {
+ const id = message.dataset.openingSequenceMessageId;
+ if (!id || this.revealedOpeningSequenceMessageIds.includes(id)) return;
+ this.revealedOpeningSequenceMessageIds.push(id);
+ },
+ openingSequenceMessageDelay(message) {
+ const delay = Number(message.dataset.delaySeconds || 0);
+ return Number.isFinite(delay) ? delay : 0;
+ },
+ scrollOpeningSequenceToBottom() {
+ if (!this.messagesContainerTarget.scroll) return;
+ this.messagesContainerTarget.scroll({
+ top: this.messagesContainerTarget.scrollHeight,
+ behavior: 'smooth'
+ });
+ },
+ cancelOpeningSequence() {
+ this.openingSequenceCancelled = true;
+ if (this.openingSequenceTimeout === null || this.openingSequenceTimeout === undefined) return;
+ window.clearTimeout(this.openingSequenceTimeout);
+ this.openingSequenceTimeout = null;
+ },
+ // Customer sends promote only what was actually revealed. Calling this also
+ // interrupts pending reveals so the payload cannot include later messages.
+ appendOpeningSequenceMessageIds(formData) {
+ this.cancelOpeningSequence();
+ const ids = this.revealedOpeningSequenceMessageIds || [];
+ ids.forEach(id => {
+ formData.append('message[opening_sequence_message_ids][]', id);
+ });
+ },
+ // Clear after a successful customer send. Failed sends keep the ids available
+ // so retrying the first message can still promote the revealed sequence.
+ clearRevealedOpeningSequenceMessageIds() {
+ this.revealedOpeningSequenceMessageIds = [];
+ }
+ });
+};
+exports.useOpeningSequence = useOpeningSequence;
\ No newline at end of file
diff --git a/lib/controllers/webchat/useOpeningSequence.js b/lib/controllers/webchat/useOpeningSequence.js
new file mode 100644
index 0000000..c290fc6
--- /dev/null
+++ b/lib/controllers/webchat/useOpeningSequence.js
@@ -0,0 +1,96 @@
+// Opening sequence markup is already present in the document; this mixin only
+// manages when those staged messages become visible and which ids should travel
+// with the first customer message. It never creates a conversation or posts the
+// staged messages by itself.
+export var useOpeningSequence = controller => {
+ Object.assign(controller, {
+ // Called from `connect` to prepare lifecycle state before the webchat opens.
+ // The sequence may not exist on every widget, so the rest of the runtime can
+ // safely call these helpers even when there are no sequence targets.
+ setupOpeningSequence() {
+ this.openingSequenceStarted = false;
+ this.openingSequenceCancelled = false;
+ this.openingSequenceTimeout = null;
+ this.openingSequenceMessages = [];
+ this.revealedOpeningSequenceMessageIds = [];
+ },
+ // Disconnect shares the same cancellation path as a user send. Both cases
+ // must stop pending timers so hidden staged messages cannot reveal later.
+ teardownOpeningSequence() {
+ this.cancelOpeningSequence();
+ },
+ // The sequence is a first-conversation affordance only. A present
+ // conversation id means the transcript already exists, so staged messages
+ // should remain untouched.
+ startOpeningSequence() {
+ this.openingSequenceMessages = Array.from(this.openingSequenceMessageTargets || []);
+ if (!this.openingSequenceCanStart()) return;
+ this.openingSequenceStarted = true;
+ this.openingSequenceCancelled = false;
+ this.revealedOpeningSequenceMessageIds = [];
+ this.playOpeningSequenceMessageAt(0);
+ },
+ openingSequenceCanStart() {
+ return !this.conversationIdValue && this.hasOpeningSequenceTarget && this.openingSequenceMessages.length > 0 && !this.openingSequenceStarted;
+ },
+ // Each staged message owns the delay before it appears. A zero-second delay
+ // still uses a timeout so reveal work stays outside the popover-open call
+ // stack and cancellation has one consistent path.
+ playOpeningSequenceMessageAt(index) {
+ var message = this.openingSequenceMessages[index];
+ if (!message) return;
+ var delay = this.openingSequenceMessageDelay(message) * 1000;
+ this.openingSequenceTimeout = window.setTimeout(() => {
+ this.openingSequenceTimeout = null;
+ if (this.openingSequenceCancelled) return;
+ this.revealOpeningSequenceMessage(message);
+ this.playOpeningSequenceMessageAt(index + 1);
+ }, delay);
+ },
+ // Revealing moves the staged node into the live message list. The message
+ // markup itself is preserved; JS only relocates it, unhides it, and remembers
+ // the hashed id if the visitor actually saw it.
+ revealOpeningSequenceMessage(message) {
+ this.messagesContainerTarget.insertBefore(message, this.messageTemplateTarget);
+ message.hidden = false;
+ this.recordOpeningSequenceMessage(message);
+ this.scrollOpeningSequenceToBottom();
+ },
+ recordOpeningSequenceMessage(message) {
+ var id = message.dataset.openingSequenceMessageId;
+ if (!id || this.revealedOpeningSequenceMessageIds.includes(id)) return;
+ this.revealedOpeningSequenceMessageIds.push(id);
+ },
+ openingSequenceMessageDelay(message) {
+ var delay = Number(message.dataset.delaySeconds || 0);
+ return Number.isFinite(delay) ? delay : 0;
+ },
+ scrollOpeningSequenceToBottom() {
+ if (!this.messagesContainerTarget.scroll) return;
+ this.messagesContainerTarget.scroll({
+ top: this.messagesContainerTarget.scrollHeight,
+ behavior: 'smooth'
+ });
+ },
+ cancelOpeningSequence() {
+ this.openingSequenceCancelled = true;
+ if (this.openingSequenceTimeout === null || this.openingSequenceTimeout === undefined) return;
+ window.clearTimeout(this.openingSequenceTimeout);
+ this.openingSequenceTimeout = null;
+ },
+ // Customer sends promote only what was actually revealed. Calling this also
+ // interrupts pending reveals so the payload cannot include later messages.
+ appendOpeningSequenceMessageIds(formData) {
+ this.cancelOpeningSequence();
+ var ids = this.revealedOpeningSequenceMessageIds || [];
+ ids.forEach(id => {
+ formData.append('message[opening_sequence_message_ids][]', id);
+ });
+ },
+ // Clear after a successful customer send. Failed sends keep the ids available
+ // so retrying the first message can still promote the revealed sequence.
+ clearRevealedOpeningSequenceMessageIds() {
+ this.revealedOpeningSequenceMessageIds = [];
+ }
+ });
+};
\ No newline at end of file
diff --git a/lib/controllers/webchat/useTeaser.cjs b/lib/controllers/webchat/useTeaser.cjs
new file mode 100644
index 0000000..8cf4cba
--- /dev/null
+++ b/lib/controllers/webchat/useTeaser.cjs
@@ -0,0 +1,163 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.useTeaser = void 0;
+// The teaser markup is already present in the document; this mixin only manages
+// the runtime around it. Keeping the teaser policy here gives the controller one
+// place to delegate click-to-open, the pre-conversation presentation, and timer
+// cleanup. Message sending stays on the controller because it owns the API flow
+// and optimistic customer bubble insertion.
+const useTeaser = controller => {
+ Object.assign(controller, {
+ // Called from `connect` after `usePopover` has given the controller `show`.
+ // The teaser is optional, so setup first prepares reusable lifecycle state,
+ // wires the click surface when present, and then lets eligibility decide
+ // whether this session may still show the presentation.
+ setupTeaser() {
+ this.teaserCycleTimeout = null;
+ this.teaserMessages = [];
+ this.boundOnTeaserClick = this.boundOnTeaserClick || this.onTeaserClick.bind(this);
+ if (!this.hasTeaserTarget) return;
+ this.teaserTarget.addEventListener('click', this.boundOnTeaserClick);
+ this.startTeaserPresentation();
+ },
+ // This is the shared teardown path for Stimulus disconnect. Cycling timers
+ // and DOM listeners both outlive the current call stack, so they need to be
+ // cancelled explicitly when the rendered widget leaves the page.
+ teardownTeaser() {
+ this.stopTeaserCycle();
+ if (this.hasTeaserTarget && this.boundOnTeaserClick) {
+ this.teaserTarget.removeEventListener('click', this.boundOnTeaserClick);
+ }
+ },
+ // The current DOM is the source of truth. Each setup pass collects the
+ // rendered teaser messages from the teaser surface itself, which keeps the
+ // target list small and avoids carrying stale nodes between presentations.
+ collectTeaserMessages() {
+ if (!this.hasTeaserTarget) return [];
+ return Array.from(this.teaserTarget.querySelectorAll('[data-teaser-message]'));
+ },
+ // The teaser is a one-time, pre-conversation presentation. It can run while
+ // the popover is closed, but any active conversation signal makes the teaser
+ // ineligible for the rest of this browser session.
+ startTeaserPresentation() {
+ this.stopTeaserCycle();
+ this.teaserMessages = this.collectTeaserMessages();
+ if (!this.hasTeaserTarget) return;
+ if (this.teaserMessages.length === 0) {
+ this.hideTeaser();
+ return;
+ }
+ if (this.openValue) {
+ this.dismissTeaserForSession();
+ return;
+ }
+ if (this.conversationIdValue || this.hasRenderedConversationMessages()) {
+ this.dismissTeaserForSession();
+ return;
+ }
+ if (this.teaserSeenForSession()) {
+ this.hideTeaser();
+ return;
+ }
+ this.teaserTarget.classList.remove('invisible');
+ this.showTeaserMessage(0);
+ if (this.teaserMessages.length < 2) return;
+ this.scheduleNextTeaserMessage(0);
+ },
+ // Delays belong to the currently visible message, so each teaser controls
+ // how long it remains on screen before the next one replaces it. The
+ // presentation stops after the last message instead of looping forever.
+ scheduleNextTeaserMessage(currentIndex) {
+ const nextIndex = currentIndex + 1;
+ if (nextIndex >= this.teaserMessages.length) return;
+ const currentMessage = this.teaserMessages[currentIndex];
+ const delay = this.teaserPresentationDelay(currentMessage);
+ this.teaserCycleTimeout = window.setTimeout(() => {
+ this.teaserCycleTimeout = null;
+ this.showTeaserMessage(nextIndex);
+ this.scheduleNextTeaserMessage(nextIndex);
+ }, delay);
+ },
+ // Visibility is managed with the existing `hidden` class for initially
+ // concealed teaser messages. This keeps JS from restructuring teaser markup.
+ showTeaserMessage(index) {
+ this.teaserMessages.forEach((message, messageIndex) => {
+ message.classList.toggle('hidden', messageIndex !== index);
+ });
+ },
+ // Safe to call even when no timer exists. Lifecycle hooks call this before
+ // starting, hiding, or tearing down the teaser so only one cycle can be alive.
+ stopTeaserCycle() {
+ if (this.teaserCycleTimeout === null || this.teaserCycleTimeout === undefined) return;
+ window.clearTimeout(this.teaserCycleTimeout);
+ this.teaserCycleTimeout = null;
+ },
+ // Invalid or missing delay values should not break teaser rendering. Treat
+ // them as zero, then use a small minimum so zero-delay presentations advance
+ // deliberately instead of flashing through every message in one frame.
+ teaserMessageDelay(message) {
+ const delay = Number(message.dataset.delaySeconds || 0);
+ return Number.isFinite(delay) ? delay : 0;
+ },
+ teaserPresentationDelay(message) {
+ return Math.max(this.teaserMessageDelay(message) * 1000, 250);
+ },
+ // A rendered transcript means the visitor is no longer in the pre-conversation
+ // state. The hidden template is deliberately excluded because it is not a live
+ // customer-visible message.
+ hasRenderedConversationMessages() {
+ let messages = [];
+ try {
+ messages = Array.from(this.messageTargets || []);
+ } catch (_error) {
+ messages = [];
+ }
+ return messages.some(message => message !== this.messageTemplateTarget);
+ },
+ teaserSeenKey() {
+ return `hellotext:webchat:${this.idValue || this.element.id}:teaser-seen`;
+ },
+ teaserSeenForSession() {
+ try {
+ return window.sessionStorage.getItem(this.teaserSeenKey()) === 'true';
+ } catch (_error) {
+ return false;
+ }
+ },
+ markTeaserSeenForSession() {
+ try {
+ window.sessionStorage.setItem(this.teaserSeenKey(), 'true');
+ } catch (_error) {
+ // Storage can be unavailable in locked-down browsers. The in-memory hide
+ // still keeps the active controller from showing the teaser again.
+ }
+ },
+ // Opening or sending ends the pre-conversation window for this browser
+ // session. Incoming message teasers are separate ephemeral content, so they
+ // use `hideTeaser`/`updateMessageTeaser` without writing this session flag.
+ dismissTeaserForSession() {
+ this.markTeaserSeenForSession();
+ this.hideTeaser();
+ },
+ // Stopping the timer before hiding prevents queued presentation steps from
+ // flipping hidden messages after the teaser surface has been dismissed.
+ hideTeaser() {
+ this.stopTeaserCycle();
+ if (this.hasTeaserTarget) {
+ this.teaserTarget.classList.add('invisible');
+ }
+ },
+ // A teaser surface click is a user request to open chat, but links inside the
+ // teaser already have native browser behavior. Leaving anchors alone keeps
+ // external URLs and `tel:` actions working without extra JS.
+ onTeaserClick(event) {
+ if (event.target.closest('a')) return;
+ this.dismissTeaserForSession();
+ this.show();
+ }
+ });
+};
+exports.useTeaser = useTeaser;
\ No newline at end of file
diff --git a/lib/controllers/webchat/useTeaser.js b/lib/controllers/webchat/useTeaser.js
new file mode 100644
index 0000000..a1a62fa
--- /dev/null
+++ b/lib/controllers/webchat/useTeaser.js
@@ -0,0 +1,156 @@
+// The teaser markup is already present in the document; this mixin only manages
+// the runtime around it. Keeping the teaser policy here gives the controller one
+// place to delegate click-to-open, the pre-conversation presentation, and timer
+// cleanup. Message sending stays on the controller because it owns the API flow
+// and optimistic customer bubble insertion.
+export var useTeaser = controller => {
+ Object.assign(controller, {
+ // Called from `connect` after `usePopover` has given the controller `show`.
+ // The teaser is optional, so setup first prepares reusable lifecycle state,
+ // wires the click surface when present, and then lets eligibility decide
+ // whether this session may still show the presentation.
+ setupTeaser() {
+ this.teaserCycleTimeout = null;
+ this.teaserMessages = [];
+ this.boundOnTeaserClick = this.boundOnTeaserClick || this.onTeaserClick.bind(this);
+ if (!this.hasTeaserTarget) return;
+ this.teaserTarget.addEventListener('click', this.boundOnTeaserClick);
+ this.startTeaserPresentation();
+ },
+ // This is the shared teardown path for Stimulus disconnect. Cycling timers
+ // and DOM listeners both outlive the current call stack, so they need to be
+ // cancelled explicitly when the rendered widget leaves the page.
+ teardownTeaser() {
+ this.stopTeaserCycle();
+ if (this.hasTeaserTarget && this.boundOnTeaserClick) {
+ this.teaserTarget.removeEventListener('click', this.boundOnTeaserClick);
+ }
+ },
+ // The current DOM is the source of truth. Each setup pass collects the
+ // rendered teaser messages from the teaser surface itself, which keeps the
+ // target list small and avoids carrying stale nodes between presentations.
+ collectTeaserMessages() {
+ if (!this.hasTeaserTarget) return [];
+ return Array.from(this.teaserTarget.querySelectorAll('[data-teaser-message]'));
+ },
+ // The teaser is a one-time, pre-conversation presentation. It can run while
+ // the popover is closed, but any active conversation signal makes the teaser
+ // ineligible for the rest of this browser session.
+ startTeaserPresentation() {
+ this.stopTeaserCycle();
+ this.teaserMessages = this.collectTeaserMessages();
+ if (!this.hasTeaserTarget) return;
+ if (this.teaserMessages.length === 0) {
+ this.hideTeaser();
+ return;
+ }
+ if (this.openValue) {
+ this.dismissTeaserForSession();
+ return;
+ }
+ if (this.conversationIdValue || this.hasRenderedConversationMessages()) {
+ this.dismissTeaserForSession();
+ return;
+ }
+ if (this.teaserSeenForSession()) {
+ this.hideTeaser();
+ return;
+ }
+ this.teaserTarget.classList.remove('invisible');
+ this.showTeaserMessage(0);
+ if (this.teaserMessages.length < 2) return;
+ this.scheduleNextTeaserMessage(0);
+ },
+ // Delays belong to the currently visible message, so each teaser controls
+ // how long it remains on screen before the next one replaces it. The
+ // presentation stops after the last message instead of looping forever.
+ scheduleNextTeaserMessage(currentIndex) {
+ var nextIndex = currentIndex + 1;
+ if (nextIndex >= this.teaserMessages.length) return;
+ var currentMessage = this.teaserMessages[currentIndex];
+ var delay = this.teaserPresentationDelay(currentMessage);
+ this.teaserCycleTimeout = window.setTimeout(() => {
+ this.teaserCycleTimeout = null;
+ this.showTeaserMessage(nextIndex);
+ this.scheduleNextTeaserMessage(nextIndex);
+ }, delay);
+ },
+ // Visibility is managed with the existing `hidden` class for initially
+ // concealed teaser messages. This keeps JS from restructuring teaser markup.
+ showTeaserMessage(index) {
+ this.teaserMessages.forEach((message, messageIndex) => {
+ message.classList.toggle('hidden', messageIndex !== index);
+ });
+ },
+ // Safe to call even when no timer exists. Lifecycle hooks call this before
+ // starting, hiding, or tearing down the teaser so only one cycle can be alive.
+ stopTeaserCycle() {
+ if (this.teaserCycleTimeout === null || this.teaserCycleTimeout === undefined) return;
+ window.clearTimeout(this.teaserCycleTimeout);
+ this.teaserCycleTimeout = null;
+ },
+ // Invalid or missing delay values should not break teaser rendering. Treat
+ // them as zero, then use a small minimum so zero-delay presentations advance
+ // deliberately instead of flashing through every message in one frame.
+ teaserMessageDelay(message) {
+ var delay = Number(message.dataset.delaySeconds || 0);
+ return Number.isFinite(delay) ? delay : 0;
+ },
+ teaserPresentationDelay(message) {
+ return Math.max(this.teaserMessageDelay(message) * 1000, 250);
+ },
+ // A rendered transcript means the visitor is no longer in the pre-conversation
+ // state. The hidden template is deliberately excluded because it is not a live
+ // customer-visible message.
+ hasRenderedConversationMessages() {
+ var messages = [];
+ try {
+ messages = Array.from(this.messageTargets || []);
+ } catch (_error) {
+ messages = [];
+ }
+ return messages.some(message => message !== this.messageTemplateTarget);
+ },
+ teaserSeenKey() {
+ return "hellotext:webchat:".concat(this.idValue || this.element.id, ":teaser-seen");
+ },
+ teaserSeenForSession() {
+ try {
+ return window.sessionStorage.getItem(this.teaserSeenKey()) === 'true';
+ } catch (_error) {
+ return false;
+ }
+ },
+ markTeaserSeenForSession() {
+ try {
+ window.sessionStorage.setItem(this.teaserSeenKey(), 'true');
+ } catch (_error) {
+ // Storage can be unavailable in locked-down browsers. The in-memory hide
+ // still keeps the active controller from showing the teaser again.
+ }
+ },
+ // Opening or sending ends the pre-conversation window for this browser
+ // session. Incoming message teasers are separate ephemeral content, so they
+ // use `hideTeaser`/`updateMessageTeaser` without writing this session flag.
+ dismissTeaserForSession() {
+ this.markTeaserSeenForSession();
+ this.hideTeaser();
+ },
+ // Stopping the timer before hiding prevents queued presentation steps from
+ // flipping hidden messages after the teaser surface has been dismissed.
+ hideTeaser() {
+ this.stopTeaserCycle();
+ if (this.hasTeaserTarget) {
+ this.teaserTarget.classList.add('invisible');
+ }
+ },
+ // A teaser surface click is a user request to open chat, but links inside the
+ // teaser already have native browser behavior. Leaving anchors alone keeps
+ // external URLs and `tel:` actions working without extra JS.
+ onTeaserClick(event) {
+ if (event.target.closest('a')) return;
+ this.dismissTeaserForSession();
+ this.show();
+ }
+ });
+};
\ No newline at end of file
diff --git a/lib/controllers/webchat_controller.cjs b/lib/controllers/webchat_controller.cjs
index 4d79388..47c95d7 100644
--- a/lib/controllers/webchat_controller.cjs
+++ b/lib/controllers/webchat_controller.cjs
@@ -11,8 +11,10 @@ var _webchat_channel = _interopRequireDefault(require("../channels/webchat_chann
var _hellotext = _interopRequireDefault(require("../hellotext"));
var _core = require("../core");
var _webchat = require("../core/configuration/webchat");
-var _logo_builder = require("../builders/logo_builder");
var _usePopover = require("./mixins/usePopover");
+var _useBehaviour = require("./webchat/useBehaviour");
+var _useOpeningSequence = require("./webchat/useOpeningSequence");
+var _useTeaser = require("./webchat/useTeaser");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
@@ -41,6 +43,7 @@ let _default = /*#__PURE__*/function (_Controller) {
this.messagesAPI = new _messages.default(this.idValue);
this.webChatChannel = new _webchat_channel.default(this.idValue, _hellotext.default.session, this.conversationIdValue);
this.files = [];
+ this.messageIds = new Set();
this.onMessageReceived = this.onMessageReceived.bind(this);
this.onMessageReaction = this.onMessageReaction.bind(this);
this.onTypingStart = this.onTypingStart.bind(this);
@@ -52,9 +55,10 @@ let _default = /*#__PURE__*/function (_Controller) {
}, {
key: "connect",
value: function connect() {
+ (0, _useBehaviour.useBehaviour)(this);
(0, _usePopover.usePopover)(this);
- this.popoverTarget.classList.add(..._webchat.Webchat.classes);
- this.triggerTarget.classList.add(..._webchat.Webchat.triggerClasses);
+ (0, _useOpeningSequence.useOpeningSequence)(this);
+ (0, _useTeaser.useTeaser)(this);
this.setupFloatingUI({
trigger: this.triggerTarget,
popover: this.popoverTarget
@@ -66,23 +70,26 @@ let _default = /*#__PURE__*/function (_Controller) {
strategy: 'absolute'
});
}
+ this.setupTeaser();
+ this.setupOpeningSequence();
this.webChatChannel.onMessage(this.onMessageReceived);
this.webChatChannel.onTypingStart(this.onTypingStart);
this.webChatChannel.onReaction(this.onMessageReaction);
this.messagesContainerTarget.addEventListener('scroll', this.onScroll);
- if (!_hellotext.default.business.features.white_label) {
- this.toolbarTarget.appendChild(_logo_builder.LogoBuilder.build());
- }
if (this.shouldOpenOnMount) {
this.openValue = true;
}
_hellotext.default.eventEmitter.dispatch('webchat:mounted');
this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent);
+ this.scheduleBehaviourOpen();
_get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
}
}, {
key: "disconnect",
value: function disconnect() {
+ this.cancelBehaviourOpen();
+ this.teardownTeaser();
+ this.teardownOpeningSequence();
this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
@@ -228,11 +235,12 @@ let _default = /*#__PURE__*/function (_Controller) {
}
if (attachments) {
attachments.forEach(attachmentUrl => {
+ var _this$messageAttachme;
const image = this.attachmentImageTarget.cloneNode(true);
image.removeAttribute('data-hellotext--webchat-target');
image.src = attachmentUrl;
image.style.display = 'block';
- element.querySelector('[data-attachment-container]').appendChild(image);
+ (_this$messageAttachme = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme === void 0 ? void 0 : _this$messageAttachme.appendChild(image);
});
}
element.setAttribute('data-body', body);
@@ -247,7 +255,7 @@ let _default = /*#__PURE__*/function (_Controller) {
}, {
key: "onClickOutside",
value: function onClickOutside(event) {
- if (_webchat.Webchat.behaviour === _webchat.behaviors.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
+ if (_webchat.Webchat.mode === _webchat.modes.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
this.openValue = false;
}
}
@@ -262,7 +270,9 @@ let _default = /*#__PURE__*/function (_Controller) {
}, {
key: "onPopoverOpened",
value: function onPopoverOpened() {
+ var _this$dismissTeaserFo;
this.popoverTarget.classList.remove(...this.fadeOutClasses);
+ (_this$dismissTeaserFo = this.dismissTeaserForSession) === null || _this$dismissTeaserFo === void 0 ? void 0 : _this$dismissTeaserFo.call(this);
if (!this.onMobile) {
this.inputTarget.focus();
}
@@ -277,9 +287,10 @@ let _default = /*#__PURE__*/function (_Controller) {
}
_hellotext.default.eventEmitter.dispatch('webchat:opened');
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'opened');
- if (this.hasTeaserTarget) {
- this.teaserTarget.classList.add('hidden');
+ if (this.messageTeaserValue) {
+ this.messageTeaserValue = null;
}
+ this.startOpeningSequence();
if (this.unreadCounterTarget.style.display === 'none') return;
this.unreadCounterTarget.style.display = 'none';
this.unreadCounterTarget.innerText = '0';
@@ -290,9 +301,6 @@ let _default = /*#__PURE__*/function (_Controller) {
value: function onPopoverClosed() {
_hellotext.default.eventEmitter.dispatch('webchat:closed');
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'closed');
- if (this.hasTeaserTarget && this.teaserValue.body) {
- this.teaserTarget.classList.remove('hidden');
- }
}
}, {
key: "onMessageReaction",
@@ -321,11 +329,15 @@ let _default = /*#__PURE__*/function (_Controller) {
}, {
key: "onMessageReceived",
value: function onMessageReceived(message) {
+ var _this$hideTeaser;
const {
id,
body,
- attachments
+ attachments,
+ teaser
} = message;
+ if (!this.claimMessageId(id)) return;
+ (_this$hideTeaser = this.hideTeaser) === null || _this$hideTeaser === void 0 ? void 0 : _this$hideTeaser.call(this);
if (message.carousel) {
return this.insertCarouselMessage(message);
}
@@ -334,13 +346,15 @@ let _default = /*#__PURE__*/function (_Controller) {
const element = this.messageTemplateTarget.cloneNode(true);
element.style.display = 'flex';
element.querySelector('[data-body]').innerHTML = div.innerHTML;
+ element.setAttribute('data-id', id);
element.setAttribute('data-hellotext--webchat-target', 'message');
if (attachments) {
attachments.forEach(attachmentUrl => {
+ var _this$messageAttachme2;
const image = this.attachmentImageTarget.cloneNode(true);
image.src = attachmentUrl;
image.style.display = 'block';
- element.querySelector('[data-attachment-container]').appendChild(image);
+ (_this$messageAttachme2 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme2 === void 0 ? void 0 : _this$messageAttachme2.appendChild(image);
});
}
this.clearTypingIndicator();
@@ -352,13 +366,33 @@ let _default = /*#__PURE__*/function (_Controller) {
element.scrollIntoView({
behavior: 'smooth'
});
+ this.updateMessageTeaser(teaser);
if (this.openValue) {
this.messagesAPI.markAsSeen(id);
return;
}
- this.unreadCounterTarget.style.display = 'flex';
- const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
+ this.incrementUnreadCounter();
+ }
+
+ // TCP broadcasts may deliver the same incoming message twice before Stimulus
+ // has connected the appended message target. Claiming the id in memory closes
+ // that window, while the target check still drops messages rendered earlier.
+ }, {
+ key: "claimMessageId",
+ value: function claimMessageId(id) {
+ const messageTargets = this.messageTargets || [];
+ if (this.messageIds.has(id)) return false;
+ this.messageIds.add(id);
+ return !messageTargets.some(element => element.dataset.id === id);
+ }
+ }, {
+ key: "updateMessageTeaser",
+ value: function updateMessageTeaser(teaser) {
+ this.messageTeaserValue = teaser;
+ if (this.messageTeaserValue && this.hasTeaserTarget) {
+ this.teaserTarget.innerHTML = this.messageTeaserValue;
+ this.teaserTarget.classList.toggle('invisible', this.openValue);
+ }
}
}, {
key: "insertCarouselMessage",
@@ -366,6 +400,8 @@ let _default = /*#__PURE__*/function (_Controller) {
var _element$querySelecto;
const html = message.html;
const element = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild;
+ element.setAttribute('data-id', message.id);
+ element.setAttribute('data-hellotext--webchat-target', 'message');
this.clearTypingIndicator();
this.messagesContainerTarget.appendChild(element);
element.scrollIntoView({
@@ -375,13 +411,12 @@ let _default = /*#__PURE__*/function (_Controller) {
...message,
body: ((_element$querySelecto = element.querySelector('[data-body]')) === null || _element$querySelecto === void 0 ? void 0 : _element$querySelecto.innerText) || ''
});
+ this.updateMessageTeaser(message.teaser);
if (this.openValue) {
this.messagesAPI.markAsSeen(message.id);
return;
}
- this.unreadCounterTarget.style.display = 'flex';
- const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
+ this.incrementUnreadCounter();
}
}, {
key: "resizeInput",
@@ -407,7 +442,8 @@ let _default = /*#__PURE__*/function (_Controller) {
cardElement
}
}) {
- var _cardElement$querySel;
+ var _this$dismissTeaserFo2, _cardElement$querySel;
+ (_this$dismissTeaserFo2 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo2 === void 0 ? void 0 : _this$dismissTeaserFo2.call(this);
const formData = new FormData();
formData.append('message[body]', body);
formData.append('message[replied_to]', id);
@@ -415,13 +451,15 @@ let _default = /*#__PURE__*/function (_Controller) {
formData.append('message[button]', buttonId);
formData.append('session', _hellotext.default.session);
formData.append('locale', _core.Locale.toString());
+ this.appendOpeningSequenceMessageIds(formData);
const element = this.buildMessageElement();
const attachment = (_cardElement$querySel = cardElement.querySelector('img')) === null || _cardElement$querySel === void 0 ? void 0 : _cardElement$querySel.cloneNode(true);
element.querySelector('[data-body]').innerText = body;
if (attachment) {
+ var _this$messageAttachme3;
attachment.removeAttribute('width');
attachment.removeAttribute('height');
- element.querySelector('[data-attachment-container]').appendChild(attachment);
+ (_this$messageAttachme3 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme3 === void 0 ? void 0 : _this$messageAttachme3.appendChild(attachment);
}
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
@@ -450,6 +488,7 @@ let _default = /*#__PURE__*/function (_Controller) {
target: element,
detail: data.id
});
+ this.clearRevealedOpeningSequenceMessageIds();
const message = {
id: data.id,
body: body,
@@ -461,10 +500,80 @@ let _default = /*#__PURE__*/function (_Controller) {
};
_hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
}
+ }, {
+ key: "sendTeaserQuickReply",
+ value: async function sendTeaserQuickReply(event) {
+ var _this$dismissTeaserFo3;
+ event.preventDefault();
+ event.stopPropagation();
+ const button = event.currentTarget;
+ const value = (button.dataset.value || '').trim();
+ const label = [button.dataset.text, button.textContent].map(text => (text || '').trim()).find(text => text.length > 0);
+ const text = value || label;
+ if (!text) return;
+ (_this$dismissTeaserFo3 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo3 === void 0 ? void 0 : _this$dismissTeaserFo3.call(this);
+ this.show();
+ const buttonType = (button.dataset.type || '').trim() || 'quick_reply';
+ const formData = new FormData();
+ formData.append('message[body]', text);
+ formData.append('session', _hellotext.default.session);
+ formData.append('locale', _core.Locale.toString());
+ this.appendOpeningSequenceMessageIds(formData);
+ const element = this.buildMessageElement();
+ element.querySelector('[data-body]').innerText = text;
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
+ } else {
+ this.messagesContainerTarget.appendChild(element);
+ }
+ element.scrollIntoView({
+ behavior: 'smooth'
+ });
+ this.broadcastChannel.postMessage({
+ type: 'message:sent',
+ element: element.outerHTML
+ });
+ if (!this.typingIndicatorVisible) {
+ clearTimeout(this.optimisticTypingTimeout);
+ this.optimisticTypingTimeout = setTimeout(() => {
+ this.showOptimisticTypingIndicator();
+ }, this.optimisticTypingIndicatorWaitValue);
+ }
+ const response = await this.messagesAPI.create(formData);
+ if (response.failed) {
+ clearTimeout(this.optimisticTypingTimeout);
+ this.broadcastChannel.postMessage({
+ type: 'message:failed',
+ id: element.id
+ });
+ return element.classList.add('failed');
+ }
+ const data = await response.json();
+ element.setAttribute('data-id', data.id);
+ this.clearRevealedOpeningSequenceMessageIds();
+ _hellotext.default.eventEmitter.dispatch('webchat:message:sent', {
+ id: data.id,
+ body: text,
+ attachments: [],
+ type: 'quick_reply',
+ teaser: {
+ text: label || text,
+ value: value || text,
+ type: buttonType
+ }
+ });
+ if (data.conversation && data.conversation !== this.conversationIdValue) {
+ this.conversationIdValue = data.conversation;
+ this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
+ }
+ if (this.typingIndicatorVisible) {
+ this.resetTypingIndicatorTimer();
+ }
+ }
}, {
key: "sendMessage",
value: async function sendMessage(e) {
- const formData = new FormData();
+ var _this$dismissTeaserFo4;
const message = {
body: this.inputTarget.value,
attachments: this.files
@@ -475,6 +584,8 @@ let _default = /*#__PURE__*/function (_Controller) {
}
return;
}
+ (_this$dismissTeaserFo4 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo4 === void 0 ? void 0 : _this$dismissTeaserFo4.call(this);
+ const formData = new FormData();
if (this.inputTarget.value.trim().length > 0) {
formData.append('message[body]', this.inputTarget.value);
} else {
@@ -485,6 +596,7 @@ let _default = /*#__PURE__*/function (_Controller) {
});
formData.append('session', _hellotext.default.session);
formData.append('locale', _core.Locale.toString());
+ this.appendOpeningSequenceMessageIds(formData);
const element = this.buildMessageElement();
if (this.inputTarget.value.trim().length > 0) {
element.querySelector('[data-body]').innerText = this.inputTarget.value;
@@ -494,7 +606,8 @@ let _default = /*#__PURE__*/function (_Controller) {
const attachments = this.attachmentContainerTarget.querySelectorAll('img');
if (attachments.length > 0) {
attachments.forEach(attachment => {
- element.querySelector('[data-attachment-container]').appendChild(attachment.cloneNode(true));
+ var _this$messageAttachme4;
+ (_this$messageAttachme4 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme4 === void 0 ? void 0 : _this$messageAttachme4.appendChild(attachment.cloneNode(true));
});
}
@@ -541,6 +654,7 @@ let _default = /*#__PURE__*/function (_Controller) {
const data = await response.json();
element.setAttribute('data-id', data.id);
message.id = data.id;
+ this.clearRevealedOpeningSequenceMessageIds();
_hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
if (data.conversation !== this.conversationIdValue) {
this.conversationIdValue = data.conversation;
@@ -564,6 +678,18 @@ let _default = /*#__PURE__*/function (_Controller) {
element.setAttribute('data-hellotext--webchat-target', 'message');
return element;
}
+ }, {
+ key: "messageAttachmentsContainer",
+ value: function messageAttachmentsContainer(element) {
+ return element.querySelector('[data-attachments-container], [data-attachment-container]');
+ }
+ }, {
+ key: "incrementUnreadCounter",
+ value: function incrementUnreadCounter() {
+ this.unreadCounterTarget.style.display = 'flex';
+ const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
+ this.unreadCounterTarget.innerText = Math.min(unreadCount, 9);
+ }
}, {
key: "openAttachment",
value: function openAttachment() {
@@ -731,7 +857,9 @@ _default.values = {
default: 1000
},
// 1 second,
- teaser: Object
+ teaser: Object,
+ messageTeaser: String,
+ behaviour: Object
};
_default.classes = ['fadeOut'];
-_default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate', 'teaser'];
\ No newline at end of file
+_default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate', 'teaser', 'openingSequence', 'openingSequenceMessage'];
\ No newline at end of file
diff --git a/lib/controllers/webchat_controller.js b/lib/controllers/webchat_controller.js
index 47506f0..74f8b29 100644
--- a/lib/controllers/webchat_controller.js
+++ b/lib/controllers/webchat_controller.js
@@ -23,9 +23,11 @@ import WebchatMessagesAPI from '../api/webchat/messages';
import WebchatChannel from '../channels/webchat_channel';
import Hellotext from '../hellotext';
import { Locale } from '../core';
-import { Webchat as WebchatConfiguration, behaviors } from '../core/configuration/webchat';
-import { LogoBuilder } from '../builders/logo_builder';
+import { Webchat as WebchatConfiguration, modes } from '../core/configuration/webchat';
import { usePopover } from './mixins/usePopover';
+import { useBehaviour } from './webchat/useBehaviour';
+import { useOpeningSequence } from './webchat/useOpeningSequence';
+import { useTeaser } from './webchat/useTeaser';
var _default = /*#__PURE__*/function (_Controller) {
_inherits(_default, _Controller);
var _super = _createSuper(_default);
@@ -39,6 +41,7 @@ var _default = /*#__PURE__*/function (_Controller) {
this.messagesAPI = new WebchatMessagesAPI(this.idValue);
this.webChatChannel = new WebchatChannel(this.idValue, Hellotext.session, this.conversationIdValue);
this.files = [];
+ this.messageIds = new Set();
this.onMessageReceived = this.onMessageReceived.bind(this);
this.onMessageReaction = this.onMessageReaction.bind(this);
this.onTypingStart = this.onTypingStart.bind(this);
@@ -50,9 +53,10 @@ var _default = /*#__PURE__*/function (_Controller) {
}, {
key: "connect",
value: function connect() {
+ useBehaviour(this);
usePopover(this);
- this.popoverTarget.classList.add(...WebchatConfiguration.classes);
- this.triggerTarget.classList.add(...WebchatConfiguration.triggerClasses);
+ useOpeningSequence(this);
+ useTeaser(this);
this.setupFloatingUI({
trigger: this.triggerTarget,
popover: this.popoverTarget
@@ -64,23 +68,26 @@ var _default = /*#__PURE__*/function (_Controller) {
strategy: 'absolute'
});
}
+ this.setupTeaser();
+ this.setupOpeningSequence();
this.webChatChannel.onMessage(this.onMessageReceived);
this.webChatChannel.onTypingStart(this.onTypingStart);
this.webChatChannel.onReaction(this.onMessageReaction);
this.messagesContainerTarget.addEventListener('scroll', this.onScroll);
- if (!Hellotext.business.features.white_label) {
- this.toolbarTarget.appendChild(LogoBuilder.build());
- }
if (this.shouldOpenOnMount) {
this.openValue = true;
}
Hellotext.eventEmitter.dispatch('webchat:mounted');
this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent);
+ this.scheduleBehaviourOpen();
_get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
}
}, {
key: "disconnect",
value: function disconnect() {
+ this.cancelBehaviourOpen();
+ this.teardownTeaser();
+ this.teardownOpeningSequence();
this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
@@ -227,11 +234,12 @@ var _default = /*#__PURE__*/function (_Controller) {
}
if (attachments) {
attachments.forEach(attachmentUrl => {
+ var _this$messageAttachme;
var image = this.attachmentImageTarget.cloneNode(true);
image.removeAttribute('data-hellotext--webchat-target');
image.src = attachmentUrl;
image.style.display = 'block';
- element.querySelector('[data-attachment-container]').appendChild(image);
+ (_this$messageAttachme = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme === void 0 ? void 0 : _this$messageAttachme.appendChild(image);
});
}
element.setAttribute('data-body', body);
@@ -251,7 +259,7 @@ var _default = /*#__PURE__*/function (_Controller) {
}, {
key: "onClickOutside",
value: function onClickOutside(event) {
- if (WebchatConfiguration.behaviour === behaviors.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
+ if (WebchatConfiguration.mode === modes.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
this.openValue = false;
}
}
@@ -266,7 +274,9 @@ var _default = /*#__PURE__*/function (_Controller) {
}, {
key: "onPopoverOpened",
value: function onPopoverOpened() {
+ var _this$dismissTeaserFo;
this.popoverTarget.classList.remove(...this.fadeOutClasses);
+ (_this$dismissTeaserFo = this.dismissTeaserForSession) === null || _this$dismissTeaserFo === void 0 ? void 0 : _this$dismissTeaserFo.call(this);
if (!this.onMobile) {
this.inputTarget.focus();
}
@@ -281,9 +291,10 @@ var _default = /*#__PURE__*/function (_Controller) {
}
Hellotext.eventEmitter.dispatch('webchat:opened');
localStorage.setItem("hellotext--webchat--".concat(this.idValue), 'opened');
- if (this.hasTeaserTarget) {
- this.teaserTarget.classList.add('hidden');
+ if (this.messageTeaserValue) {
+ this.messageTeaserValue = null;
}
+ this.startOpeningSequence();
if (this.unreadCounterTarget.style.display === 'none') return;
this.unreadCounterTarget.style.display = 'none';
this.unreadCounterTarget.innerText = '0';
@@ -294,9 +305,6 @@ var _default = /*#__PURE__*/function (_Controller) {
value: function onPopoverClosed() {
Hellotext.eventEmitter.dispatch('webchat:closed');
localStorage.setItem("hellotext--webchat--".concat(this.idValue), 'closed');
- if (this.hasTeaserTarget && this.teaserValue.body) {
- this.teaserTarget.classList.remove('hidden');
- }
}
}, {
key: "onMessageReaction",
@@ -325,11 +333,15 @@ var _default = /*#__PURE__*/function (_Controller) {
}, {
key: "onMessageReceived",
value: function onMessageReceived(message) {
+ var _this$hideTeaser;
var {
id,
body,
- attachments
+ attachments,
+ teaser
} = message;
+ if (!this.claimMessageId(id)) return;
+ (_this$hideTeaser = this.hideTeaser) === null || _this$hideTeaser === void 0 ? void 0 : _this$hideTeaser.call(this);
if (message.carousel) {
return this.insertCarouselMessage(message);
}
@@ -338,13 +350,15 @@ var _default = /*#__PURE__*/function (_Controller) {
var element = this.messageTemplateTarget.cloneNode(true);
element.style.display = 'flex';
element.querySelector('[data-body]').innerHTML = div.innerHTML;
+ element.setAttribute('data-id', id);
element.setAttribute('data-hellotext--webchat-target', 'message');
if (attachments) {
attachments.forEach(attachmentUrl => {
+ var _this$messageAttachme2;
var image = this.attachmentImageTarget.cloneNode(true);
image.src = attachmentUrl;
image.style.display = 'block';
- element.querySelector('[data-attachment-container]').appendChild(image);
+ (_this$messageAttachme2 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme2 === void 0 ? void 0 : _this$messageAttachme2.appendChild(image);
});
}
this.clearTypingIndicator();
@@ -355,13 +369,33 @@ var _default = /*#__PURE__*/function (_Controller) {
element.scrollIntoView({
behavior: 'smooth'
});
+ this.updateMessageTeaser(teaser);
if (this.openValue) {
this.messagesAPI.markAsSeen(id);
return;
}
- this.unreadCounterTarget.style.display = 'flex';
- var unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
+ this.incrementUnreadCounter();
+ }
+
+ // TCP broadcasts may deliver the same incoming message twice before Stimulus
+ // has connected the appended message target. Claiming the id in memory closes
+ // that window, while the target check still drops messages rendered earlier.
+ }, {
+ key: "claimMessageId",
+ value: function claimMessageId(id) {
+ var messageTargets = this.messageTargets || [];
+ if (this.messageIds.has(id)) return false;
+ this.messageIds.add(id);
+ return !messageTargets.some(element => element.dataset.id === id);
+ }
+ }, {
+ key: "updateMessageTeaser",
+ value: function updateMessageTeaser(teaser) {
+ this.messageTeaserValue = teaser;
+ if (this.messageTeaserValue && this.hasTeaserTarget) {
+ this.teaserTarget.innerHTML = this.messageTeaserValue;
+ this.teaserTarget.classList.toggle('invisible', this.openValue);
+ }
}
}, {
key: "insertCarouselMessage",
@@ -369,6 +403,8 @@ var _default = /*#__PURE__*/function (_Controller) {
var _element$querySelecto;
var html = message.html;
var element = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild;
+ element.setAttribute('data-id', message.id);
+ element.setAttribute('data-hellotext--webchat-target', 'message');
this.clearTypingIndicator();
this.messagesContainerTarget.appendChild(element);
element.scrollIntoView({
@@ -377,13 +413,12 @@ var _default = /*#__PURE__*/function (_Controller) {
Hellotext.eventEmitter.dispatch('webchat:message:received', _objectSpread(_objectSpread({}, message), {}, {
body: ((_element$querySelecto = element.querySelector('[data-body]')) === null || _element$querySelecto === void 0 ? void 0 : _element$querySelecto.innerText) || ''
}));
+ this.updateMessageTeaser(message.teaser);
if (this.openValue) {
this.messagesAPI.markAsSeen(message.id);
return;
}
- this.unreadCounterTarget.style.display = 'flex';
- var unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
+ this.incrementUnreadCounter();
}
}, {
key: "resizeInput",
@@ -402,7 +437,7 @@ var _default = /*#__PURE__*/function (_Controller) {
key: "sendQuickReplyMessage",
value: function () {
var _sendQuickReplyMessage = _asyncToGenerator(function* (_ref) {
- var _cardElement$querySel;
+ var _this$dismissTeaserFo2, _cardElement$querySel;
var {
detail: {
id,
@@ -412,6 +447,7 @@ var _default = /*#__PURE__*/function (_Controller) {
cardElement
}
} = _ref;
+ (_this$dismissTeaserFo2 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo2 === void 0 ? void 0 : _this$dismissTeaserFo2.call(this);
var formData = new FormData();
formData.append('message[body]', body);
formData.append('message[replied_to]', id);
@@ -419,13 +455,15 @@ var _default = /*#__PURE__*/function (_Controller) {
formData.append('message[button]', buttonId);
formData.append('session', Hellotext.session);
formData.append('locale', Locale.toString());
+ this.appendOpeningSequenceMessageIds(formData);
var element = this.buildMessageElement();
var attachment = (_cardElement$querySel = cardElement.querySelector('img')) === null || _cardElement$querySel === void 0 ? void 0 : _cardElement$querySel.cloneNode(true);
element.querySelector('[data-body]').innerText = body;
if (attachment) {
+ var _this$messageAttachme3;
attachment.removeAttribute('width');
attachment.removeAttribute('height');
- element.querySelector('[data-attachment-container]').appendChild(attachment);
+ (_this$messageAttachme3 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme3 === void 0 ? void 0 : _this$messageAttachme3.appendChild(attachment);
}
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
@@ -454,6 +492,7 @@ var _default = /*#__PURE__*/function (_Controller) {
target: element,
detail: data.id
});
+ this.clearRevealedOpeningSequenceMessageIds();
var message = {
id: data.id,
body: body,
@@ -470,11 +509,87 @@ var _default = /*#__PURE__*/function (_Controller) {
}
return sendQuickReplyMessage;
}()
+ }, {
+ key: "sendTeaserQuickReply",
+ value: function () {
+ var _sendTeaserQuickReply = _asyncToGenerator(function* (event) {
+ var _this$dismissTeaserFo3;
+ event.preventDefault();
+ event.stopPropagation();
+ var button = event.currentTarget;
+ var value = (button.dataset.value || '').trim();
+ var label = [button.dataset.text, button.textContent].map(text => (text || '').trim()).find(text => text.length > 0);
+ var text = value || label;
+ if (!text) return;
+ (_this$dismissTeaserFo3 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo3 === void 0 ? void 0 : _this$dismissTeaserFo3.call(this);
+ this.show();
+ var buttonType = (button.dataset.type || '').trim() || 'quick_reply';
+ var formData = new FormData();
+ formData.append('message[body]', text);
+ formData.append('session', Hellotext.session);
+ formData.append('locale', Locale.toString());
+ this.appendOpeningSequenceMessageIds(formData);
+ var element = this.buildMessageElement();
+ element.querySelector('[data-body]').innerText = text;
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
+ } else {
+ this.messagesContainerTarget.appendChild(element);
+ }
+ element.scrollIntoView({
+ behavior: 'smooth'
+ });
+ this.broadcastChannel.postMessage({
+ type: 'message:sent',
+ element: element.outerHTML
+ });
+ if (!this.typingIndicatorVisible) {
+ clearTimeout(this.optimisticTypingTimeout);
+ this.optimisticTypingTimeout = setTimeout(() => {
+ this.showOptimisticTypingIndicator();
+ }, this.optimisticTypingIndicatorWaitValue);
+ }
+ var response = yield this.messagesAPI.create(formData);
+ if (response.failed) {
+ clearTimeout(this.optimisticTypingTimeout);
+ this.broadcastChannel.postMessage({
+ type: 'message:failed',
+ id: element.id
+ });
+ return element.classList.add('failed');
+ }
+ var data = yield response.json();
+ element.setAttribute('data-id', data.id);
+ this.clearRevealedOpeningSequenceMessageIds();
+ Hellotext.eventEmitter.dispatch('webchat:message:sent', {
+ id: data.id,
+ body: text,
+ attachments: [],
+ type: 'quick_reply',
+ teaser: {
+ text: label || text,
+ value: value || text,
+ type: buttonType
+ }
+ });
+ if (data.conversation && data.conversation !== this.conversationIdValue) {
+ this.conversationIdValue = data.conversation;
+ this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
+ }
+ if (this.typingIndicatorVisible) {
+ this.resetTypingIndicatorTimer();
+ }
+ });
+ function sendTeaserQuickReply(_x2) {
+ return _sendTeaserQuickReply.apply(this, arguments);
+ }
+ return sendTeaserQuickReply;
+ }()
}, {
key: "sendMessage",
value: function () {
var _sendMessage = _asyncToGenerator(function* (e) {
- var formData = new FormData();
+ var _this$dismissTeaserFo4;
var message = {
body: this.inputTarget.value,
attachments: this.files
@@ -485,6 +600,8 @@ var _default = /*#__PURE__*/function (_Controller) {
}
return;
}
+ (_this$dismissTeaserFo4 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo4 === void 0 ? void 0 : _this$dismissTeaserFo4.call(this);
+ var formData = new FormData();
if (this.inputTarget.value.trim().length > 0) {
formData.append('message[body]', this.inputTarget.value);
} else {
@@ -495,6 +612,7 @@ var _default = /*#__PURE__*/function (_Controller) {
});
formData.append('session', Hellotext.session);
formData.append('locale', Locale.toString());
+ this.appendOpeningSequenceMessageIds(formData);
var element = this.buildMessageElement();
if (this.inputTarget.value.trim().length > 0) {
element.querySelector('[data-body]').innerText = this.inputTarget.value;
@@ -504,7 +622,8 @@ var _default = /*#__PURE__*/function (_Controller) {
var attachments = this.attachmentContainerTarget.querySelectorAll('img');
if (attachments.length > 0) {
attachments.forEach(attachment => {
- element.querySelector('[data-attachment-container]').appendChild(attachment.cloneNode(true));
+ var _this$messageAttachme4;
+ (_this$messageAttachme4 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme4 === void 0 ? void 0 : _this$messageAttachme4.appendChild(attachment.cloneNode(true));
});
}
@@ -551,6 +670,7 @@ var _default = /*#__PURE__*/function (_Controller) {
var data = yield response.json();
element.setAttribute('data-id', data.id);
message.id = data.id;
+ this.clearRevealedOpeningSequenceMessageIds();
Hellotext.eventEmitter.dispatch('webchat:message:sent', message);
if (data.conversation !== this.conversationIdValue) {
this.conversationIdValue = data.conversation;
@@ -563,7 +683,7 @@ var _default = /*#__PURE__*/function (_Controller) {
}
this.attachmentContainerTarget.style.display = '';
});
- function sendMessage(_x2) {
+ function sendMessage(_x3) {
return _sendMessage.apply(this, arguments);
}
return sendMessage;
@@ -579,6 +699,18 @@ var _default = /*#__PURE__*/function (_Controller) {
element.setAttribute('data-hellotext--webchat-target', 'message');
return element;
}
+ }, {
+ key: "messageAttachmentsContainer",
+ value: function messageAttachmentsContainer(element) {
+ return element.querySelector('[data-attachments-container], [data-attachment-container]');
+ }
+ }, {
+ key: "incrementUnreadCounter",
+ value: function incrementUnreadCounter() {
+ this.unreadCounterTarget.style.display = 'flex';
+ var unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
+ this.unreadCounterTarget.innerText = Math.min(unreadCount, 9);
+ }
}, {
key: "openAttachment",
value: function openAttachment() {
@@ -747,8 +879,10 @@ _default.values = {
default: 1000
},
// 1 second,
- teaser: Object
+ teaser: Object,
+ messageTeaser: String,
+ behaviour: Object
};
_default.classes = ['fadeOut'];
-_default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate', 'teaser'];
+_default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate', 'teaser', 'openingSequence', 'openingSequenceMessage'];
export { _default as default };
\ No newline at end of file
diff --git a/lib/core/configuration/webchat.cjs b/lib/core/configuration/webchat.cjs
index 1e17a7a..ff06040 100644
--- a/lib/core/configuration/webchat.cjs
+++ b/lib/core/configuration/webchat.cjs
@@ -3,7 +3,7 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
-exports.behaviors = exports.Webchat = void 0;
+exports.modes = exports.Webchat = void 0;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
@@ -39,13 +39,13 @@ const strategies = {
};
/**
- * @typedef {'modal' | 'popover'} Behaviour
+ * @typedef {'modal' | 'popover'} Mode
*/
/**
- * @enum {Behaviour}
+ * @enum {Mode}
*/
-const behaviors = {
+const modes = {
MODAL: 'modal',
POPOVER: 'popover'
};
@@ -57,6 +57,20 @@ const behaviors = {
* @property {string} typography - The typography style (e.g., font family).
*/
+/**
+ * @typedef {Object} Appearance
+ * @property {Object} [header] - Header appearance overrides.
+ * @property {string} [header.name] - Business name shown in the webchat header.
+ * @property {Object} [launcher] - Launcher appearance overrides.
+ * @property {string} [launcher.iconUrl] - Image URL used for the launcher icon.
+ */
+
+/**
+ * @typedef {Object} WhatsApp
+ * @property {string} [number] - WhatsApp number used by the handoff configuration.
+ * @property {boolean} [restrictToChannel] - Whether handoff should be restricted to the configured channel.
+ */
+
/**
* @class Webchat
* @classdesc
@@ -64,13 +78,14 @@ const behaviors = {
* @property {String} id - the id of the webchat.
* @property {String} container - the container to append the webchat to, defaults to 'body'
* @property {Placement} placement - the placement of the webchat within the container, defaults to "bottom-right".
- * @property {String} classes - additional classes to apply to the webchat popup.
- * @property {String} triggerClasses - additional classes to apply to the webchat popup trigger.
- * @property {Behaviour} behaviour - the behaviour of the webchat, defaults to 'popover'.
+ * @property {Mode} mode - how the webchat behaves while open, defaults to 'popover'.
+ * @property {Object} behaviour - runtime opening behaviour for the webchat.
* @property {Style} style - the style of the webchat.
+ * @property {Appearance} appearance - appearance overrides.
+ * @property {WhatsApp} whatsapp - WhatsApp handoff overrides.
* @property {Strategy} strategy - the strategy used to position the webchat. Defaults to 'absolute'
*/
-exports.behaviors = behaviors;
+exports.modes = modes;
let Webchat = /*#__PURE__*/function () {
function Webchat() {
_classCallCheck(this, Webchat);
@@ -94,36 +109,6 @@ let Webchat = /*#__PURE__*/function () {
}
this._placement = value;
}
- }, {
- key: "classes",
- get: function () {
- if (typeof this._classes === 'string') {
- return this._classes.split(',').map(c => c.trim());
- } else {
- return this._classes;
- }
- },
- set: function (value) {
- if (!Array.isArray(value) && typeof value !== 'string') {
- throw new Error('classes must be an array or a string');
- }
- this._classes = value;
- }
- }, {
- key: "triggerClasses",
- get: function () {
- if (typeof this._triggerClasses === 'string') {
- return this._triggerClasses.split(',').map(c => c.trim());
- } else {
- return this._triggerClasses;
- }
- },
- set: function (value) {
- if (!Array.isArray(value) && typeof value !== 'string') {
- throw new Error('triggerClasses must be an array or a string');
- }
- this._triggerClasses = value;
- }
}, {
key: "id",
get: function () {
@@ -159,17 +144,100 @@ let Webchat = /*#__PURE__*/function () {
});
this._style = value;
}
+ }, {
+ key: "appearance",
+ get: function () {
+ return this._appearance;
+ },
+ set: function (value) {
+ if (!this.isPlainObject(value)) {
+ throw new Error('Appearance must be an object');
+ }
+ Object.entries(value).forEach(([key, nestedValue]) => {
+ if (!['header', 'launcher'].includes(key)) {
+ throw new Error(`Invalid appearance property: ${key}`);
+ }
+ if (!this.isPlainObject(nestedValue)) {
+ throw new Error(`Appearance ${key} must be an object`);
+ }
+ Object.entries(nestedValue).forEach(([nestedKey, propertyValue]) => {
+ if (key === 'header' && nestedKey !== 'name') {
+ throw new Error(`Invalid appearance header property: ${nestedKey}`);
+ }
+ if (key === 'launcher' && nestedKey !== 'iconUrl') {
+ throw new Error(`Invalid appearance launcher property: ${nestedKey}`);
+ }
+ if (propertyValue == null) {
+ return;
+ }
+ if (typeof propertyValue !== 'string') {
+ throw new Error(`Invalid appearance ${key}.${nestedKey} value: ${propertyValue}`);
+ }
+ });
+ });
+ this._appearance = value;
+ }
+ }, {
+ key: "whatsapp",
+ get: function () {
+ return this._whatsapp;
+ },
+ set: function (value) {
+ if (!this.isPlainObject(value)) {
+ throw new Error('WhatsApp must be an object');
+ }
+ Object.entries(value).forEach(([key, nestedValue]) => {
+ if (!['number', 'restrictToChannel'].includes(key)) {
+ throw new Error(`Invalid WhatsApp property: ${key}`);
+ }
+ if (nestedValue == null) {
+ return;
+ }
+ if (key === 'number' && typeof nestedValue !== 'string') {
+ throw new Error(`Invalid WhatsApp number value: ${nestedValue}`);
+ }
+ if (key === 'restrictToChannel' && typeof nestedValue !== 'boolean') {
+ throw new Error(`Invalid WhatsApp restrictToChannel value: ${nestedValue}`);
+ }
+ });
+ this._whatsapp = value;
+ }
+ }, {
+ key: "mode",
+ get: function () {
+ return this._mode;
+ },
+ set: function (value) {
+ if (!Object.values(modes).includes(value)) {
+ throw new Error(`Invalid mode value: ${value}`);
+ }
+ this._mode = value;
+ }
}, {
key: "behaviour",
get: function () {
return this._behaviour;
},
set: function (value) {
- if (!Object.values(behaviors).includes(value)) {
+ if (value == null) {
+ this._behaviour = value;
+ return;
+ }
+ if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`Invalid behaviour value: ${value}`);
}
this._behaviour = value;
}
+ }, {
+ key: "hasBehaviourOverride",
+ get: function () {
+ return this._hasBehaviourOverride;
+ }
+ }, {
+ key: "behaviourOverride",
+ set: function (value) {
+ this._hasBehaviourOverride = !!value;
+ }
}, {
key: "strategy",
get: function () {
@@ -199,6 +267,11 @@ let Webchat = /*#__PURE__*/function () {
value: function isHexOrRgba(value) {
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value) || /^rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3},?\s*(0|1|0?\.\d+)?\s*\)$/.test(value);
}
+ }, {
+ key: "isPlainObject",
+ value: function isPlainObject(value) {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+ }
}]);
return Webchat;
}();
@@ -206,8 +279,10 @@ exports.Webchat = Webchat;
Webchat._id = void 0;
Webchat._container = 'body';
Webchat._placement = 'bottom-right';
-Webchat._classes = [];
-Webchat._triggerClasses = [];
Webchat._style = {};
-Webchat._behaviour = behaviors.POPOVER;
+Webchat._appearance = {};
+Webchat._whatsapp = {};
+Webchat._mode = modes.POPOVER;
+Webchat._behaviour = null;
+Webchat._hasBehaviourOverride = false;
Webchat._strategy = null;
\ No newline at end of file
diff --git a/lib/core/configuration/webchat.js b/lib/core/configuration/webchat.js
index 8c5fa51..0e3f777 100644
--- a/lib/core/configuration/webchat.js
+++ b/lib/core/configuration/webchat.js
@@ -33,13 +33,13 @@ var strategies = {
};
/**
- * @typedef {'modal' | 'popover'} Behaviour
+ * @typedef {'modal' | 'popover'} Mode
*/
/**
- * @enum {Behaviour}
+ * @enum {Mode}
*/
-var behaviors = {
+var modes = {
MODAL: 'modal',
POPOVER: 'popover'
};
@@ -51,6 +51,20 @@ var behaviors = {
* @property {string} typography - The typography style (e.g., font family).
*/
+/**
+ * @typedef {Object} Appearance
+ * @property {Object} [header] - Header appearance overrides.
+ * @property {string} [header.name] - Business name shown in the webchat header.
+ * @property {Object} [launcher] - Launcher appearance overrides.
+ * @property {string} [launcher.iconUrl] - Image URL used for the launcher icon.
+ */
+
+/**
+ * @typedef {Object} WhatsApp
+ * @property {string} [number] - WhatsApp number used by the handoff configuration.
+ * @property {boolean} [restrictToChannel] - Whether handoff should be restricted to the configured channel.
+ */
+
/**
* @class Webchat
* @classdesc
@@ -58,10 +72,11 @@ var behaviors = {
* @property {String} id - the id of the webchat.
* @property {String} container - the container to append the webchat to, defaults to 'body'
* @property {Placement} placement - the placement of the webchat within the container, defaults to "bottom-right".
- * @property {String} classes - additional classes to apply to the webchat popup.
- * @property {String} triggerClasses - additional classes to apply to the webchat popup trigger.
- * @property {Behaviour} behaviour - the behaviour of the webchat, defaults to 'popover'.
+ * @property {Mode} mode - how the webchat behaves while open, defaults to 'popover'.
+ * @property {Object} behaviour - runtime opening behaviour for the webchat.
* @property {Style} style - the style of the webchat.
+ * @property {Appearance} appearance - appearance overrides.
+ * @property {WhatsApp} whatsapp - WhatsApp handoff overrides.
* @property {Strategy} strategy - the strategy used to position the webchat. Defaults to 'absolute'
*/
var Webchat = /*#__PURE__*/function () {
@@ -87,36 +102,6 @@ var Webchat = /*#__PURE__*/function () {
}
this._placement = value;
}
- }, {
- key: "classes",
- get: function get() {
- if (typeof this._classes === 'string') {
- return this._classes.split(',').map(c => c.trim());
- } else {
- return this._classes;
- }
- },
- set: function set(value) {
- if (!Array.isArray(value) && typeof value !== 'string') {
- throw new Error('classes must be an array or a string');
- }
- this._classes = value;
- }
- }, {
- key: "triggerClasses",
- get: function get() {
- if (typeof this._triggerClasses === 'string') {
- return this._triggerClasses.split(',').map(c => c.trim());
- } else {
- return this._triggerClasses;
- }
- },
- set: function set(value) {
- if (!Array.isArray(value) && typeof value !== 'string') {
- throw new Error('triggerClasses must be an array or a string');
- }
- this._triggerClasses = value;
- }
}, {
key: "id",
get: function get() {
@@ -153,17 +138,103 @@ var Webchat = /*#__PURE__*/function () {
});
this._style = value;
}
+ }, {
+ key: "appearance",
+ get: function get() {
+ return this._appearance;
+ },
+ set: function set(value) {
+ if (!this.isPlainObject(value)) {
+ throw new Error('Appearance must be an object');
+ }
+ Object.entries(value).forEach(_ref2 => {
+ var [key, nestedValue] = _ref2;
+ if (!['header', 'launcher'].includes(key)) {
+ throw new Error("Invalid appearance property: ".concat(key));
+ }
+ if (!this.isPlainObject(nestedValue)) {
+ throw new Error("Appearance ".concat(key, " must be an object"));
+ }
+ Object.entries(nestedValue).forEach(_ref3 => {
+ var [nestedKey, propertyValue] = _ref3;
+ if (key === 'header' && nestedKey !== 'name') {
+ throw new Error("Invalid appearance header property: ".concat(nestedKey));
+ }
+ if (key === 'launcher' && nestedKey !== 'iconUrl') {
+ throw new Error("Invalid appearance launcher property: ".concat(nestedKey));
+ }
+ if (propertyValue == null) {
+ return;
+ }
+ if (typeof propertyValue !== 'string') {
+ throw new Error("Invalid appearance ".concat(key, ".").concat(nestedKey, " value: ").concat(propertyValue));
+ }
+ });
+ });
+ this._appearance = value;
+ }
+ }, {
+ key: "whatsapp",
+ get: function get() {
+ return this._whatsapp;
+ },
+ set: function set(value) {
+ if (!this.isPlainObject(value)) {
+ throw new Error('WhatsApp must be an object');
+ }
+ Object.entries(value).forEach(_ref4 => {
+ var [key, nestedValue] = _ref4;
+ if (!['number', 'restrictToChannel'].includes(key)) {
+ throw new Error("Invalid WhatsApp property: ".concat(key));
+ }
+ if (nestedValue == null) {
+ return;
+ }
+ if (key === 'number' && typeof nestedValue !== 'string') {
+ throw new Error("Invalid WhatsApp number value: ".concat(nestedValue));
+ }
+ if (key === 'restrictToChannel' && typeof nestedValue !== 'boolean') {
+ throw new Error("Invalid WhatsApp restrictToChannel value: ".concat(nestedValue));
+ }
+ });
+ this._whatsapp = value;
+ }
+ }, {
+ key: "mode",
+ get: function get() {
+ return this._mode;
+ },
+ set: function set(value) {
+ if (!Object.values(modes).includes(value)) {
+ throw new Error("Invalid mode value: ".concat(value));
+ }
+ this._mode = value;
+ }
}, {
key: "behaviour",
get: function get() {
return this._behaviour;
},
set: function set(value) {
- if (!Object.values(behaviors).includes(value)) {
+ if (value == null) {
+ this._behaviour = value;
+ return;
+ }
+ if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error("Invalid behaviour value: ".concat(value));
}
this._behaviour = value;
}
+ }, {
+ key: "hasBehaviourOverride",
+ get: function get() {
+ return this._hasBehaviourOverride;
+ }
+ }, {
+ key: "behaviourOverride",
+ set: function set(value) {
+ this._hasBehaviourOverride = !!value;
+ }
}, {
key: "strategy",
get: function get() {
@@ -182,8 +253,8 @@ var Webchat = /*#__PURE__*/function () {
key: "assign",
value: function assign(props) {
if (props) {
- Object.entries(props).forEach(_ref2 => {
- var [key, value] = _ref2;
+ Object.entries(props).forEach(_ref5 => {
+ var [key, value] = _ref5;
this[key] = value;
});
}
@@ -194,15 +265,22 @@ var Webchat = /*#__PURE__*/function () {
value: function isHexOrRgba(value) {
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value) || /^rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3},?\s*(0|1|0?\.\d+)?\s*\)$/.test(value);
}
+ }, {
+ key: "isPlainObject",
+ value: function isPlainObject(value) {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+ }
}]);
return Webchat;
}();
Webchat._id = void 0;
Webchat._container = 'body';
Webchat._placement = 'bottom-right';
-Webchat._classes = [];
-Webchat._triggerClasses = [];
Webchat._style = {};
-Webchat._behaviour = behaviors.POPOVER;
+Webchat._appearance = {};
+Webchat._whatsapp = {};
+Webchat._mode = modes.POPOVER;
+Webchat._behaviour = null;
+Webchat._hasBehaviourOverride = false;
Webchat._strategy = null;
-export { behaviors, Webchat };
\ No newline at end of file
+export { modes, Webchat };
\ No newline at end of file
diff --git a/lib/hellotext.cjs b/lib/hellotext.cjs
index cc3d896..42ad094 100644
--- a/lib/hellotext.cjs
+++ b/lib/hellotext.cjs
@@ -35,10 +35,9 @@ let Hellotext = /*#__PURE__*/function () {
this.forms = new _models.FormCollection();
this.query = new _models.Query();
const businessData = await this.business.hydrate();
- const webchatConfig = config.webchat === false ? false : {
- ...(businessData && businessData.webchat || {}),
- ...(config.webchat || {})
- };
+ const webchatConfig = config.webchat === false ? false : this.mergeWebchatConfig(businessData && businessData.webchat || {}, config.webchat || {});
+ const hasExplicitBehaviourOverride = config.webchat && config.webchat !== false && Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour');
+ _core.Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride;
if (webchatConfig && webchatConfig.id) {
_core.Configuration.webchat.assign(webchatConfig);
this.webchat = await _models.Webchat.load(webchatConfig.id);
@@ -47,6 +46,31 @@ let Hellotext = /*#__PURE__*/function () {
this.forms.collectExistingFormsOnPage();
}
}
+ }, {
+ key: "mergeWebchatConfig",
+ value: function mergeWebchatConfig(dashboardConfig, localConfig) {
+ return this.deepMergePlainObjects(dashboardConfig, localConfig);
+ }
+ }, {
+ key: "deepMergePlainObjects",
+ value: function deepMergePlainObjects(base, override) {
+ const result = {
+ ...base
+ };
+ Object.entries(override).forEach(([key, value]) => {
+ if (this.isPlainObject(value) && this.isPlainObject(result[key])) {
+ result[key] = this.deepMergePlainObjects(result[key], value);
+ } else {
+ result[key] = value;
+ }
+ });
+ return result;
+ }
+ }, {
+ key: "isPlainObject",
+ value: function isPlainObject(value) {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+ }
/**
* Tracks an action that has happened on the page
diff --git a/lib/hellotext.js b/lib/hellotext.js
index 37b77b5..ba9f8ea 100644
--- a/lib/hellotext.js
+++ b/lib/hellotext.js
@@ -34,7 +34,9 @@ var Hellotext = /*#__PURE__*/function () {
this.forms = new FormCollection();
this.query = new Query();
var businessData = yield this.business.hydrate();
- var webchatConfig = config.webchat === false ? false : _objectSpread(_objectSpread({}, businessData && businessData.webchat || {}), config.webchat || {});
+ var webchatConfig = config.webchat === false ? false : this.mergeWebchatConfig(businessData && businessData.webchat || {}, config.webchat || {});
+ var hasExplicitBehaviourOverride = config.webchat && config.webchat !== false && Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour');
+ Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride;
if (webchatConfig && webchatConfig.id) {
Configuration.webchat.assign(webchatConfig);
this.webchat = yield Webchat.load(webchatConfig.id);
@@ -48,6 +50,31 @@ var Hellotext = /*#__PURE__*/function () {
}
return initialize;
}()
+ }, {
+ key: "mergeWebchatConfig",
+ value: function mergeWebchatConfig(dashboardConfig, localConfig) {
+ return this.deepMergePlainObjects(dashboardConfig, localConfig);
+ }
+ }, {
+ key: "deepMergePlainObjects",
+ value: function deepMergePlainObjects(base, override) {
+ var result = _objectSpread({}, base);
+ Object.entries(override).forEach(_ref => {
+ var [key, value] = _ref;
+ if (this.isPlainObject(value) && this.isPlainObject(result[key])) {
+ result[key] = this.deepMergePlainObjects(result[key], value);
+ } else {
+ result[key] = value;
+ }
+ });
+ return result;
+ }
+ }, {
+ key: "isPlainObject",
+ value: function isPlainObject(value) {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+ }
+
/**
* Tracks an action that has happened on the page
*
diff --git a/lib/models/business.cjs b/lib/models/business.cjs
index edabaa5..2bbed2a 100644
--- a/lib/models/business.cjs
+++ b/lib/models/business.cjs
@@ -20,6 +20,8 @@ function _toPrimitive(input, hint) { if (typeof input !== "object" || input ===
/**
* @typedef {Object} BusinessWebchat
* @property {String} [id] - Dashboard webchat id configured for the business.
+ * @property {Object} [appearance] - Dashboard appearance defaults for the webchat.
+ * @property {Object} [whatsapp] - Dashboard WhatsApp handoff defaults for the webchat.
*/
/**
* Public business metadata returned by `public/businesses/:id`.
diff --git a/lib/models/business.js b/lib/models/business.js
index 5daecb7..25a9175 100644
--- a/lib/models/business.js
+++ b/lib/models/business.js
@@ -17,6 +17,8 @@ import BusinessesAPI from '../api/businesses';
/**
* @typedef {Object} BusinessWebchat
* @property {String} [id] - Dashboard webchat id configured for the business.
+ * @property {Object} [appearance] - Dashboard appearance defaults for the webchat.
+ * @property {Object} [whatsapp] - Dashboard WhatsApp handoff defaults for the webchat.
*/
/**
diff --git a/lib/models/webchat.cjs b/lib/models/webchat.cjs
index c22139d..f83bfce 100644
--- a/lib/models/webchat.cjs
+++ b/lib/models/webchat.cjs
@@ -21,8 +21,33 @@ let Webchat = /*#__PURE__*/function () {
_createClass(Webchat, [{
key: "render",
value: function render() {
+ this.applyBehaviourOverride();
this.containerToAppendTo.appendChild(this.data.html);
}
+ }, {
+ key: "applyBehaviourOverride",
+ value: function applyBehaviourOverride() {
+ if (!_core.Configuration.webchat.hasBehaviourOverride || !_core.Configuration.webchat.behaviour) return;
+ this.data.html.setAttribute('data-hellotext--webchat-behaviour-value', JSON.stringify(this.serializedBehaviour));
+ }
+ }, {
+ key: "serializedBehaviour",
+ get: function () {
+ const behaviour = _core.Configuration.webchat.behaviour;
+ return {
+ trigger: this.serializeTrigger(behaviour.trigger),
+ delay_seconds: behaviour.delaySeconds,
+ first_visit_only: behaviour.firstVisitOnly,
+ once_per_session: behaviour.oncePerSession
+ };
+ }
+ }, {
+ key: "serializeTrigger",
+ value: function serializeTrigger(trigger) {
+ if (trigger === 'onLoad') return 'on_load';
+ if (trigger === 'onClick') return 'on_click';
+ return trigger;
+ }
}, {
key: "containerToAppendTo",
get: function () {
diff --git a/lib/models/webchat.js b/lib/models/webchat.js
index 36df456..b96ab7b 100644
--- a/lib/models/webchat.js
+++ b/lib/models/webchat.js
@@ -16,8 +16,33 @@ var Webchat = /*#__PURE__*/function () {
_createClass(Webchat, [{
key: "render",
value: function render() {
+ this.applyBehaviourOverride();
this.containerToAppendTo.appendChild(this.data.html);
}
+ }, {
+ key: "applyBehaviourOverride",
+ value: function applyBehaviourOverride() {
+ if (!Configuration.webchat.hasBehaviourOverride || !Configuration.webchat.behaviour) return;
+ this.data.html.setAttribute('data-hellotext--webchat-behaviour-value', JSON.stringify(this.serializedBehaviour));
+ }
+ }, {
+ key: "serializedBehaviour",
+ get: function get() {
+ var behaviour = Configuration.webchat.behaviour;
+ return {
+ trigger: this.serializeTrigger(behaviour.trigger),
+ delay_seconds: behaviour.delaySeconds,
+ first_visit_only: behaviour.firstVisitOnly,
+ once_per_session: behaviour.oncePerSession
+ };
+ }
+ }, {
+ key: "serializeTrigger",
+ value: function serializeTrigger(trigger) {
+ if (trigger === 'onLoad') return 'on_load';
+ if (trigger === 'onClick') return 'on_click';
+ return trigger;
+ }
}, {
key: "containerToAppendTo",
get: function get() {
diff --git a/src/api/webchats.js b/src/api/webchats.js
index 5b7b548..89292c0 100644
--- a/src/api/webchats.js
+++ b/src/api/webchats.js
@@ -16,6 +16,8 @@ class WebchatsAPI {
url.searchParams.append(`style[${key}]`, value)
})
+ this.appendWebchatOverrides(url)
+
url.searchParams.append('placement', Configuration.webchat.placement)
const response = await fetch(url, {
@@ -32,6 +34,21 @@ class WebchatsAPI {
return new DOMParser().parseFromString(data.html, 'text/html').querySelector('article')
}
+
+ static appendWebchatOverrides(url) {
+ const { appearance, whatsapp } = Configuration.webchat
+
+ this.appendIfSupplied(url, 'webchat[appearance][header][name]', appearance.header?.name)
+ this.appendIfSupplied(url, 'webchat[appearance][launcher][icon_url]', appearance.launcher?.iconUrl)
+ this.appendIfSupplied(url, 'webchat[handoff][identifier]', whatsapp.number)
+ this.appendIfSupplied(url, 'webchat[handoff][restrict_to_channel]', whatsapp.restrictToChannel)
+ }
+
+ static appendIfSupplied(url, key, value) {
+ if (value === undefined || value === null) return
+
+ url.searchParams.append(key, String(value))
+ }
}
export default WebchatsAPI
diff --git a/src/controllers/mixins/usePopover.js b/src/controllers/mixins/usePopover.js
index b786d57..08e4430 100644
--- a/src/controllers/mixins/usePopover.js
+++ b/src/controllers/mixins/usePopover.js
@@ -4,12 +4,14 @@ import { Configuration } from '../../core'
export const usePopover = controller => {
Object.assign(controller, {
show() {
+ this.cancelBehaviourOpen?.()
this.openValue = true
},
hide() {
this.openValue = false
},
toggle() {
+ this.cancelBehaviourOpen?.()
this.openValue = !this.openValue
},
setupFloatingUI({ trigger, popover, strategy }) {
diff --git a/src/controllers/webchat/emoji_picker_controller.js b/src/controllers/webchat/emoji_picker_controller.js
index fb0b304..8cfa8a5 100644
--- a/src/controllers/webchat/emoji_picker_controller.js
+++ b/src/controllers/webchat/emoji_picker_controller.js
@@ -1,11 +1,6 @@
import { autoPlacement, offset, shift } from '@floating-ui/dom'
import { Controller } from '@hotwired/stimulus'
-import en from '@emoji-mart/data/i18n/en.json'
-import es from '@emoji-mart/data/i18n/es.json'
-
-import { Picker } from 'emoji-mart'
-
import { usePopover } from '../mixins/usePopover'
export default class extends Controller {
@@ -22,10 +17,15 @@ export default class extends Controller {
initialize() {
this.onEmojiSelect = this.onEmojiSelect.bind(this)
+ this.pickerLoaded = false
+ this.pickerLoadPromise = null
+ this.connected = false
super.initialize()
}
connect() {
+ this.connected = true
+
usePopover(this)
this.setupFloatingUI({
@@ -34,12 +34,12 @@ export default class extends Controller {
strategy: 'absolute',
})
- this.popoverTarget.appendChild(this.pickerObject)
-
super.connect()
}
disconnect() {
+ this.connected = false
+ this.pickerLoadPromise = null
this.floatingUICleanup()
super.disconnect()
}
@@ -58,7 +58,44 @@ export default class extends Controller {
}
}
- get pickerObject() {
+ async onPopoverOpened() {
+ await this.loadPicker()
+ }
+
+ async loadPicker() {
+ if (this.pickerLoaded) return
+
+ this.pickerLoadPromise ||= this.loadPickerDependencies()
+
+ const { Picker, i18n } = await this.pickerLoadPromise
+
+ if (!this.connected || this.pickerLoaded) return
+
+ this.popoverTarget.appendChild(this.buildPicker(Picker, i18n))
+ this.pickerLoaded = true
+ }
+
+ async loadPickerDependencies() {
+ const [pickerModule, i18nModule] = await Promise.all([
+ import(/* webpackChunkName: "webchat-emoji" */ 'emoji-mart'),
+ this.loadI18n(),
+ ])
+
+ return {
+ Picker: pickerModule.Picker,
+ i18n: i18nModule.default || i18nModule,
+ }
+ }
+
+ loadI18n() {
+ if (Hellotext.business.locale === 'es') {
+ return import(/* webpackChunkName: "webchat-emoji-es" */ '@emoji-mart/data/i18n/es.json')
+ }
+
+ return import(/* webpackChunkName: "webchat-emoji-en" */ '@emoji-mart/data/i18n/en.json')
+ }
+
+ buildPicker(Picker, i18n) {
return new Picker({
onEmojiSelect: this.onEmojiSelect,
theme: 'light',
@@ -67,7 +104,7 @@ export default class extends Controller {
skinTonePosition: 'none',
emojiSize: this.sizeValue,
perLine: this.perLineValue,
- i18n: Hellotext.business.locale === 'es' ? es : en,
+ i18n,
})
}
diff --git a/src/controllers/webchat/useBehaviour.js b/src/controllers/webchat/useBehaviour.js
new file mode 100644
index 0000000..fd47b0b
--- /dev/null
+++ b/src/controllers/webchat/useBehaviour.js
@@ -0,0 +1,81 @@
+// This mixin owns automatic opening only. The webchat controller still owns the
+// Stimulus lifecycle and `usePopover` still owns the mechanics of opening and
+// closing the popover. Keeping this policy here gives the configured behaviour
+// value one place to translate into timers, storage gates, and cleanup.
+export const useBehaviour = controller => {
+ Object.assign(controller, {
+ // Called from `connect` after the controller has wired channels, targets,
+ // and popover state. Even a zero-second delay goes through a timeout so the
+ // automatic open happens after the mount pass rather than during setup.
+ scheduleBehaviourOpen() {
+ if (!this.shouldAutoOpenFromBehaviour()) return
+
+ const delay = Number(this.behaviourValue.delay_seconds || 0) * 1000
+
+ this.behaviourOpenTimeout = window.setTimeout(() => {
+ this.behaviourOpenTimeout = null
+
+ if (this.openValue) return
+
+ this.openValue = true
+ this.markBehaviourAutoOpened()
+ }, delay)
+ },
+
+ // This is the shared escape hatch for lifecycle teardown and manual user
+ // intent. `disconnect` calls it to avoid orphaned timers; `usePopover`
+ // calls it before show/toggle so a delayed behaviour cannot reopen the chat
+ // or consume gates after the user has already acted.
+ cancelBehaviourOpen() {
+ window.clearTimeout(this.behaviourOpenTimeout)
+ this.behaviourOpenTimeout = null
+ },
+
+ // Eligibility is deliberately read-only. A visit should only be marked as
+ // auto-opened after the timer opens the widget, not when we merely discover
+ // that it would be allowed. That keeps blocked, cancelled, and manually
+ // opened visits from being counted as behaviour-driven opens.
+ shouldAutoOpenFromBehaviour() {
+ const behaviour = this.behaviourValue
+
+ if (!behaviour || behaviour.trigger !== 'on_load') return false
+
+ if (behaviour.first_visit_only && localStorage.getItem(this.firstVisitKey())) {
+ return false
+ }
+
+ if (behaviour.once_per_session && sessionStorage.getItem(this.sessionKey())) {
+ return false
+ }
+
+ return true
+ },
+
+ // The gates are independent because they answer different product
+ // questions. `first_visit_only` is a long-lived visitor gate in
+ // localStorage; `once_per_session` is intentionally softer and resets with
+ // sessionStorage.
+ markBehaviourAutoOpened() {
+ if (this.behaviourValue.first_visit_only) {
+ localStorage.setItem(this.firstVisitKey(), '1')
+ }
+
+ if (this.behaviourValue.once_per_session) {
+ sessionStorage.setItem(this.sessionKey(), '1')
+ }
+ },
+
+ // Records that this widget has already used its long-lived first-visit
+ // automatic open. This is the durable gate behind `first_visit_only`.
+ firstVisitKey() {
+ return `hellotext--webchat--${this.idValue}--auto-opened`
+ },
+
+ // Records that this widget has already used its automatic open during the
+ // current browser session. This is the shorter-lived gate behind
+ // `once_per_session`.
+ sessionKey() {
+ return `hellotext--webchat--${this.idValue}--auto-opened-session`
+ },
+ })
+}
diff --git a/src/controllers/webchat/useOpeningSequence.js b/src/controllers/webchat/useOpeningSequence.js
new file mode 100644
index 0000000..397e8d0
--- /dev/null
+++ b/src/controllers/webchat/useOpeningSequence.js
@@ -0,0 +1,127 @@
+// Opening sequence markup is already present in the document; this mixin only
+// manages when those staged messages become visible and which ids should travel
+// with the first customer message. It never creates a conversation or posts the
+// staged messages by itself.
+export const useOpeningSequence = controller => {
+ Object.assign(controller, {
+ // Called from `connect` to prepare lifecycle state before the webchat opens.
+ // The sequence may not exist on every widget, so the rest of the runtime can
+ // safely call these helpers even when there are no sequence targets.
+ setupOpeningSequence() {
+ this.openingSequenceStarted = false
+ this.openingSequenceCancelled = false
+ this.openingSequenceTimeout = null
+ this.openingSequenceMessages = []
+ this.revealedOpeningSequenceMessageIds = []
+ },
+
+ // Disconnect shares the same cancellation path as a user send. Both cases
+ // must stop pending timers so hidden staged messages cannot reveal later.
+ teardownOpeningSequence() {
+ this.cancelOpeningSequence()
+ },
+
+ // The sequence is a first-conversation affordance only. A present
+ // conversation id means the transcript already exists, so staged messages
+ // should remain untouched.
+ startOpeningSequence() {
+ this.openingSequenceMessages = Array.from(this.openingSequenceMessageTargets || [])
+
+ if (!this.openingSequenceCanStart()) return
+
+ this.openingSequenceStarted = true
+ this.openingSequenceCancelled = false
+ this.revealedOpeningSequenceMessageIds = []
+ this.playOpeningSequenceMessageAt(0)
+ },
+
+ openingSequenceCanStart() {
+ return (
+ !this.conversationIdValue &&
+ this.hasOpeningSequenceTarget &&
+ this.openingSequenceMessages.length > 0 &&
+ !this.openingSequenceStarted
+ )
+ },
+
+ // Each staged message owns the delay before it appears. A zero-second delay
+ // still uses a timeout so reveal work stays outside the popover-open call
+ // stack and cancellation has one consistent path.
+ playOpeningSequenceMessageAt(index) {
+ const message = this.openingSequenceMessages[index]
+
+ if (!message) return
+
+ const delay = this.openingSequenceMessageDelay(message) * 1000
+
+ this.openingSequenceTimeout = window.setTimeout(() => {
+ this.openingSequenceTimeout = null
+
+ if (this.openingSequenceCancelled) return
+
+ this.revealOpeningSequenceMessage(message)
+ this.playOpeningSequenceMessageAt(index + 1)
+ }, delay)
+ },
+
+ // Revealing moves the staged node into the live message list. The message
+ // markup itself is preserved; JS only relocates it, unhides it, and remembers
+ // the hashed id if the visitor actually saw it.
+ revealOpeningSequenceMessage(message) {
+ this.messagesContainerTarget.insertBefore(message, this.messageTemplateTarget)
+ message.hidden = false
+ this.recordOpeningSequenceMessage(message)
+ this.scrollOpeningSequenceToBottom()
+ },
+
+ recordOpeningSequenceMessage(message) {
+ const id = message.dataset.openingSequenceMessageId
+
+ if (!id || this.revealedOpeningSequenceMessageIds.includes(id)) return
+
+ this.revealedOpeningSequenceMessageIds.push(id)
+ },
+
+ openingSequenceMessageDelay(message) {
+ const delay = Number(message.dataset.delaySeconds || 0)
+
+ return Number.isFinite(delay) ? delay : 0
+ },
+
+ scrollOpeningSequenceToBottom() {
+ if (!this.messagesContainerTarget.scroll) return
+
+ this.messagesContainerTarget.scroll({
+ top: this.messagesContainerTarget.scrollHeight,
+ behavior: 'smooth',
+ })
+ },
+
+ cancelOpeningSequence() {
+ this.openingSequenceCancelled = true
+
+ if (this.openingSequenceTimeout === null || this.openingSequenceTimeout === undefined) return
+
+ window.clearTimeout(this.openingSequenceTimeout)
+ this.openingSequenceTimeout = null
+ },
+
+ // Customer sends promote only what was actually revealed. Calling this also
+ // interrupts pending reveals so the payload cannot include later messages.
+ appendOpeningSequenceMessageIds(formData) {
+ this.cancelOpeningSequence()
+
+ const ids = this.revealedOpeningSequenceMessageIds || []
+
+ ids.forEach(id => {
+ formData.append('message[opening_sequence_message_ids][]', id)
+ })
+ },
+
+ // Clear after a successful customer send. Failed sends keep the ids available
+ // so retrying the first message can still promote the revealed sequence.
+ clearRevealedOpeningSequenceMessageIds() {
+ this.revealedOpeningSequenceMessageIds = []
+ },
+ })
+}
diff --git a/src/controllers/webchat/useTeaser.js b/src/controllers/webchat/useTeaser.js
new file mode 100644
index 0000000..2ade203
--- /dev/null
+++ b/src/controllers/webchat/useTeaser.js
@@ -0,0 +1,192 @@
+// The teaser markup is already present in the document; this mixin only manages
+// the runtime around it. Keeping the teaser policy here gives the controller one
+// place to delegate click-to-open, the pre-conversation presentation, and timer
+// cleanup. Message sending stays on the controller because it owns the API flow
+// and optimistic customer bubble insertion.
+export const useTeaser = controller => {
+ Object.assign(controller, {
+ // Called from `connect` after `usePopover` has given the controller `show`.
+ // The teaser is optional, so setup first prepares reusable lifecycle state,
+ // wires the click surface when present, and then lets eligibility decide
+ // whether this session may still show the presentation.
+ setupTeaser() {
+ this.teaserCycleTimeout = null
+ this.teaserMessages = []
+ this.boundOnTeaserClick = this.boundOnTeaserClick || this.onTeaserClick.bind(this)
+
+ if (!this.hasTeaserTarget) return
+
+ this.teaserTarget.addEventListener('click', this.boundOnTeaserClick)
+ this.startTeaserPresentation()
+ },
+
+ // This is the shared teardown path for Stimulus disconnect. Cycling timers
+ // and DOM listeners both outlive the current call stack, so they need to be
+ // cancelled explicitly when the rendered widget leaves the page.
+ teardownTeaser() {
+ this.stopTeaserCycle()
+
+ if (this.hasTeaserTarget && this.boundOnTeaserClick) {
+ this.teaserTarget.removeEventListener('click', this.boundOnTeaserClick)
+ }
+ },
+
+ // The current DOM is the source of truth. Each setup pass collects the
+ // rendered teaser messages from the teaser surface itself, which keeps the
+ // target list small and avoids carrying stale nodes between presentations.
+ collectTeaserMessages() {
+ if (!this.hasTeaserTarget) return []
+
+ return Array.from(this.teaserTarget.querySelectorAll('[data-teaser-message]'))
+ },
+
+ // The teaser is a one-time, pre-conversation presentation. It can run while
+ // the popover is closed, but any active conversation signal makes the teaser
+ // ineligible for the rest of this browser session.
+ startTeaserPresentation() {
+ this.stopTeaserCycle()
+ this.teaserMessages = this.collectTeaserMessages()
+
+ if (!this.hasTeaserTarget) return
+
+ if (this.teaserMessages.length === 0) {
+ this.hideTeaser()
+ return
+ }
+
+ if (this.openValue) {
+ this.dismissTeaserForSession()
+ return
+ }
+
+ if (this.conversationIdValue || this.hasRenderedConversationMessages()) {
+ this.dismissTeaserForSession()
+ return
+ }
+
+ if (this.teaserSeenForSession()) {
+ this.hideTeaser()
+ return
+ }
+
+ this.teaserTarget.classList.remove('invisible')
+ this.showTeaserMessage(0)
+
+ if (this.teaserMessages.length < 2) return
+
+ this.scheduleNextTeaserMessage(0)
+ },
+
+ // Delays belong to the currently visible message, so each teaser controls
+ // how long it remains on screen before the next one replaces it. The
+ // presentation stops after the last message instead of looping forever.
+ scheduleNextTeaserMessage(currentIndex) {
+ const nextIndex = currentIndex + 1
+
+ if (nextIndex >= this.teaserMessages.length) return
+
+ const currentMessage = this.teaserMessages[currentIndex]
+ const delay = this.teaserPresentationDelay(currentMessage)
+
+ this.teaserCycleTimeout = window.setTimeout(() => {
+ this.teaserCycleTimeout = null
+ this.showTeaserMessage(nextIndex)
+ this.scheduleNextTeaserMessage(nextIndex)
+ }, delay)
+ },
+
+ // Visibility is managed with the existing `hidden` class for initially
+ // concealed teaser messages. This keeps JS from restructuring teaser markup.
+ showTeaserMessage(index) {
+ this.teaserMessages.forEach((message, messageIndex) => {
+ message.classList.toggle('hidden', messageIndex !== index)
+ })
+ },
+
+ // Safe to call even when no timer exists. Lifecycle hooks call this before
+ // starting, hiding, or tearing down the teaser so only one cycle can be alive.
+ stopTeaserCycle() {
+ if (this.teaserCycleTimeout === null || this.teaserCycleTimeout === undefined) return
+
+ window.clearTimeout(this.teaserCycleTimeout)
+ this.teaserCycleTimeout = null
+ },
+
+ // Invalid or missing delay values should not break teaser rendering. Treat
+ // them as zero, then use a small minimum so zero-delay presentations advance
+ // deliberately instead of flashing through every message in one frame.
+ teaserMessageDelay(message) {
+ const delay = Number(message.dataset.delaySeconds || 0)
+
+ return Number.isFinite(delay) ? delay : 0
+ },
+
+ teaserPresentationDelay(message) {
+ return Math.max(this.teaserMessageDelay(message) * 1000, 250)
+ },
+
+ // A rendered transcript means the visitor is no longer in the pre-conversation
+ // state. The hidden template is deliberately excluded because it is not a live
+ // customer-visible message.
+ hasRenderedConversationMessages() {
+ let messages = []
+
+ try {
+ messages = Array.from(this.messageTargets || [])
+ } catch (_error) {
+ messages = []
+ }
+
+ return messages.some(message => message !== this.messageTemplateTarget)
+ },
+
+ teaserSeenKey() {
+ return `hellotext:webchat:${this.idValue || this.element.id}:teaser-seen`
+ },
+
+ teaserSeenForSession() {
+ try {
+ return window.sessionStorage.getItem(this.teaserSeenKey()) === 'true'
+ } catch (_error) {
+ return false
+ }
+ },
+
+ markTeaserSeenForSession() {
+ try {
+ window.sessionStorage.setItem(this.teaserSeenKey(), 'true')
+ } catch (_error) {
+ // Storage can be unavailable in locked-down browsers. The in-memory hide
+ // still keeps the active controller from showing the teaser again.
+ }
+ },
+
+ // Opening or sending ends the pre-conversation window for this browser
+ // session. Incoming message teasers are separate ephemeral content, so they
+ // use `hideTeaser`/`updateMessageTeaser` without writing this session flag.
+ dismissTeaserForSession() {
+ this.markTeaserSeenForSession()
+ this.hideTeaser()
+ },
+
+ // Stopping the timer before hiding prevents queued presentation steps from
+ // flipping hidden messages after the teaser surface has been dismissed.
+ hideTeaser() {
+ this.stopTeaserCycle()
+
+ if (this.hasTeaserTarget) {
+ this.teaserTarget.classList.add('invisible')
+ }
+ },
+
+ // A teaser surface click is a user request to open chat, but links inside the
+ // teaser already have native browser behavior. Leaving anchors alone keeps
+ // external URLs and `tel:` actions working without extra JS.
+ onTeaserClick(event) {
+ if (event.target.closest('a')) return
+
+ this.dismissTeaserForSession()
+ this.show()
+ },
+ })
+}
diff --git a/src/controllers/webchat_controller.js b/src/controllers/webchat_controller.js
index 3bdaf0c..a613a42 100644
--- a/src/controllers/webchat_controller.js
+++ b/src/controllers/webchat_controller.js
@@ -6,10 +6,12 @@ import WebchatChannel from '../channels/webchat_channel'
import Hellotext from '../hellotext'
import { Locale } from '../core'
-import { Webchat as WebchatConfiguration, behaviors } from '../core/configuration/webchat'
+import { Webchat as WebchatConfiguration, modes } from '../core/configuration/webchat'
-import { LogoBuilder } from '../builders/logo_builder'
import { usePopover } from './mixins/usePopover'
+import { useBehaviour } from './webchat/useBehaviour'
+import { useOpeningSequence } from './webchat/useOpeningSequence'
+import { useTeaser } from './webchat/useTeaser'
export default class extends Controller {
static values = {
@@ -28,6 +30,8 @@ export default class extends Controller {
padding: { type: Number, default: 24 },
optimisticTypingIndicatorWait: { type: Number, default: 1000 }, // 1 second,
teaser: Object,
+ messageTeaser: String,
+ behaviour: Object,
}
static classes = ['fadeOut']
@@ -53,6 +57,8 @@ export default class extends Controller {
'typingIndicator',
'typingIndicatorTemplate',
'teaser',
+ 'openingSequence',
+ 'openingSequenceMessage',
]
initialize() {
@@ -65,6 +71,7 @@ export default class extends Controller {
)
this.files = []
+ this.messageIds = new Set()
this.onMessageReceived = this.onMessageReceived.bind(this)
this.onMessageReaction = this.onMessageReaction.bind(this)
@@ -79,10 +86,10 @@ export default class extends Controller {
}
connect() {
+ useBehaviour(this)
usePopover(this)
-
- this.popoverTarget.classList.add(...WebchatConfiguration.classes)
- this.triggerTarget.classList.add(...WebchatConfiguration.triggerClasses)
+ useOpeningSequence(this)
+ useTeaser(this)
this.setupFloatingUI({ trigger: this.triggerTarget, popover: this.popoverTarget })
@@ -94,6 +101,9 @@ export default class extends Controller {
})
}
+ this.setupTeaser()
+ this.setupOpeningSequence()
+
this.webChatChannel.onMessage(this.onMessageReceived)
this.webChatChannel.onTypingStart(this.onTypingStart)
@@ -101,21 +111,22 @@ export default class extends Controller {
this.messagesContainerTarget.addEventListener('scroll', this.onScroll)
- if (!Hellotext.business.features.white_label) {
- this.toolbarTarget.appendChild(LogoBuilder.build())
- }
-
if (this.shouldOpenOnMount) {
this.openValue = true
}
Hellotext.eventEmitter.dispatch('webchat:mounted')
this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent)
+ this.scheduleBehaviourOpen()
super.connect()
}
disconnect() {
+ this.cancelBehaviourOpen()
+ this.teardownTeaser()
+ this.teardownOpeningSequence()
+
this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent)
this.messagesContainerTarget.removeEventListener('scroll', this.onScroll)
@@ -283,7 +294,7 @@ export default class extends Controller {
image.src = attachmentUrl
image.style.display = 'block'
- element.querySelector('[data-attachment-container]').appendChild(image)
+ this.messageAttachmentsContainer(element)?.appendChild(image)
})
}
@@ -301,7 +312,7 @@ export default class extends Controller {
onClickOutside(event) {
if (
- WebchatConfiguration.behaviour === behaviors.POPOVER &&
+ WebchatConfiguration.mode === modes.POPOVER &&
this.openValue &&
event.target.nodeType &&
this.element.contains(event.target) === false
@@ -320,6 +331,7 @@ export default class extends Controller {
onPopoverOpened() {
this.popoverTarget.classList.remove(...this.fadeOutClasses)
+ this.dismissTeaserForSession?.()
if (!this.onMobile) {
this.inputTarget.focus()
@@ -339,10 +351,12 @@ export default class extends Controller {
Hellotext.eventEmitter.dispatch('webchat:opened')
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'opened')
- if (this.hasTeaserTarget) {
- this.teaserTarget.classList.add('hidden')
+ if (this.messageTeaserValue) {
+ this.messageTeaserValue = null
}
+ this.startOpeningSequence()
+
if (this.unreadCounterTarget.style.display === 'none') return
this.unreadCounterTarget.style.display = 'none'
@@ -354,10 +368,6 @@ export default class extends Controller {
onPopoverClosed() {
Hellotext.eventEmitter.dispatch('webchat:closed')
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'closed')
-
- if (this.hasTeaserTarget && this.teaserValue.body) {
- this.teaserTarget.classList.remove('hidden')
- }
}
onMessageReaction(message) {
@@ -385,7 +395,11 @@ export default class extends Controller {
}
onMessageReceived(message) {
- const { id, body, attachments } = message
+ const { id, body, attachments, teaser } = message
+
+ if (!this.claimMessageId(id)) return
+
+ this.hideTeaser?.()
if (message.carousel) {
return this.insertCarouselMessage(message)
@@ -398,6 +412,8 @@ export default class extends Controller {
element.style.display = 'flex'
element.querySelector('[data-body]').innerHTML = div.innerHTML
+
+ element.setAttribute('data-id', id)
element.setAttribute('data-hellotext--webchat-target', 'message')
if (attachments) {
@@ -406,7 +422,7 @@ export default class extends Controller {
image.src = attachmentUrl
image.style.display = 'block'
- element.querySelector('[data-attachment-container]').appendChild(image)
+ this.messageAttachmentsContainer(element)?.appendChild(image)
})
}
@@ -420,21 +436,45 @@ export default class extends Controller {
element.scrollIntoView({ behavior: 'smooth' })
+ this.updateMessageTeaser(teaser)
+
if (this.openValue) {
this.messagesAPI.markAsSeen(id)
return
}
- this.unreadCounterTarget.style.display = 'flex'
+ this.incrementUnreadCounter()
+ }
- const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount
+ // TCP broadcasts may deliver the same incoming message twice before Stimulus
+ // has connected the appended message target. Claiming the id in memory closes
+ // that window, while the target check still drops messages rendered earlier.
+ claimMessageId(id) {
+ const messageTargets = this.messageTargets || []
+
+ if (this.messageIds.has(id)) return false
+
+ this.messageIds.add(id)
+
+ return !messageTargets.some(element => element.dataset.id === id)
+ }
+
+ updateMessageTeaser(teaser) {
+ this.messageTeaserValue = teaser
+
+ if (this.messageTeaserValue && this.hasTeaserTarget) {
+ this.teaserTarget.innerHTML = this.messageTeaserValue
+ this.teaserTarget.classList.toggle('invisible', this.openValue)
+ }
}
insertCarouselMessage(message) {
const html = message.html
const element = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild
+ element.setAttribute('data-id', message.id)
+ element.setAttribute('data-hellotext--webchat-target', 'message')
+
this.clearTypingIndicator()
this.messagesContainerTarget.appendChild(element)
@@ -445,14 +485,14 @@ export default class extends Controller {
body: element.querySelector('[data-body]')?.innerText || '',
})
+ this.updateMessageTeaser(message.teaser)
+
if (this.openValue) {
this.messagesAPI.markAsSeen(message.id)
return
}
- this.unreadCounterTarget.style.display = 'flex'
- const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount
+ this.incrementUnreadCounter()
}
resizeInput() {
@@ -469,6 +509,8 @@ export default class extends Controller {
}
async sendQuickReplyMessage({ detail: { id, product, buttonId, body, cardElement } }) {
+ this.dismissTeaserForSession?.()
+
const formData = new FormData()
formData.append('message[body]', body)
@@ -478,6 +520,7 @@ export default class extends Controller {
formData.append('session', Hellotext.session)
formData.append('locale', Locale.toString())
+ this.appendOpeningSequenceMessageIds(formData)
const element = this.buildMessageElement()
const attachment = cardElement.querySelector('img')?.cloneNode(true)
@@ -488,7 +531,7 @@ export default class extends Controller {
attachment.removeAttribute('width')
attachment.removeAttribute('height')
- element.querySelector('[data-attachment-container]').appendChild(attachment)
+ this.messageAttachmentsContainer(element)?.appendChild(attachment)
}
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
@@ -521,6 +564,7 @@ export default class extends Controller {
const data = await response.json()
this.dispatch('set:id', { target: element, detail: data.id })
+ this.clearRevealedOpeningSequenceMessageIds()
const message = {
id: data.id,
@@ -535,9 +579,94 @@ export default class extends Controller {
Hellotext.eventEmitter.dispatch('webchat:message:sent', message)
}
- async sendMessage(e) {
+ async sendTeaserQuickReply(event) {
+ event.preventDefault()
+ event.stopPropagation()
+
+ const button = event.currentTarget
+ const value = (button.dataset.value || '').trim()
+ const label = [button.dataset.text, button.textContent]
+ .map(text => (text || '').trim())
+ .find(text => text.length > 0)
+ const text = value || label
+
+ if (!text) return
+
+ this.dismissTeaserForSession?.()
+ this.show()
+
+ const buttonType = (button.dataset.type || '').trim() || 'quick_reply'
const formData = new FormData()
+ formData.append('message[body]', text)
+ formData.append('session', Hellotext.session)
+ formData.append('locale', Locale.toString())
+ this.appendOpeningSequenceMessageIds(formData)
+
+ const element = this.buildMessageElement()
+
+ element.querySelector('[data-body]').innerText = text
+
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget)
+ } else {
+ this.messagesContainerTarget.appendChild(element)
+ }
+
+ element.scrollIntoView({ behavior: 'smooth' })
+
+ this.broadcastChannel.postMessage({
+ type: 'message:sent',
+ element: element.outerHTML,
+ })
+
+ if (!this.typingIndicatorVisible) {
+ clearTimeout(this.optimisticTypingTimeout)
+ this.optimisticTypingTimeout = setTimeout(() => {
+ this.showOptimisticTypingIndicator()
+ }, this.optimisticTypingIndicatorWaitValue)
+ }
+
+ const response = await this.messagesAPI.create(formData)
+
+ if (response.failed) {
+ clearTimeout(this.optimisticTypingTimeout)
+
+ this.broadcastChannel.postMessage({
+ type: 'message:failed',
+ id: element.id,
+ })
+
+ return element.classList.add('failed')
+ }
+
+ const data = await response.json()
+ element.setAttribute('data-id', data.id)
+ this.clearRevealedOpeningSequenceMessageIds()
+
+ Hellotext.eventEmitter.dispatch('webchat:message:sent', {
+ id: data.id,
+ body: text,
+ attachments: [],
+ type: 'quick_reply',
+ teaser: {
+ text: label || text,
+ value: value || text,
+ type: buttonType,
+ },
+ })
+
+ if (data.conversation && data.conversation !== this.conversationIdValue) {
+ this.conversationIdValue = data.conversation
+ this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)
+ }
+
+ if (this.typingIndicatorVisible) {
+ this.resetTypingIndicatorTimer()
+ }
+ }
+
+ async sendMessage(e) {
const message = {
body: this.inputTarget.value,
attachments: this.files,
@@ -551,6 +680,10 @@ export default class extends Controller {
return
}
+ this.dismissTeaserForSession?.()
+
+ const formData = new FormData()
+
if (this.inputTarget.value.trim().length > 0) {
formData.append('message[body]', this.inputTarget.value)
} else {
@@ -563,6 +696,7 @@ export default class extends Controller {
formData.append('session', Hellotext.session)
formData.append('locale', Locale.toString())
+ this.appendOpeningSequenceMessageIds(formData)
const element = this.buildMessageElement()
@@ -576,7 +710,7 @@ export default class extends Controller {
if (attachments.length > 0) {
attachments.forEach(attachment => {
- element.querySelector('[data-attachment-container]').appendChild(attachment.cloneNode(true))
+ this.messageAttachmentsContainer(element)?.appendChild(attachment.cloneNode(true))
})
}
@@ -632,6 +766,7 @@ export default class extends Controller {
const data = await response.json()
element.setAttribute('data-id', data.id)
message.id = data.id
+ this.clearRevealedOpeningSequenceMessageIds()
Hellotext.eventEmitter.dispatch('webchat:message:sent', message)
@@ -662,6 +797,17 @@ export default class extends Controller {
return element
}
+ messageAttachmentsContainer(element) {
+ return element.querySelector('[data-attachments-container], [data-attachment-container]')
+ }
+
+ incrementUnreadCounter() {
+ this.unreadCounterTarget.style.display = 'flex'
+
+ const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1
+ this.unreadCounterTarget.innerText = Math.min(unreadCount, 9)
+ }
+
openAttachment() {
this.attachmentInputTarget.click()
}
diff --git a/src/core/configuration/webchat.js b/src/core/configuration/webchat.js
index 091cb4f..a3d6b17 100644
--- a/src/core/configuration/webchat.js
+++ b/src/core/configuration/webchat.js
@@ -28,13 +28,13 @@ const strategies = {
}
/**
- * @typedef {'modal' | 'popover'} Behaviour
+ * @typedef {'modal' | 'popover'} Mode
*/
/**
- * @enum {Behaviour}
+ * @enum {Mode}
*/
-const behaviors = {
+const modes = {
MODAL: 'modal',
POPOVER: 'popover',
}
@@ -46,6 +46,20 @@ const behaviors = {
* @property {string} typography - The typography style (e.g., font family).
*/
+/**
+ * @typedef {Object} Appearance
+ * @property {Object} [header] - Header appearance overrides.
+ * @property {string} [header.name] - Business name shown in the webchat header.
+ * @property {Object} [launcher] - Launcher appearance overrides.
+ * @property {string} [launcher.iconUrl] - Image URL used for the launcher icon.
+ */
+
+/**
+ * @typedef {Object} WhatsApp
+ * @property {string} [number] - WhatsApp number used by the handoff configuration.
+ * @property {boolean} [restrictToChannel] - Whether handoff should be restricted to the configured channel.
+ */
+
/**
* @class Webchat
* @classdesc
@@ -53,10 +67,11 @@ const behaviors = {
* @property {String} id - the id of the webchat.
* @property {String} container - the container to append the webchat to, defaults to 'body'
* @property {Placement} placement - the placement of the webchat within the container, defaults to "bottom-right".
- * @property {String} classes - additional classes to apply to the webchat popup.
- * @property {String} triggerClasses - additional classes to apply to the webchat popup trigger.
- * @property {Behaviour} behaviour - the behaviour of the webchat, defaults to 'popover'.
+ * @property {Mode} mode - how the webchat behaves while open, defaults to 'popover'.
+ * @property {Object} behaviour - runtime opening behaviour for the webchat.
* @property {Style} style - the style of the webchat.
+ * @property {Appearance} appearance - appearance overrides.
+ * @property {WhatsApp} whatsapp - WhatsApp handoff overrides.
* @property {Strategy} strategy - the strategy used to position the webchat. Defaults to 'absolute'
*/
@@ -64,10 +79,12 @@ class Webchat {
static _id
static _container = 'body'
static _placement = 'bottom-right'
- static _classes = []
- static _triggerClasses = []
static _style = {}
- static _behaviour = behaviors.POPOVER
+ static _appearance = {}
+ static _whatsapp = {}
+ static _mode = modes.POPOVER
+ static _behaviour = null
+ static _hasBehaviourOverride = false
static _strategy = null
static set container(value) {
@@ -90,38 +107,6 @@ class Webchat {
return this._placement
}
- static set classes(value) {
- if (!Array.isArray(value) && typeof value !== 'string') {
- throw new Error('classes must be an array or a string')
- }
-
- this._classes = value
- }
-
- static get classes() {
- if (typeof this._classes === 'string') {
- return this._classes.split(',').map(c => c.trim())
- } else {
- return this._classes
- }
- }
-
- static set triggerClasses(value) {
- if (!Array.isArray(value) && typeof value !== 'string') {
- throw new Error('triggerClasses must be an array or a string')
- }
-
- this._triggerClasses = value
- }
-
- static get triggerClasses() {
- if (typeof this._triggerClasses === 'string') {
- return this._triggerClasses.split(',').map(c => c.trim())
- } else {
- return this._triggerClasses
- }
- }
-
static set id(value) {
this._id = value
}
@@ -160,18 +145,113 @@ class Webchat {
this._style = value
}
+ static get appearance() {
+ return this._appearance
+ }
+
+ static set appearance(value) {
+ if (!this.isPlainObject(value)) {
+ throw new Error('Appearance must be an object')
+ }
+
+ Object.entries(value).forEach(([key, nestedValue]) => {
+ if (!['header', 'launcher'].includes(key)) {
+ throw new Error(`Invalid appearance property: ${key}`)
+ }
+
+ if (!this.isPlainObject(nestedValue)) {
+ throw new Error(`Appearance ${key} must be an object`)
+ }
+
+ Object.entries(nestedValue).forEach(([nestedKey, propertyValue]) => {
+ if (key === 'header' && nestedKey !== 'name') {
+ throw new Error(`Invalid appearance header property: ${nestedKey}`)
+ }
+
+ if (key === 'launcher' && nestedKey !== 'iconUrl') {
+ throw new Error(`Invalid appearance launcher property: ${nestedKey}`)
+ }
+
+ if (propertyValue == null) {
+ return
+ }
+
+ if (typeof propertyValue !== 'string') {
+ throw new Error(`Invalid appearance ${key}.${nestedKey} value: ${propertyValue}`)
+ }
+ })
+ })
+
+ this._appearance = value
+ }
+
+ static get whatsapp() {
+ return this._whatsapp
+ }
+
+ static set whatsapp(value) {
+ if (!this.isPlainObject(value)) {
+ throw new Error('WhatsApp must be an object')
+ }
+
+ Object.entries(value).forEach(([key, nestedValue]) => {
+ if (!['number', 'restrictToChannel'].includes(key)) {
+ throw new Error(`Invalid WhatsApp property: ${key}`)
+ }
+
+ if (nestedValue == null) {
+ return
+ }
+
+ if (key === 'number' && typeof nestedValue !== 'string') {
+ throw new Error(`Invalid WhatsApp number value: ${nestedValue}`)
+ }
+
+ if (key === 'restrictToChannel' && typeof nestedValue !== 'boolean') {
+ throw new Error(`Invalid WhatsApp restrictToChannel value: ${nestedValue}`)
+ }
+ })
+
+ this._whatsapp = value
+ }
+
+ static get mode() {
+ return this._mode
+ }
+
+ static set mode(value) {
+ if (!Object.values(modes).includes(value)) {
+ throw new Error(`Invalid mode value: ${value}`)
+ }
+
+ this._mode = value
+ }
+
static get behaviour() {
return this._behaviour
}
static set behaviour(value) {
- if (!Object.values(behaviors).includes(value)) {
+ if (value == null) {
+ this._behaviour = value
+ return
+ }
+
+ if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`Invalid behaviour value: ${value}`)
}
this._behaviour = value
}
+ static get hasBehaviourOverride() {
+ return this._hasBehaviourOverride
+ }
+
+ static set behaviourOverride(value) {
+ this._hasBehaviourOverride = !!value
+ }
+
static get strategy() {
if (this._strategy) {
return this._strategy
@@ -204,6 +284,10 @@ class Webchat {
/^rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3},?\s*(0|1|0?\.\d+)?\s*\)$/.test(value)
)
}
+
+ static isPlainObject(value) {
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
+ }
}
-export { behaviors, Webchat }
+export { modes, Webchat }
diff --git a/src/hellotext.js b/src/hellotext.js
index d79d130..61fd6c2 100644
--- a/src/hellotext.js
+++ b/src/hellotext.js
@@ -28,10 +28,16 @@ class Hellotext {
this.query = new Query()
const businessData = await this.business.hydrate()
- const webchatConfig = config.webchat === false ? false : {
- ...((businessData && businessData.webchat) || {}),
- ...(config.webchat || {}),
- }
+ const webchatConfig = config.webchat === false ?
+ false :
+ this.mergeWebchatConfig((businessData && businessData.webchat) || {}, config.webchat || {})
+
+ const hasExplicitBehaviourOverride = (
+ config.webchat &&
+ config.webchat !== false &&
+ Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour')
+ )
+ Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride
if (webchatConfig && webchatConfig.id) {
Configuration.webchat.assign(webchatConfig)
@@ -43,6 +49,28 @@ class Hellotext {
}
}
+ static mergeWebchatConfig(dashboardConfig, localConfig) {
+ return this.deepMergePlainObjects(dashboardConfig, localConfig)
+ }
+
+ static deepMergePlainObjects(base, override) {
+ const result = { ...base }
+
+ Object.entries(override).forEach(([key, value]) => {
+ if (this.isPlainObject(value) && this.isPlainObject(result[key])) {
+ result[key] = this.deepMergePlainObjects(result[key], value)
+ } else {
+ result[key] = value
+ }
+ })
+
+ return result
+ }
+
+ static isPlainObject(value) {
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
+ }
+
/**
* Tracks an action that has happened on the page
*
diff --git a/src/models/business.js b/src/models/business.js
index 07a114a..51ff205 100644
--- a/src/models/business.js
+++ b/src/models/business.js
@@ -10,6 +10,8 @@ import BusinessesAPI from '../api/businesses'
/**
* @typedef {Object} BusinessWebchat
* @property {String} [id] - Dashboard webchat id configured for the business.
+ * @property {Object} [appearance] - Dashboard appearance defaults for the webchat.
+ * @property {Object} [whatsapp] - Dashboard WhatsApp handoff defaults for the webchat.
*/
/**
diff --git a/src/models/webchat.js b/src/models/webchat.js
index 7337ece..5a0a607 100644
--- a/src/models/webchat.js
+++ b/src/models/webchat.js
@@ -16,9 +16,37 @@ class Webchat {
}
render() {
+ this.applyBehaviourOverride()
this.containerToAppendTo.appendChild(this.data.html)
}
+ applyBehaviourOverride() {
+ if (!Configuration.webchat.hasBehaviourOverride || !Configuration.webchat.behaviour) return
+
+ this.data.html.setAttribute(
+ 'data-hellotext--webchat-behaviour-value',
+ JSON.stringify(this.serializedBehaviour),
+ )
+ }
+
+ get serializedBehaviour() {
+ const behaviour = Configuration.webchat.behaviour
+
+ return {
+ trigger: this.serializeTrigger(behaviour.trigger),
+ delay_seconds: behaviour.delaySeconds,
+ first_visit_only: behaviour.firstVisitOnly,
+ once_per_session: behaviour.oncePerSession,
+ }
+ }
+
+ serializeTrigger(trigger) {
+ if (trigger === 'onLoad') return 'on_load'
+ if (trigger === 'onClick') return 'on_click'
+
+ return trigger
+ }
+
get containerToAppendTo() {
return document.querySelector(Configuration.webchat.container)
}
diff --git a/webpack.config.js b/webpack.config.js
index 4a67b01..e142ad5 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,10 +3,12 @@ module.exports = {
entry: ['@hotwired/stimulus', './src/index.bundle.js'],
output: {
filename: 'hellotext.js',
+ chunkFilename: '[name].js',
library: 'Hellotext',
libraryTarget: 'umd',
libraryExport: 'default',
globalObject: "typeof self !== 'undefined' ? self : this",
+ publicPath: 'auto',
},
module: {
rules: [